mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
8 Commits
crossQFile
...
local-runn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f64c389df9 | ||
|
|
5fa895e561 | ||
|
|
9f2eff21e2 | ||
|
|
4c0533c43f | ||
|
|
853cb4108d | ||
|
|
249b0bdbb4 | ||
|
|
fd363c2558 | ||
|
|
4535ac0cda |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -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']
|
||||
|
||||
432
.github/workflows/main.yml
vendored
432
.github/workflows/main.yml
vendored
@@ -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
5
.gitignore
vendored
@@ -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
7
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
14
defaults.pri
14
defaults.pri
@@ -1,14 +0,0 @@
|
||||
QT += gui bluetooth widgets xml positioning quick networkauth websockets texttospeech location multimedia
|
||||
QTPLUGIN += qavfmediaplayer
|
||||
QT+= charts
|
||||
|
||||
unix:android: QT += androidextras gui-private
|
||||
|
||||
android: include(android_openssl/openssl.pri)
|
||||
|
||||
INCLUDEPATH += $$PWD/src/qmdnsengine/src/include
|
||||
|
||||
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/src/android
|
||||
|
||||
ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64
|
||||
|
||||
@@ -4,20 +4,17 @@ QDomyos-Zwift can be installed from source on MacOs, Linux, Android and IOS.
|
||||
|
||||
Once you've installed QDomyos-Zwift, you can access the [operation guide](30_usage.md) for more information.
|
||||
|
||||
These instructions build the app itself, not the test project.
|
||||
|
||||
## On a Linux System (from source)
|
||||
|
||||
```buildoutcfg
|
||||
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
|
||||
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
|
||||
$ sudo 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
|
||||
|
||||

|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
1058
src/Computrainer.cpp
1058
src/Computrainer.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// I have consciously avoided putting things like data logging, lap marking,
|
||||
// intervals or any load management functions in this class. It is restricted
|
||||
// to controlling an reading telemetry from the device
|
||||
//
|
||||
// I expect higher order classes to implement such functions whilst
|
||||
// other devices (e.g. ANT+ devices) may be implemented with the same basic
|
||||
// interface
|
||||
//
|
||||
// I have avoided a base abstract class at this stage since I am uncertain
|
||||
// what core methods would be required by say, ANT+ or Tacx devices
|
||||
|
||||
#ifndef _Computrainer_h
|
||||
#define _Computrainer_h 1
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windef.h>
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winbase.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h> // unix!!
|
||||
#include <unistd.h> // unix!!
|
||||
#ifndef N_TTY // for OpenBSD, this is a hack XXX
|
||||
#define N_TTY 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#include <QAndroidJniObject>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* Some CT Microcontroller / Protocol Constants */
|
||||
|
||||
/* read timeouts in microseconds */
|
||||
#define CT_READTIMEOUT 1000
|
||||
#define CT_WRITETIMEOUT 2000
|
||||
|
||||
// message type
|
||||
#define CT_SPEED 0x01
|
||||
#define CT_POWER 0x02
|
||||
#define CT_HEARTRATE 0x03
|
||||
#define CT_CADENCE 0x06
|
||||
#define CT_RRC 0x09
|
||||
#define CT_SENSOR 0x0b
|
||||
|
||||
// buttons
|
||||
#define CT_RESET 0x01
|
||||
#define CT_F1 0x02
|
||||
#define CT_F3 0x04
|
||||
#define CT_PLUS 0x08
|
||||
#define CT_F2 0x10
|
||||
#define CT_MINUS 0x20
|
||||
#define CT_SSS 0x40 // spinscan sync is not a button!
|
||||
#define CT_NONE 0x80
|
||||
|
||||
/* Device operation mode */
|
||||
#define CT_ERGOMODE 0x01
|
||||
#define CT_SSMODE 0x02
|
||||
#define CT_CALIBRATE 0x04
|
||||
|
||||
/* UI operation mode */
|
||||
#define UI_MANUAL 0x01 // using +/- keys to adjust
|
||||
#define UI_ERG 0x02 // running an erg file!
|
||||
|
||||
/* Control status */
|
||||
#define CT_RUNNING 0x01
|
||||
#define CT_PAUSED 0x02
|
||||
|
||||
/* default operation mode */
|
||||
#define DEFAULT_MODE CT_ERGOMODE
|
||||
#define DEFAULT_LOAD 100.00
|
||||
#define DEFAULT_GRADIENT 2.00
|
||||
|
||||
class Computrainer : public QThread {
|
||||
|
||||
public:
|
||||
Computrainer(QObject *parent = 0, QString deviceFilename = 0); // pass device
|
||||
~Computrainer();
|
||||
|
||||
QObject *parent;
|
||||
|
||||
// HIGH-LEVEL FUNCTIONS
|
||||
int start(); // Calls QThread to start
|
||||
int restart(); // restart after paused
|
||||
int pause(); // pauses data collection, inbound telemetry is discarded
|
||||
int stop(); // stops data collection thread
|
||||
int quit(int error); // called by thread before exiting
|
||||
bool discover(QString deviceFilename); // confirm CT is attached to device
|
||||
|
||||
// SET
|
||||
void setDevice(QString deviceFilename); // setup the device filename
|
||||
void setLoad(double load); // set the load to generate in ERGOMODE
|
||||
void setGradient(double gradient); // set the load to generate in SSMODE
|
||||
void setMode(int mode,
|
||||
double load = DEFAULT_LOAD, // set mode to CT_ERGOMODE or CT_SSMODE
|
||||
double gradient = DEFAULT_GRADIENT);
|
||||
|
||||
// GET TELEMETRY AND STATUS
|
||||
// direct access to class variables is not allowed because we need to use wait conditions
|
||||
// to sync data read/writes between the run() thread and the main gui thread
|
||||
bool isCalibrated();
|
||||
bool isHRConnected();
|
||||
bool isCADConnected();
|
||||
void getTelemetry(double &Power, double &HeartRate, double &Cadence, double &Speed, double &RRC, bool &calibration,
|
||||
int &Buttons, uint8_t *ss, int &Status);
|
||||
void getSpinScan(double spinData[]);
|
||||
int getMode();
|
||||
double getGradient();
|
||||
double getLoad();
|
||||
|
||||
private:
|
||||
void run(); // called by start to kick off the CT comtrol thread
|
||||
|
||||
// 56 bytes comprise of 8 7byte command messages, where
|
||||
// the last is the set load / gradient respectively
|
||||
uint8_t ERGO_Command[56], SS_Command[56];
|
||||
|
||||
// Utility and BG Thread functions
|
||||
int openPort();
|
||||
int closePort();
|
||||
|
||||
// Protocol encoding
|
||||
void prepareCommand(int mode, double value); // sets up the command packet according to current settings
|
||||
int sendCommand(int mode); // writes a command to the device
|
||||
int calcCRC(int value); // calculates the checksum for the current command
|
||||
|
||||
// Protocol decoding
|
||||
int readMessage();
|
||||
void unpackTelemetry(int &b1, int &b2, int &b3, int &buttons, int &type, int &value8, int &value12);
|
||||
|
||||
// Mutex for controlling accessing private data
|
||||
QMutex pvars;
|
||||
|
||||
// INBOUND TELEMETRY - all volatile since it is updated by the run() thread
|
||||
volatile double devicePower; // current output power in Watts
|
||||
volatile double deviceHeartRate; // current heartrate in BPM
|
||||
volatile double deviceCadence; // current cadence in RPM
|
||||
volatile double deviceSpeed; // current speed in KPH
|
||||
volatile double deviceRRC; // calibrated Rolling Resistance
|
||||
volatile bool deviceCalibrated; // is it calibrated?
|
||||
volatile uint8_t spinScan[24]; // SS values only in SS_MODE
|
||||
volatile int deviceButtons; // Button status
|
||||
volatile bool deviceHRConnected; // HR jack is connected
|
||||
volatile bool deviceCADConnected; // Cadence jack is connected
|
||||
volatile int deviceStatus; // Device status running, paused, disconnected
|
||||
|
||||
// OUTBOUND COMMANDS - all volatile since it is updated by the GUI thread
|
||||
volatile int mode;
|
||||
volatile double load;
|
||||
volatile double gradient;
|
||||
|
||||
// i/o message holder
|
||||
uint8_t buf[7];
|
||||
|
||||
// device port
|
||||
QString deviceFilename;
|
||||
#ifdef WIN32
|
||||
HANDLE devicePort; // file descriptor for reading from com3
|
||||
DCB deviceSettings; // serial port settings baud rate et al
|
||||
#else
|
||||
int devicePort; // unix!!
|
||||
struct termios deviceSettings; // unix!!
|
||||
#endif
|
||||
// raw device utils
|
||||
int rawWrite(uint8_t *bytes, int size); // unix!!
|
||||
int rawRead(uint8_t *bytes, int size); // unix!!
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QList<jbyte> bufRX;
|
||||
bool cleanFrame = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
class CTsleeper : public QThread {
|
||||
public:
|
||||
static void msleep(unsigned long msecs); // inherited from QThread
|
||||
};
|
||||
|
||||
#endif // _GC_Computrainer_h
|
||||
@@ -1,165 +0,0 @@
|
||||
#include "CrossQFile.h"
|
||||
#include <QFileInfo>
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
#include <QtAndroidExtras/QtAndroid>
|
||||
#include <QtAndroidExtras/qandroidjnienvironment.h>
|
||||
#endif
|
||||
#include <QCoreApplication>
|
||||
|
||||
CrossQFile::CrossQFile(const QString& nameOrUri, const bool isUri) : QFile(nameOrUri), isWorkingWithUri(isUri){
|
||||
#ifdef __ANDROID__
|
||||
mainActivityObj = QtAndroid::androidActivity();
|
||||
contentResolverObj = mainActivityObj.callObjectMethod
|
||||
("getContentResolver","()Landroid/content/ContentResolver;");
|
||||
checkJenvExceptions();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
bool CrossQFile::checkJenvExceptions() const{
|
||||
QAndroidJniEnvironment env;
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
QAndroidJniObject CrossQFile::parseUriString(const QString& uriString) const{
|
||||
return QAndroidJniObject::callStaticObjectMethod
|
||||
("android/net/Uri" , "parse",
|
||||
"(Ljava/lang/String;)Landroid/net/Uri;",
|
||||
QAndroidJniObject::fromString(uriString).object());
|
||||
}
|
||||
#endif
|
||||
|
||||
void CrossQFile::setFileName(const QString& nameOrUri, bool isUri){
|
||||
QFile::setFileName(nameOrUri);
|
||||
isWorkingWithUri = isUri;
|
||||
}
|
||||
|
||||
|
||||
qint64 CrossQFile::size() const{
|
||||
#ifdef __ANDROID__
|
||||
if(isWorkingWithUri){
|
||||
QAndroidJniObject cursorObj {contentResolverObj.callObjectMethod
|
||||
("query",
|
||||
"(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;",
|
||||
parseUriString(fileName()).object(), QAndroidJniObject().object(), QAndroidJniObject().object()
|
||||
, QAndroidJniObject().object(), QAndroidJniObject().object())};
|
||||
int sizeIndex {cursorObj.callMethod<jint>
|
||||
("getColumnIndex","(Ljava/lang/String;)I",
|
||||
QAndroidJniObject::getStaticObjectField<jstring>
|
||||
("android/provider/OpenableColumns","SIZE").object())};
|
||||
cursorObj.callMethod<jboolean>("moveToFirst");
|
||||
qint64 ret {cursorObj.callMethod<jlong>("getLong","(I)J",sizeIndex)};
|
||||
if(checkJenvExceptions()){
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return QFile::size();
|
||||
}
|
||||
|
||||
bool CrossQFile::open(CrossQFile::OpenMode openMode){
|
||||
#ifdef __ANDROID__
|
||||
if(isWorkingWithUri){
|
||||
QAndroidJniObject jopenMode {QAndroidJniObject::fromString("rw")};
|
||||
switch (openMode){
|
||||
case QFile::ReadOnly:
|
||||
jopenMode = QAndroidJniObject::fromString("r");
|
||||
break;
|
||||
case QFile::WriteOnly:
|
||||
jopenMode = QAndroidJniObject::fromString("w");
|
||||
break;
|
||||
default:
|
||||
jopenMode = QAndroidJniObject::fromString("rw");
|
||||
}
|
||||
QAndroidJniObject pfdObj{contentResolverObj.callObjectMethod
|
||||
("openFileDescriptor", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
|
||||
parseUriString(fileName()).object(), jopenMode.object())};
|
||||
int fd{pfdObj.callMethod<jint>("detachFd")};
|
||||
bool ret {false};
|
||||
if(QFile::open(fd, openMode)){
|
||||
ret = true;
|
||||
}
|
||||
if(checkJenvExceptions()){
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return QFile::open(openMode);
|
||||
}
|
||||
QString CrossQFile::displayName(){
|
||||
#ifdef __ANDROID__
|
||||
if(isWorkingWithUri){
|
||||
QAndroidJniObject cursorObj {contentResolverObj.callObjectMethod
|
||||
("query",
|
||||
"(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;",
|
||||
parseUriString(fileName()).object(), QAndroidJniObject().object(), QAndroidJniObject().object(),
|
||||
QAndroidJniObject().object(), QAndroidJniObject().object())};
|
||||
cursorObj.callMethod<jboolean>("moveToFirst");
|
||||
QAndroidJniObject retObj{cursorObj.callObjectMethod
|
||||
("getString","(I)Ljava/lang/String;", cursorObj.callMethod<jint>
|
||||
("getColumnIndex","(Ljava/lang/String;)I",
|
||||
QAndroidJniObject::getStaticObjectField<jstring>
|
||||
("android/provider/OpenableColumns","DISPLAY_NAME").object()))};
|
||||
QString ret {retObj.toString()};
|
||||
if(checkJenvExceptions()){
|
||||
ret = "";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
QFileInfo fileInfo(fileName());
|
||||
return fileInfo.fileName();
|
||||
}
|
||||
|
||||
bool CrossQFile::remove(){
|
||||
#ifdef __ANDROID__
|
||||
if(isWorkingWithUri){
|
||||
bool ret {static_cast<bool>(QAndroidJniObject::callStaticMethod<jboolean>
|
||||
("android/provider/DocumentsContract", "deleteDocument",
|
||||
"(Landroid/content/ContentResolver;Landroid/net/Uri;)Z",
|
||||
contentResolverObj.object(), parseUriString(fileName()).object()))};
|
||||
if(checkJenvExceptions()){
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return QFile::remove();
|
||||
}
|
||||
|
||||
bool CrossQFile::rename(const QString& newName){
|
||||
if(!isWorkingWithUri){
|
||||
return QFile::rename(newName);
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CrossQFile::exists() const{
|
||||
#ifdef __ANDROID__
|
||||
if(isWorkingWithUri){
|
||||
bool ret {static_cast<bool>(QAndroidJniObject::callStaticMethod<jboolean>
|
||||
("android/provider/DocumentsContract", "isDocumentUri",
|
||||
"(Landroid/content/Context;Landroid/net/Uri;)Z",
|
||||
mainActivityObj.object(), parseUriString(fileName()).object()))};
|
||||
if(checkJenvExceptions()){
|
||||
ret = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return QFile::exists();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef CROSSQFILE_H
|
||||
#define CROSSQFILE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QSharedPointer>
|
||||
#ifdef __ANDROID__
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#endif
|
||||
|
||||
class CrossQFile : public QFile
|
||||
{
|
||||
public:
|
||||
CrossQFile(const QString& nameOrUri, const bool isUri = false);
|
||||
virtual qint64 size() const override;
|
||||
|
||||
//if working with uri, it uses QAndroidjniObject to open the file using the uri.
|
||||
//otherwise it would act like a normal QFile.
|
||||
bool open(CrossQFile::OpenMode openMode) override;
|
||||
bool remove();
|
||||
//if working with uri, it does nothing.
|
||||
//otherwise it would act like a normal QFile.
|
||||
bool rename(const QString& newName);
|
||||
bool exists() const;
|
||||
//returns the display name of the file.
|
||||
QString displayName();
|
||||
void setFileName(const QString& nameOrUri, bool isUri = false);
|
||||
private:
|
||||
bool isWorkingWithUri{false};
|
||||
#ifdef __ANDROID__
|
||||
QAndroidJniObject mainActivityObj;
|
||||
QAndroidJniObject contentResolverObj;
|
||||
bool checkJenvExceptions() const;
|
||||
QAndroidJniObject parseUriString(const QString& uriString) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#endif // CROSSQFILE_H
|
||||
|
||||
267
src/GPXList.qml
267
src/GPXList.qml
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
410
src/Home.qml
410
src/Home.qml
@@ -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
|
||||
|
||||
@@ -157,7 +157,7 @@ Page {
|
||||
width: parent.width
|
||||
anchors.top: row1.bottom
|
||||
anchors.topMargin: 30
|
||||
text: "This app should automatically connect to your bike/treadmill/rower. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill/rower should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issues, please feel free to contact me at roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
|
||||
text: "This app should automatically connects to your bike/treadmill. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issue, please, feel free to contact me to roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
|
||||
wrapMode: Label.WordWrap
|
||||
visible: rootItem.labelHelp
|
||||
}
|
||||
|
||||
@@ -1,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 ¢er) {
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
BIN
src/adb/adb.exe
BIN
src/adb/adb.exe
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
|
||||
public class NotificationUtils {
|
||||
|
||||
public static final int NOTIFICATION_ID = 1337;
|
||||
private static final String NOTIFICATION_CHANNEL_ID = "org.cagnulen.qdomyoszwift";
|
||||
private static final String NOTIFICATION_CHANNEL_NAME = "org.cagnulen.qdomyoszwift";
|
||||
|
||||
public static Pair<Integer, Notification> getNotification(Context context) {
|
||||
createNotificationChannel(context);
|
||||
Notification notification = createNotification(context);
|
||||
NotificationManager notificationManager
|
||||
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
return new Pair<>(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private static void createNotificationChannel(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
NOTIFICATION_CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private static Notification createNotification(Context context) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||
builder.setSmallIcon(R.drawable.icon);
|
||||
builder.setContentTitle("QZ Peloton Sync");
|
||||
builder.setContentText("Active!");
|
||||
builder.setOngoing(true);
|
||||
builder.setCategory(Notification.CATEGORY_SERVICE);
|
||||
builder.setPriority(Notification.PRIORITY_LOW);
|
||||
builder.setShowWhen(true);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.content.ContentValues.TAG;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.cgutman.androidremotedebugger.AdbUtils;
|
||||
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
|
||||
import com.cgutman.androidremotedebugger.devconn.DeviceConnection;
|
||||
import com.cgutman.androidremotedebugger.devconn.DeviceConnectionListener;
|
||||
import com.cgutman.androidremotedebugger.service.ShellService;
|
||||
import com.cgutman.adblib.AdbCrypto;
|
||||
|
||||
public class QZAdbRemote implements DeviceConnectionListener {
|
||||
private static ShellService.ShellServiceBinder binder;
|
||||
private static DeviceConnection connection;
|
||||
private static Intent service;
|
||||
private static final String LOG_TAG = "QZ:AdbRemote";
|
||||
private static String lastCommand = "";
|
||||
private static boolean ADBConnected = false;
|
||||
|
||||
private static String _address = "127.0.0.1";
|
||||
private static Context _context;
|
||||
|
||||
private static QZAdbRemote INSTANCE;
|
||||
|
||||
public static QZAdbRemote getInstance() {
|
||||
if(INSTANCE == null) {
|
||||
INSTANCE = new QZAdbRemote();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionEstablished(DeviceConnection devConn) {
|
||||
ADBConnected = true;
|
||||
Log.i(LOG_TAG, "notifyConnectionEstablished" + lastCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
|
||||
ADBConnected = false;
|
||||
Log.e(LOG_TAG, e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
|
||||
ADBConnected = false;
|
||||
Log.e(LOG_TAG, e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreamClosed(DeviceConnection devConn) {
|
||||
ADBConnected = false;
|
||||
Log.e(LOG_TAG, "notifyStreamClosed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
|
||||
return AdbUtils.readCryptoConfig(_context.getFilesDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReceiveData() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedData(DeviceConnection devConn, byte[] data, int offset, int length) {
|
||||
Log.i(LOG_TAG, data.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleUpdated(DeviceConnection devConn, ConsoleBuffer console) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private DeviceConnection startConnection(String host, int port) {
|
||||
/* Create the connection object */
|
||||
DeviceConnection conn = binder.createConnection(host, port);
|
||||
|
||||
/* Add this activity as a connection listener */
|
||||
binder.addListener(conn, this);
|
||||
|
||||
/* Begin the async connection process */
|
||||
conn.startConnect();
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
private DeviceConnection connectOrLookupConnection(String host, int port) {
|
||||
DeviceConnection conn = binder.findConnection(host, port);
|
||||
if (conn == null) {
|
||||
/* No existing connection, so start the connection process */
|
||||
conn = startConnection(host, port);
|
||||
}
|
||||
else {
|
||||
/* Add ourselves as a new listener of this connection */
|
||||
binder.addListener(conn, this);
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
public ServiceConnection serviceConn = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
|
||||
binder = (ShellService.ShellServiceBinder)arg1;
|
||||
if (connection != null) {
|
||||
binder.removeListener(connection, QZAdbRemote.getInstance());
|
||||
}
|
||||
connection = connectOrLookupConnection(_address, 5555);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName arg0) {
|
||||
binder = null;
|
||||
}
|
||||
};
|
||||
|
||||
static public void createConnection(String ip, Context context) {
|
||||
_address = ip;
|
||||
_context = context;
|
||||
|
||||
/* If we have old RSA keys, just use them */
|
||||
AdbCrypto crypto = AdbUtils.readCryptoConfig(_context.getFilesDir());
|
||||
if (crypto == null)
|
||||
{
|
||||
/* We need to make a new pair */
|
||||
Log.i(LOG_TAG,
|
||||
"This will only be done once.");
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdbCrypto crypto;
|
||||
|
||||
crypto = AdbUtils.writeNewCryptoConfig(_context.getFilesDir());
|
||||
|
||||
if (crypto == null)
|
||||
{
|
||||
Log.e(LOG_TAG,
|
||||
"Unable to generate and save RSA key pair");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
if (binder == null) {
|
||||
service = new Intent(_context, ShellService.class);
|
||||
|
||||
/* Bind the service if we're not bound already. After binding, the callback will
|
||||
* perform the initial connection. */
|
||||
_context.bindService(service, QZAdbRemote.getInstance().serviceConn, Service.BIND_AUTO_CREATE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
_context.startForegroundService(service);
|
||||
}
|
||||
else {
|
||||
_context.startService(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public void sendCommand(String command) {
|
||||
Log.d(LOG_TAG, "sendCommand " + ADBConnected + " " + command);
|
||||
if(ADBConnected) {
|
||||
StringBuilder commandBuffer = new StringBuilder();
|
||||
|
||||
commandBuffer.append(command);
|
||||
|
||||
/* Append a newline since it's not included in the command itself */
|
||||
commandBuffer.append('\n');
|
||||
|
||||
/* Send it to the device */
|
||||
connection.queueCommand(commandBuffer.toString());
|
||||
} else {
|
||||
Log.e(LOG_TAG, "sendCommand ADB is not connected!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.display.VirtualDisplay;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.media.projection.MediaProjection;
|
||||
import android.media.projection.MediaProjectionManager;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.mlkit.vision.common.InputImage;
|
||||
import com.google.mlkit.vision.text.Text;
|
||||
import com.google.mlkit.vision.text.TextRecognition;
|
||||
import com.google.mlkit.vision.text.TextRecognizer;
|
||||
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
|
||||
import android.media.ImageReader.OnImageAvailableListener;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Point;
|
||||
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
public class ScreenCaptureService extends Service {
|
||||
|
||||
private static final String TAG = "ScreenCaptureService";
|
||||
private static final String RESULT_CODE = "RESULT_CODE";
|
||||
private static final String DATA = "DATA";
|
||||
private static final String ACTION = "ACTION";
|
||||
private static final String START = "START";
|
||||
private static final String STOP = "STOP";
|
||||
private static final String SCREENCAP_NAME = "screencap";
|
||||
|
||||
private static int IMAGES_PRODUCED;
|
||||
|
||||
private MediaProjection mMediaProjection;
|
||||
private String mStoreDir;
|
||||
private ImageReader mImageReader;
|
||||
private Handler mHandler;
|
||||
private Display mDisplay;
|
||||
private VirtualDisplay mVirtualDisplay;
|
||||
private int mDensity;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private static int mWidthImage;
|
||||
private static int mHeightImage;
|
||||
private int mRotation;
|
||||
private OrientationChangeCallback mOrientationChangeCallback;
|
||||
|
||||
private TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
|
||||
|
||||
private static String lastText = "";
|
||||
private static String lastTextExtended = "";
|
||||
private static boolean isRunning = false;
|
||||
|
||||
public static String getLastText() {
|
||||
return lastText;
|
||||
}
|
||||
|
||||
public static String getLastTextExtended() {
|
||||
return lastTextExtended;
|
||||
}
|
||||
|
||||
public static int getImageWidth() {
|
||||
return mWidthImage;
|
||||
}
|
||||
|
||||
public static int getImageHeight() {
|
||||
return mHeightImage;
|
||||
}
|
||||
|
||||
public static Intent getStartIntent(Context context, int resultCode, Intent data) {
|
||||
Intent intent = new Intent(context, ScreenCaptureService.class);
|
||||
intent.putExtra(ACTION, START);
|
||||
intent.putExtra(RESULT_CODE, resultCode);
|
||||
intent.putExtra(DATA, data);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent getStopIntent(Context context) {
|
||||
Intent intent = new Intent(context, ScreenCaptureService.class);
|
||||
intent.putExtra(ACTION, STOP);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static boolean isStartCommand(Intent intent) {
|
||||
return intent.hasExtra(RESULT_CODE) && intent.hasExtra(DATA)
|
||||
&& intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), START);
|
||||
}
|
||||
|
||||
private static boolean isStopCommand(Intent intent) {
|
||||
return intent.hasExtra(ACTION) && Objects.equals(intent.getStringExtra(ACTION), STOP);
|
||||
}
|
||||
|
||||
private static int getVirtualDisplayFlags() {
|
||||
return DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
|
||||
}
|
||||
|
||||
private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
|
||||
@Override
|
||||
public void onImageAvailable(ImageReader reader) {
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try (Image image = mImageReader.acquireLatestImage()) {
|
||||
if (image != null) {
|
||||
if(!isRunning) {
|
||||
Image.Plane[] planes = image.getPlanes();
|
||||
ByteBuffer buffer = planes[0].getBuffer();
|
||||
int pixelStride = planes[0].getPixelStride();
|
||||
int rowStride = planes[0].getRowStride();
|
||||
int rowPadding = rowStride - pixelStride * mWidth;
|
||||
//Log.e(TAG, "Image reviewing");
|
||||
|
||||
isRunning = true;
|
||||
|
||||
// create bitmap
|
||||
mWidthImage = mWidth + rowPadding / pixelStride;
|
||||
mHeightImage = mHeight;
|
||||
final Bitmap bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888);
|
||||
bitmap.copyPixelsFromBuffer(buffer);
|
||||
/*
|
||||
// write bitmap to a file
|
||||
fos = new FileOutputStream(mStoreDir + "/myscreen.png");
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
|
||||
|
||||
IMAGES_PRODUCED++;
|
||||
Log.e(TAG, "captured image: " + IMAGES_PRODUCED);
|
||||
*/
|
||||
|
||||
InputImage inputImage = InputImage.fromBitmap(bitmap, 0);
|
||||
/*InputImage inputImage = InputImage.fromByteBuffer(buffer,
|
||||
mWidth + rowPadding / pixelStride, mHeight,
|
||||
0,
|
||||
InputImage.IMAGE_FORMAT_NV21 // or IMAGE_FORMAT_YV12
|
||||
);*/
|
||||
|
||||
Task<Text> result =
|
||||
recognizer.process(inputImage)
|
||||
.addOnSuccessListener(new OnSuccessListener<Text>() {
|
||||
@Override
|
||||
public void onSuccess(Text result) {
|
||||
// Task completed successfully
|
||||
|
||||
//Log.e(TAG, "Image done!");
|
||||
|
||||
String resultText = result.getText();
|
||||
lastText = resultText;
|
||||
lastTextExtended = "";
|
||||
for (Text.TextBlock block : result.getTextBlocks()) {
|
||||
String blockText = block.getText();
|
||||
Point[] blockCornerPoints = block.getCornerPoints();
|
||||
Rect blockFrame = block.getBoundingBox();
|
||||
lastTextExtended = lastTextExtended + blockText + "$$" + blockFrame.toString() + "§§";
|
||||
/*for (Text.Line line : block.getLines()) {
|
||||
String lineText = line.getText();
|
||||
Point[] lineCornerPoints = line.getCornerPoints();
|
||||
Rect lineFrame = line.getBoundingBox();
|
||||
for (Text.Element element : line.getElements()) {
|
||||
String elementText = element.getText();
|
||||
Point[] elementCornerPoints = element.getCornerPoints();
|
||||
Rect elementFrame = element.getBoundingBox();
|
||||
for (Text.Symbol symbol : element.getSymbols()) {
|
||||
String symbolText = symbol.getText();
|
||||
Point[] symbolCornerPoints = symbol.getCornerPoints();
|
||||
Rect symbolFrame = symbol.getBoundingBox();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
bitmap.recycle();
|
||||
isRunning = false;
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(
|
||||
new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// Task failed with an exception
|
||||
//Log.e(TAG, "Image fail");
|
||||
isRunning = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//Log.e(TAG, "Image ignored");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OrientationChangeCallback extends OrientationEventListener {
|
||||
|
||||
OrientationChangeCallback(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
final int rotation = mDisplay.getRotation();
|
||||
if (rotation != mRotation) {
|
||||
mRotation = rotation;
|
||||
try {
|
||||
// clean up
|
||||
if (mVirtualDisplay != null) mVirtualDisplay.release();
|
||||
if (mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
|
||||
|
||||
// re-create virtual display depending on device width / height
|
||||
createVirtualDisplay();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MediaProjectionStopCallback extends MediaProjection.Callback {
|
||||
@Override
|
||||
public void onStop() {
|
||||
Log.e(TAG, "stopping projection.");
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mVirtualDisplay != null) mVirtualDisplay.release();
|
||||
if (mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
|
||||
if (mOrientationChangeCallback != null) mOrientationChangeCallback.disable();
|
||||
mMediaProjection.unregisterCallback(MediaProjectionStopCallback.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// create store dir
|
||||
File externalFilesDir = getExternalFilesDir(null);
|
||||
if (externalFilesDir != null) {
|
||||
mStoreDir = externalFilesDir.getAbsolutePath() + "/screenshots/";
|
||||
File storeDirectory = new File(mStoreDir);
|
||||
if (!storeDirectory.exists()) {
|
||||
boolean success = storeDirectory.mkdirs();
|
||||
if (!success) {
|
||||
Log.e(TAG, "failed to create file storage directory.");
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
// start capture handling thread
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Looper.prepare();
|
||||
mHandler = new Handler();
|
||||
Looper.loop();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (isStartCommand(intent)) {
|
||||
// create notification
|
||||
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
|
||||
startForeground(notification.first, notification.second);
|
||||
// start projection
|
||||
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
|
||||
Intent data = intent.getParcelableExtra(DATA);
|
||||
startProjection(resultCode, data);
|
||||
} else if (isStopCommand(intent)) {
|
||||
stopProjection();
|
||||
stopSelf();
|
||||
} else {
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void startProjection(int resultCode, Intent data) {
|
||||
MediaProjectionManager mpManager =
|
||||
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
|
||||
if (mMediaProjection == null) {
|
||||
mMediaProjection = mpManager.getMediaProjection(resultCode, data);
|
||||
if (mMediaProjection != null) {
|
||||
// display metrics
|
||||
mDensity = Resources.getSystem().getDisplayMetrics().densityDpi;
|
||||
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
||||
mDisplay = windowManager.getDefaultDisplay();
|
||||
|
||||
// create virtual display depending on device width / height
|
||||
createVirtualDisplay();
|
||||
|
||||
// register orientation change callback
|
||||
mOrientationChangeCallback = new OrientationChangeCallback(this);
|
||||
if (mOrientationChangeCallback.canDetectOrientation()) {
|
||||
mOrientationChangeCallback.enable();
|
||||
}
|
||||
|
||||
// register media projection stop callback
|
||||
mMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopProjection() {
|
||||
if (mHandler != null) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mMediaProjection != null) {
|
||||
mMediaProjection.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
private void createVirtualDisplay() {
|
||||
// get width and height
|
||||
mWidth = Resources.getSystem().getDisplayMetrics().widthPixels;
|
||||
mHeight = Resources.getSystem().getDisplayMetrics().heightPixels;
|
||||
|
||||
// start capture reader
|
||||
mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2);
|
||||
mVirtualDisplay = mMediaProjection.createVirtualDisplay(SCREENCAP_NAME, mWidth, mHeight,
|
||||
mDensity, getVirtualDisplayFlags(), mImageReader.getSurface(), null, mHandler);
|
||||
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), mHandler);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dsi.ant.channel.AntChannel;
|
||||
@@ -33,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
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.util.Log;
|
||||
import android.app.Service;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.CommonUsbSerialPort;
|
||||
import com.hoho.android.usbserial.driver.Cp21xxSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.ProlificSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.UsbId;
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.UsbSerialPort;
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Deque;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Usbserial {
|
||||
|
||||
static UsbSerialPort port = null;
|
||||
static byte[] receiveData = new byte[4096];
|
||||
static int lastReadLen = 0;
|
||||
|
||||
public static void open(Context context) {
|
||||
Log.d("QZ","UsbSerial open");
|
||||
// Find all available drivers from attached devices.
|
||||
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
|
||||
if (availableDrivers.isEmpty()) {
|
||||
Log.d("QZ","UsbSerial no available drivers");
|
||||
return;
|
||||
}
|
||||
|
||||
// Open a connection to the first available driver.
|
||||
UsbSerialDriver driver = availableDrivers.get(0);
|
||||
if (!manager.hasPermission(driver.getDevice())) {
|
||||
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
|
||||
RingtoneManager.getRingtone(context, notification).play();
|
||||
|
||||
Log.d("QZ","USB permission ...");
|
||||
final Boolean[] granted = {null};
|
||||
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
granted[0] = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
|
||||
}
|
||||
};
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.cagnulen.qdomyoszwift.USB_PERMISSION"), flags);
|
||||
IntentFilter filter = new IntentFilter("org.cagnulen.qdomyoszwift.USB_PERMISSION");
|
||||
context.registerReceiver(usbReceiver, filter);
|
||||
manager.requestPermission(driver.getDevice(), permissionIntent);
|
||||
for(int i=0; i<5000; i++) {
|
||||
if(granted[0] != null) break;
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// Do something here
|
||||
}
|
||||
}
|
||||
Log.d("QZ","USB permission "+granted[0]);
|
||||
}
|
||||
|
||||
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
|
||||
if (connection == null) {
|
||||
Log.d("QZ","UsbSerial no permissions");
|
||||
// add UsbManager.requestPermission(driver.getDevice(), ..) handling here
|
||||
return;
|
||||
}
|
||||
|
||||
port = driver.getPorts().get(0); // Most devices have just one port (port 0)
|
||||
try {
|
||||
port.open(connection);
|
||||
port.setParameters(2400, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Do something here
|
||||
}
|
||||
|
||||
Log.d("QZ","UsbSerial port opened");
|
||||
}
|
||||
|
||||
public static void write (byte[] bytes) {
|
||||
if(port == null)
|
||||
return;
|
||||
|
||||
Log.d("QZ","UsbSerial writing " + new String(bytes, StandardCharsets.UTF_8));
|
||||
try {
|
||||
port.write(bytes, 2000);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Do something here
|
||||
}
|
||||
}
|
||||
|
||||
public static int readLen() {
|
||||
return lastReadLen;
|
||||
}
|
||||
|
||||
public static byte[] read() {
|
||||
if(port == null) {
|
||||
lastReadLen = 0;
|
||||
return receiveData;
|
||||
}
|
||||
|
||||
try {
|
||||
lastReadLen = port.read(receiveData, 2000);
|
||||
Log.d("QZ","UsbSerial reading " + lastReadLen + new String(receiveData, StandardCharsets.UTF_8));
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Do something here
|
||||
}
|
||||
return receiveData;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.cgutman.adblib;
|
||||
|
||||
/**
|
||||
* This interface specifies the required functions for AdbCrypto to
|
||||
* perform Base64 encoding of its public key.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public interface AdbBase64 {
|
||||
/**
|
||||
* This function must encoded the specified data as a base 64 string, without
|
||||
* appending any extra newlines or other characters.
|
||||
* @param data Data to encode
|
||||
* @return String containing base 64 encoded data
|
||||
*/
|
||||
public String encodeToString(byte[] data);
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
package com.cgutman.adblib;
|
||||
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This class represents an ADB connection.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class AdbConnection implements Closeable {
|
||||
|
||||
/** The underlying socket that this class uses to
|
||||
* communicate with the target device.
|
||||
*/
|
||||
private Socket socket;
|
||||
|
||||
/** The last allocated local stream ID. The ID
|
||||
* chosen for the next stream will be this value + 1.
|
||||
*/
|
||||
private int lastLocalId;
|
||||
|
||||
/**
|
||||
* The input stream that this class uses to read from
|
||||
* the socket.
|
||||
*/
|
||||
private InputStream inputStream;
|
||||
|
||||
/**
|
||||
* The output stream that this class uses to read from
|
||||
* the socket.
|
||||
*/
|
||||
OutputStream outputStream;
|
||||
|
||||
/**
|
||||
* The backend thread that handles responding to ADB packets.
|
||||
*/
|
||||
private Thread connectionThread;
|
||||
|
||||
/**
|
||||
* Specifies whether a connect has been attempted
|
||||
*/
|
||||
private boolean connectAttempted;
|
||||
|
||||
/**
|
||||
* Specifies whether a CNXN packet has been received from the peer.
|
||||
*/
|
||||
private boolean connected;
|
||||
|
||||
/**
|
||||
* Specifies the maximum amount data that can be sent to the remote peer.
|
||||
* This is only valid after connect() returns successfully.
|
||||
*/
|
||||
private int maxData;
|
||||
|
||||
/**
|
||||
* An initialized ADB crypto object that contains a key pair.
|
||||
*/
|
||||
private AdbCrypto crypto;
|
||||
|
||||
/**
|
||||
* Specifies whether this connection has already sent a signed token.
|
||||
*/
|
||||
private boolean sentSignature;
|
||||
|
||||
/**
|
||||
* A hash map of our open streams indexed by local ID.
|
||||
**/
|
||||
private HashMap<Integer, AdbStream> openStreams;
|
||||
|
||||
/**
|
||||
* Internal constructor to initialize some internal state
|
||||
*/
|
||||
private AdbConnection()
|
||||
{
|
||||
openStreams = new HashMap<Integer, AdbStream>();
|
||||
lastLocalId = 0;
|
||||
connectionThread = createConnectionThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a AdbConnection object associated with the socket and
|
||||
* crypto object specified.
|
||||
* @param socket The socket that the connection will use for communcation.
|
||||
* @param crypto The crypto object that stores the key pair for authentication.
|
||||
* @return A new AdbConnection object.
|
||||
* @throws IOException If there is a socket error
|
||||
*/
|
||||
public static AdbConnection create(Socket socket, AdbCrypto crypto) throws IOException
|
||||
{
|
||||
AdbConnection newConn = new AdbConnection();
|
||||
|
||||
newConn.crypto = crypto;
|
||||
|
||||
newConn.socket = socket;
|
||||
newConn.inputStream = socket.getInputStream();
|
||||
newConn.outputStream = socket.getOutputStream();
|
||||
|
||||
/* Disable Nagle because we're sending tiny packets */
|
||||
socket.setTcpNoDelay(true);
|
||||
|
||||
return newConn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection thread.
|
||||
* @return A new connection thread.
|
||||
*/
|
||||
private Thread createConnectionThread()
|
||||
{
|
||||
@SuppressWarnings("resource")
|
||||
final AdbConnection conn = this;
|
||||
return new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!connectionThread.isInterrupted())
|
||||
{
|
||||
try {
|
||||
/* Read and parse a message off the socket's input stream */
|
||||
AdbProtocol.AdbMessage msg = AdbProtocol.AdbMessage.parseAdbMessage(inputStream);
|
||||
|
||||
/* Verify magic and checksum */
|
||||
if (!AdbProtocol.validateMessage(msg))
|
||||
continue;
|
||||
|
||||
switch (msg.command)
|
||||
{
|
||||
/* Stream-oriented commands */
|
||||
case AdbProtocol.CMD_OKAY:
|
||||
case AdbProtocol.CMD_WRTE:
|
||||
case AdbProtocol.CMD_CLSE:
|
||||
/* We must ignore all packets when not connected */
|
||||
if (!conn.connected)
|
||||
continue;
|
||||
|
||||
/* Get the stream object corresponding to the packet */
|
||||
AdbStream waitingStream = openStreams.get(msg.arg1);
|
||||
if (waitingStream == null)
|
||||
continue;
|
||||
|
||||
synchronized (waitingStream) {
|
||||
if (msg.command == AdbProtocol.CMD_OKAY)
|
||||
{
|
||||
/* We're ready for writes */
|
||||
waitingStream.updateRemoteId(msg.arg0);
|
||||
waitingStream.readyForWrite();
|
||||
|
||||
/* Unwait an open/write */
|
||||
waitingStream.notify();
|
||||
}
|
||||
else if (msg.command == AdbProtocol.CMD_WRTE)
|
||||
{
|
||||
/* Got some data from our partner */
|
||||
waitingStream.addPayload(msg.payload);
|
||||
|
||||
/* Tell it we're ready for more */
|
||||
waitingStream.sendReady();
|
||||
}
|
||||
else if (msg.command == AdbProtocol.CMD_CLSE)
|
||||
{
|
||||
/* He doesn't like us anymore :-( */
|
||||
conn.openStreams.remove(msg.arg1);
|
||||
|
||||
/* Notify readers and writers */
|
||||
waitingStream.notifyClose();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AdbProtocol.CMD_AUTH:
|
||||
|
||||
byte[] packet;
|
||||
|
||||
if (msg.arg0 == AdbProtocol.AUTH_TYPE_TOKEN)
|
||||
{
|
||||
/* This is an authentication challenge */
|
||||
if (conn.sentSignature)
|
||||
{
|
||||
/* We've already tried our signature, so send our public key */
|
||||
packet = AdbProtocol.generateAuth(AdbProtocol.AUTH_TYPE_RSA_PUBLIC,
|
||||
conn.crypto.getAdbPublicKeyPayload());
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We'll sign the token */
|
||||
packet = AdbProtocol.generateAuth(AdbProtocol.AUTH_TYPE_SIGNATURE,
|
||||
conn.crypto.signAdbTokenPayload(msg.payload));
|
||||
conn.sentSignature = true;
|
||||
}
|
||||
|
||||
/* Write the AUTH reply */
|
||||
conn.outputStream.write(packet);
|
||||
conn.outputStream.flush();
|
||||
}
|
||||
break;
|
||||
|
||||
case AdbProtocol.CMD_CNXN:
|
||||
synchronized (conn) {
|
||||
/* We need to store the max data size */
|
||||
conn.maxData = msg.arg1;
|
||||
|
||||
/* Mark us as connected and unwait anyone waiting on the connection */
|
||||
conn.connected = true;
|
||||
conn.notifyAll();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unrecognized packet, just drop it */
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
/* The cleanup is taken care of by a combination of this thread
|
||||
* and close() */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* This thread takes care of cleaning up pending streams */
|
||||
synchronized (conn) {
|
||||
cleanupStreams();
|
||||
conn.notifyAll();
|
||||
conn.connectAttempted = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the max data size that the remote client supports.
|
||||
* A connection must have been attempted before calling this routine.
|
||||
* This routine will block if a connection is in progress.
|
||||
* @return The maximum data size indicated in the connect packet.
|
||||
* @throws InterruptedException If a connection cannot be waited on.
|
||||
* @throws IOException if the connection fails
|
||||
*/
|
||||
public int getMaxData() throws InterruptedException, IOException
|
||||
{
|
||||
if (!connectAttempted)
|
||||
throw new IllegalStateException("connect() must be called first");
|
||||
|
||||
synchronized (this) {
|
||||
/* Block if a connection is pending, but not yet complete */
|
||||
if (!connected)
|
||||
wait();
|
||||
|
||||
if (!connected) {
|
||||
throw new IOException("Connection failed");
|
||||
}
|
||||
}
|
||||
|
||||
return maxData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the remote device. This routine will block until the connection
|
||||
* completes.
|
||||
* @throws IOException If the socket fails while connecting
|
||||
* @throws InterruptedException If we are unable to wait for the connection to finish
|
||||
*/
|
||||
public void connect() throws IOException, InterruptedException
|
||||
{
|
||||
if (connected)
|
||||
throw new IllegalStateException("Already connected");
|
||||
|
||||
/* Write the CONNECT packet */
|
||||
outputStream.write(AdbProtocol.generateConnect());
|
||||
outputStream.flush();
|
||||
|
||||
/* Start the connection thread to respond to the peer */
|
||||
connectAttempted = true;
|
||||
connectionThread.start();
|
||||
|
||||
/* Wait for the connection to go live */
|
||||
synchronized (this) {
|
||||
if (!connected)
|
||||
wait();
|
||||
|
||||
if (!connected) {
|
||||
throw new IOException("Connection failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an AdbStream object corresponding to the specified destination.
|
||||
* This routine will block until the connection completes.
|
||||
* @param destination The destination to open on the target
|
||||
* @return AdbStream object corresponding to the specified destination
|
||||
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
|
||||
* @throws IOException If the stream fails while sending the packet
|
||||
* @throws InterruptedException If we are unable to wait for the connection to finish
|
||||
*/
|
||||
public AdbStream open(String destination) throws UnsupportedEncodingException, IOException, InterruptedException
|
||||
{
|
||||
int localId = ++lastLocalId;
|
||||
|
||||
if (!connectAttempted)
|
||||
throw new IllegalStateException("connect() must be called first");
|
||||
|
||||
/* Wait for the connect response */
|
||||
synchronized (this) {
|
||||
if (!connected)
|
||||
wait();
|
||||
|
||||
if (!connected) {
|
||||
throw new IOException("Connection failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* Add this stream to this list of half-open streams */
|
||||
AdbStream stream = new AdbStream(this, localId);
|
||||
openStreams.put(localId, stream);
|
||||
|
||||
/* Send the open */
|
||||
outputStream.write(AdbProtocol.generateOpen(localId, destination));
|
||||
outputStream.flush();
|
||||
|
||||
/* Wait for the connection thread to receive the OKAY */
|
||||
synchronized (stream) {
|
||||
stream.wait();
|
||||
}
|
||||
|
||||
/* Check if the open was rejected */
|
||||
if (stream.isClosed())
|
||||
throw new ConnectException("Stream open actively rejected by remote peer");
|
||||
|
||||
/* We're fully setup now */
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function terminates all I/O on streams associated with this ADB connection
|
||||
*/
|
||||
private void cleanupStreams() {
|
||||
/* Close all streams on this connection */
|
||||
for (AdbStream s : openStreams.values()) {
|
||||
/* We handle exceptions for each close() call to avoid
|
||||
* terminating cleanup for one failed close(). */
|
||||
try {
|
||||
s.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
/* No open streams anymore */
|
||||
openStreams.clear();
|
||||
}
|
||||
|
||||
/** This routine closes the Adb connection and underlying socket
|
||||
* @throws IOException if the socket fails to close
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
/* If the connection thread hasn't spawned yet, there's nothing to do */
|
||||
if (connectionThread == null)
|
||||
return;
|
||||
|
||||
/* Closing the socket will kick the connection thread */
|
||||
socket.close();
|
||||
|
||||
/* Wait for the connection thread to die */
|
||||
connectionThread.interrupt();
|
||||
try {
|
||||
connectionThread.join();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
package com.cgutman.adblib;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.EncodedKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* This class encapsulates the ADB cryptography functions and provides
|
||||
* an interface for the storage and retrieval of keys.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class AdbCrypto {
|
||||
|
||||
/** An RSA keypair encapsulated by the AdbCrypto object */
|
||||
private KeyPair keyPair;
|
||||
|
||||
/** The base 64 conversion interface to use */
|
||||
private AdbBase64 base64;
|
||||
|
||||
/** The ADB RSA key length in bits */
|
||||
public static final int KEY_LENGTH_BITS = 2048;
|
||||
|
||||
/** The ADB RSA key length in bytes */
|
||||
public static final int KEY_LENGTH_BYTES = KEY_LENGTH_BITS / 8;
|
||||
|
||||
/** The ADB RSA key length in words */
|
||||
public static final int KEY_LENGTH_WORDS = KEY_LENGTH_BYTES / 4;
|
||||
|
||||
/** The RSA signature padding as an int array */
|
||||
public static final int[] SIGNATURE_PADDING_AS_INT = new int[]
|
||||
{
|
||||
0x00,0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
||||
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,
|
||||
0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,
|
||||
0x04,0x14
|
||||
};
|
||||
|
||||
/** The RSA signature padding as a byte array */
|
||||
public static byte[] SIGNATURE_PADDING;
|
||||
|
||||
static {
|
||||
SIGNATURE_PADDING = new byte[SIGNATURE_PADDING_AS_INT.length];
|
||||
|
||||
for (int i = 0; i < SIGNATURE_PADDING.length; i++)
|
||||
SIGNATURE_PADDING[i] = (byte)SIGNATURE_PADDING_AS_INT[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a standard RSAPublicKey object to the special ADB format
|
||||
* @param pubkey RSAPublicKey object to convert
|
||||
* @return Byte array containing the converted RSAPublicKey object
|
||||
*/
|
||||
private static byte[] convertRsaPublicKeyToAdbFormat(RSAPublicKey pubkey)
|
||||
{
|
||||
/*
|
||||
* ADB literally just saves the RSAPublicKey struct to a file.
|
||||
*
|
||||
* typedef struct RSAPublicKey {
|
||||
* int len; // Length of n[] in number of uint32_t
|
||||
* uint32_t n0inv; // -1 / n[0] mod 2^32
|
||||
* uint32_t n[RSANUMWORDS]; // modulus as little endian array
|
||||
* uint32_t rr[RSANUMWORDS]; // R^2 as little endian array
|
||||
* int exponent; // 3 or 65537
|
||||
* } RSAPublicKey;
|
||||
*/
|
||||
|
||||
/* ------ This part is a Java-ified version of RSA_to_RSAPublicKey from adb_host_auth.c ------ */
|
||||
BigInteger r32, r, rr, rem, n, n0inv;
|
||||
|
||||
r32 = BigInteger.ZERO.setBit(32);
|
||||
n = pubkey.getModulus();
|
||||
r = BigInteger.ZERO.setBit(KEY_LENGTH_WORDS * 32);
|
||||
rr = r.modPow(BigInteger.valueOf(2), n);
|
||||
rem = n.remainder(r32);
|
||||
n0inv = rem.modInverse(r32);
|
||||
|
||||
int myN[] = new int[KEY_LENGTH_WORDS];
|
||||
int myRr[] = new int[KEY_LENGTH_WORDS];
|
||||
BigInteger res[];
|
||||
for (int i = 0; i < KEY_LENGTH_WORDS; i++)
|
||||
{
|
||||
res = rr.divideAndRemainder(r32);
|
||||
rr = res[0];
|
||||
rem = res[1];
|
||||
myRr[i] = rem.intValue();
|
||||
|
||||
res = n.divideAndRemainder(r32);
|
||||
n = res[0];
|
||||
rem = res[1];
|
||||
myN[i] = rem.intValue();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
|
||||
ByteBuffer bbuf = ByteBuffer.allocate(524).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
|
||||
bbuf.putInt(KEY_LENGTH_WORDS);
|
||||
bbuf.putInt(n0inv.negate().intValue());
|
||||
for (int i : myN)
|
||||
bbuf.putInt(i);
|
||||
for (int i : myRr)
|
||||
bbuf.putInt(i);
|
||||
|
||||
bbuf.putInt(pubkey.getPublicExponent().intValue());
|
||||
return bbuf.array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AdbCrypto object from a key pair loaded from files.
|
||||
* @param base64 Implementation of base 64 conversion interface required by ADB
|
||||
* @param privateKey File containing the RSA private key
|
||||
* @param publicKey File containing the RSA public key
|
||||
* @return New AdbCrypto object
|
||||
* @throws IOException If the files cannot be read
|
||||
* @throws NoSuchAlgorithmException If an RSA key factory cannot be found
|
||||
* @throws InvalidKeySpecException If a PKCS8 or X509 key spec cannot be found
|
||||
*/
|
||||
public static AdbCrypto loadAdbKeyPair(AdbBase64 base64, File privateKey, File publicKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
AdbCrypto crypto = new AdbCrypto();
|
||||
|
||||
int privKeyLength = (int)privateKey.length();
|
||||
int pubKeyLength = (int)publicKey.length();
|
||||
byte[] privKeyBytes = new byte[privKeyLength];
|
||||
byte[] pubKeyBytes = new byte[pubKeyLength];
|
||||
|
||||
FileInputStream privIn = new FileInputStream(privateKey);
|
||||
FileInputStream pubIn = new FileInputStream(publicKey);
|
||||
|
||||
privIn.read(privKeyBytes);
|
||||
pubIn.read(pubKeyBytes);
|
||||
|
||||
privIn.close();
|
||||
pubIn.close();
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
|
||||
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKeyBytes);
|
||||
|
||||
crypto.keyPair = new KeyPair(keyFactory.generatePublic(publicKeySpec),
|
||||
keyFactory.generatePrivate(privateKeySpec));
|
||||
crypto.base64 = base64;
|
||||
|
||||
return crypto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AdbCrypto object by generating a new key pair.
|
||||
* @param base64 Implementation of base 64 conversion interface required by ADB
|
||||
* @return A new AdbCrypto object
|
||||
* @throws NoSuchAlgorithmException If an RSA key factory cannot be found
|
||||
*/
|
||||
public static AdbCrypto generateAdbKeyPair(AdbBase64 base64) throws NoSuchAlgorithmException
|
||||
{
|
||||
AdbCrypto crypto = new AdbCrypto();
|
||||
|
||||
KeyPairGenerator rsaKeyPg = KeyPairGenerator.getInstance("RSA");
|
||||
rsaKeyPg.initialize(KEY_LENGTH_BITS);
|
||||
|
||||
crypto.keyPair = rsaKeyPg.genKeyPair();
|
||||
crypto.base64 = base64;
|
||||
|
||||
return crypto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the ADB SHA1 payload with the private key of this object.
|
||||
* @param payload SHA1 payload to sign
|
||||
* @return Signed SHA1 payload
|
||||
* @throws GeneralSecurityException If signing fails
|
||||
*/
|
||||
public byte[] signAdbTokenPayload(byte[] payload) throws GeneralSecurityException
|
||||
{
|
||||
Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
|
||||
|
||||
c.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
|
||||
|
||||
c.update(SIGNATURE_PADDING);
|
||||
|
||||
return c.doFinal(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the RSA public key in ADB format.
|
||||
* @return Byte array containing the RSA public key in ADB format.
|
||||
* @throws IOException If the key cannot be retrived
|
||||
*/
|
||||
public byte[] getAdbPublicKeyPayload() throws IOException
|
||||
{
|
||||
byte[] convertedKey = convertRsaPublicKeyToAdbFormat((RSAPublicKey)keyPair.getPublic());
|
||||
StringBuilder keyString = new StringBuilder(720);
|
||||
|
||||
/* The key is base64 encoded with a user@host suffix and terminated with a NUL */
|
||||
keyString.append(base64.encodeToString(convertedKey));
|
||||
keyString.append(" unknown@unknown");
|
||||
keyString.append('\0');
|
||||
|
||||
return keyString.toString().getBytes("UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the AdbCrypto's key pair to the specified files.
|
||||
* @param privateKey The file to store the encoded private key
|
||||
* @param publicKey The file to store the encoded public key
|
||||
* @throws IOException If the files cannot be written
|
||||
*/
|
||||
public void saveAdbKeyPair(File privateKey, File publicKey) throws IOException
|
||||
{
|
||||
FileOutputStream privOut = new FileOutputStream(privateKey);
|
||||
FileOutputStream pubOut = new FileOutputStream(publicKey);
|
||||
|
||||
privOut.write(keyPair.getPrivate().getEncoded());
|
||||
pubOut.write(keyPair.getPublic().getEncoded());
|
||||
|
||||
privOut.close();
|
||||
pubOut.close();
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package com.cgutman.adblib;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides useful functions and fields for ADB protocol details.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class AdbProtocol {
|
||||
|
||||
/** The length of the ADB message header */
|
||||
public static final int ADB_HEADER_LENGTH = 24;
|
||||
|
||||
public static final int CMD_SYNC = 0x434e5953;
|
||||
|
||||
/** CNXN is the connect message. No messages (except AUTH)
|
||||
* are valid before this message is received. */
|
||||
public static final int CMD_CNXN = 0x4e584e43;
|
||||
|
||||
/** The current version of the ADB protocol */
|
||||
public static final int CONNECT_VERSION = 0x01000000;
|
||||
|
||||
/** The maximum data payload supported by the ADB implementation */
|
||||
public static final int CONNECT_MAXDATA = 4096;
|
||||
|
||||
/** The payload sent with the connect message */
|
||||
public static byte[] CONNECT_PAYLOAD;
|
||||
static {
|
||||
try {
|
||||
CONNECT_PAYLOAD = "host::\0".getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {}
|
||||
}
|
||||
|
||||
/** AUTH is the authentication message. It is part of the
|
||||
* RSA public key authentication added in Android 4.2.2. */
|
||||
public static final int CMD_AUTH = 0x48545541;
|
||||
|
||||
/** This authentication type represents a SHA1 hash to sign */
|
||||
public static final int AUTH_TYPE_TOKEN = 1;
|
||||
|
||||
/** This authentication type represents the signed SHA1 hash */
|
||||
public static final int AUTH_TYPE_SIGNATURE = 2;
|
||||
|
||||
/** This authentication type represents a RSA public key */
|
||||
public static final int AUTH_TYPE_RSA_PUBLIC = 3;
|
||||
|
||||
/** OPEN is the open stream message. It is sent to open
|
||||
* a new stream on the target device. */
|
||||
public static final int CMD_OPEN = 0x4e45504f;
|
||||
|
||||
/** OKAY is a success message. It is sent when a write is
|
||||
* processed successfully. */
|
||||
public static final int CMD_OKAY = 0x59414b4f;
|
||||
|
||||
/** CLSE is the close stream message. It it sent to close an
|
||||
* existing stream on the target device. */
|
||||
public static final int CMD_CLSE = 0x45534c43;
|
||||
|
||||
/** WRTE is the write stream message. It is sent with a payload
|
||||
* that is the data to write to the stream. */
|
||||
public static final int CMD_WRTE = 0x45545257;
|
||||
|
||||
/**
|
||||
* This function performs a checksum on the ADB payload data.
|
||||
* @param payload Payload to checksum
|
||||
* @return The checksum of the payload
|
||||
*/
|
||||
private static int getPayloadChecksum(byte[] payload)
|
||||
{
|
||||
int checksum = 0;
|
||||
|
||||
for (byte b : payload)
|
||||
{
|
||||
/* We have to manually "unsign" these bytes because Java sucks */
|
||||
if (b >= 0)
|
||||
checksum += b;
|
||||
else
|
||||
checksum += b+256;
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function validate the ADB message by checking
|
||||
* its command, magic, and payload checksum.
|
||||
* @param msg ADB message to validate
|
||||
* @return True if the message was valid, false otherwise
|
||||
*/
|
||||
public static boolean validateMessage(AdbMessage msg)
|
||||
{
|
||||
/* Magic is cmd ^ 0xFFFFFFFF */
|
||||
if (msg.command != (msg.magic ^ 0xFFFFFFFF))
|
||||
return false;
|
||||
|
||||
if (msg.payloadLength != 0)
|
||||
{
|
||||
if (getPayloadChecksum(msg.payload) != msg.checksum)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generates an ADB message given the fields.
|
||||
* @param cmd Command identifier
|
||||
* @param arg0 First argument
|
||||
* @param arg1 Second argument
|
||||
* @param payload Data payload
|
||||
* @return Byte array containing the message
|
||||
*/
|
||||
public static byte[] generateMessage(int cmd, int arg0, int arg1, byte[] payload)
|
||||
{
|
||||
/* struct message {
|
||||
* unsigned command; // command identifier constant
|
||||
* unsigned arg0; // first argument
|
||||
* unsigned arg1; // second argument
|
||||
* unsigned data_length; // length of payload (0 is allowed)
|
||||
* unsigned data_check; // checksum of data payload
|
||||
* unsigned magic; // command ^ 0xffffffff
|
||||
* };
|
||||
*/
|
||||
|
||||
ByteBuffer message;
|
||||
|
||||
if (payload != null)
|
||||
{
|
||||
message = ByteBuffer.allocate(ADB_HEADER_LENGTH + payload.length).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
message.putInt(cmd);
|
||||
message.putInt(arg0);
|
||||
message.putInt(arg1);
|
||||
|
||||
if (payload != null)
|
||||
{
|
||||
message.putInt(payload.length);
|
||||
message.putInt(getPayloadChecksum(payload));
|
||||
}
|
||||
else
|
||||
{
|
||||
message.putInt(0);
|
||||
message.putInt(0);
|
||||
}
|
||||
|
||||
message.putInt(cmd ^ 0xFFFFFFFF);
|
||||
|
||||
if (payload != null)
|
||||
{
|
||||
message.put(payload);
|
||||
}
|
||||
|
||||
return message.array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a connect message with default parameters.
|
||||
* @return Byte array containing the message
|
||||
*/
|
||||
public static byte[] generateConnect()
|
||||
{
|
||||
return generateMessage(CMD_CNXN, CONNECT_VERSION, CONNECT_MAXDATA, CONNECT_PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an auth message with the specified type and payload.
|
||||
* @param type Authentication type (see AUTH_TYPE_* constants)
|
||||
* @param data The payload for the message
|
||||
* @return Byte array containing the message
|
||||
*/
|
||||
public static byte[] generateAuth(int type, byte[] data)
|
||||
{
|
||||
return generateMessage(CMD_AUTH, type, 0, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an open stream message with the specified local ID and destination.
|
||||
* @param localId A unique local ID identifying the stream
|
||||
* @param dest The destination of the stream on the target
|
||||
* @return Byte array containing the message
|
||||
* @throws UnsupportedEncodingException If the destination cannot be encoded to UTF-8
|
||||
*/
|
||||
public static byte[] generateOpen(int localId, String dest) throws UnsupportedEncodingException
|
||||
{
|
||||
ByteBuffer bbuf = ByteBuffer.allocate(dest.length() + 1);
|
||||
bbuf.put(dest.getBytes("UTF-8"));
|
||||
bbuf.put((byte)0);
|
||||
return generateMessage(CMD_OPEN, localId, 0, bbuf.array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a write stream message with the specified IDs and payload.
|
||||
* @param localId The unique local ID of the stream
|
||||
* @param remoteId The unique remote ID of the stream
|
||||
* @param data The data to provide as the write payload
|
||||
* @return Byte array containing the message
|
||||
*/
|
||||
public static byte[] generateWrite(int localId, int remoteId, byte[] data)
|
||||
{
|
||||
return generateMessage(CMD_WRTE, localId, remoteId, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a close stream message with the specified IDs.
|
||||
* @param localId The unique local ID of the stream
|
||||
* @param remoteId The unique remote ID of the stream
|
||||
* @return Byte array containing the message
|
||||
*/
|
||||
public static byte[] generateClose(int localId, int remoteId)
|
||||
{
|
||||
return generateMessage(CMD_CLSE, localId, remoteId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an okay message with the specified IDs.
|
||||
* @param localId The unique local ID of the stream
|
||||
* @param remoteId The unique remote ID of the stream
|
||||
* @return Byte array containing the message
|
||||
*/
|
||||
public static byte[] generateReady(int localId, int remoteId)
|
||||
{
|
||||
return generateMessage(CMD_OKAY, localId, remoteId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides an abstraction for the ADB message format.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
final static class AdbMessage {
|
||||
/** The command field of the message */
|
||||
public int command;
|
||||
/** The arg0 field of the message */
|
||||
public int arg0;
|
||||
/** The arg1 field of the message */
|
||||
public int arg1;
|
||||
/** The payload length field of the message */
|
||||
public int payloadLength;
|
||||
/** The checksum field of the message */
|
||||
public int checksum;
|
||||
/** The magic field of the message */
|
||||
public int magic;
|
||||
/** The payload of the message */
|
||||
public byte[] payload;
|
||||
|
||||
/**
|
||||
* Read and parse an ADB message from the supplied input stream.
|
||||
* This message is NOT validated.
|
||||
* @param in InputStream object to read data from
|
||||
* @return An AdbMessage object represented the message read
|
||||
* @throws IOException If the stream fails while reading
|
||||
*/
|
||||
public static AdbMessage parseAdbMessage(InputStream in) throws IOException
|
||||
{
|
||||
AdbMessage msg = new AdbMessage();
|
||||
ByteBuffer packet = ByteBuffer.allocate(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
/* Read the header first */
|
||||
int dataRead = 0;
|
||||
do
|
||||
{
|
||||
int bytesRead = in.read(packet.array(), dataRead, 24 - dataRead);
|
||||
|
||||
if (bytesRead < 0)
|
||||
throw new IOException("Stream closed");
|
||||
else
|
||||
dataRead += bytesRead;
|
||||
}
|
||||
while (dataRead < ADB_HEADER_LENGTH);
|
||||
|
||||
/* Pull out header fields */
|
||||
msg.command = packet.getInt();
|
||||
msg.arg0 = packet.getInt();
|
||||
msg.arg1 = packet.getInt();
|
||||
msg.payloadLength = packet.getInt();
|
||||
msg.checksum = packet.getInt();
|
||||
msg.magic = packet.getInt();
|
||||
|
||||
/* If there's a payload supplied, read that too */
|
||||
if (msg.payloadLength != 0)
|
||||
{
|
||||
msg.payload = new byte[msg.payloadLength];
|
||||
|
||||
dataRead = 0;
|
||||
do
|
||||
{
|
||||
int bytesRead = in.read(msg.payload, dataRead, msg.payloadLength - dataRead);
|
||||
|
||||
if (bytesRead < 0)
|
||||
throw new IOException("Stream closed");
|
||||
else
|
||||
dataRead += bytesRead;
|
||||
}
|
||||
while (dataRead < msg.payloadLength);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package com.cgutman.adblib;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* This class abstracts the underlying ADB streams
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
public class AdbStream implements Closeable {
|
||||
|
||||
/** The AdbConnection object that the stream communicates over */
|
||||
private AdbConnection adbConn;
|
||||
|
||||
/** The local ID of the stream */
|
||||
private int localId;
|
||||
|
||||
/** The remote ID of the stream */
|
||||
private int remoteId;
|
||||
|
||||
/** Indicates whether a write is currently allowed */
|
||||
private AtomicBoolean writeReady;
|
||||
|
||||
/** A queue of data from the target's write packets */
|
||||
private Queue<byte[]> readQueue;
|
||||
|
||||
/** Indicates whether the connection is closed already */
|
||||
private boolean isClosed;
|
||||
|
||||
/**
|
||||
* Creates a new AdbStream object on the specified AdbConnection
|
||||
* with the given local ID.
|
||||
* @param adbConn AdbConnection that this stream is running on
|
||||
* @param localId Local ID of the stream
|
||||
*/
|
||||
public AdbStream(AdbConnection adbConn, int localId)
|
||||
{
|
||||
this.adbConn = adbConn;
|
||||
this.localId = localId;
|
||||
this.readQueue = new ConcurrentLinkedQueue<byte[]>();
|
||||
this.writeReady = new AtomicBoolean(false);
|
||||
this.isClosed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection thread to indicate newly received data.
|
||||
* @param payload Data inside the write message
|
||||
*/
|
||||
void addPayload(byte[] payload)
|
||||
{
|
||||
synchronized (readQueue) {
|
||||
readQueue.add(payload);
|
||||
readQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection thread to send an OKAY packet, allowing the
|
||||
* other side to continue transmission.
|
||||
* @throws IOException If the connection fails while sending the packet
|
||||
*/
|
||||
void sendReady() throws IOException
|
||||
{
|
||||
/* Generate and send a READY packet */
|
||||
byte[] packet = AdbProtocol.generateReady(localId, remoteId);
|
||||
adbConn.outputStream.write(packet);
|
||||
adbConn.outputStream.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection thread to update the remote ID for this stream
|
||||
* @param remoteId New remote ID
|
||||
*/
|
||||
void updateRemoteId(int remoteId)
|
||||
{
|
||||
this.remoteId = remoteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection thread to indicate the stream is okay to send data.
|
||||
*/
|
||||
void readyForWrite()
|
||||
{
|
||||
writeReady.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the connection thread to notify that the stream was closed by the peer.
|
||||
*/
|
||||
void notifyClose()
|
||||
{
|
||||
/* We don't call close() because it sends another CLOSE */
|
||||
isClosed = true;
|
||||
|
||||
/* Unwait readers and writers */
|
||||
synchronized (this) {
|
||||
notifyAll();
|
||||
}
|
||||
synchronized (readQueue) {
|
||||
readQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a pending write payload from the other side.
|
||||
* @return Byte array containing the payload of the write
|
||||
* @throws InterruptedException If we are unable to wait for data
|
||||
* @throws IOException If the stream fails while waiting
|
||||
*/
|
||||
public byte[] read() throws InterruptedException, IOException
|
||||
{
|
||||
byte[] data = null;
|
||||
|
||||
synchronized (readQueue) {
|
||||
/* Wait for the connection to close or data to be received */
|
||||
while (!isClosed && (data = readQueue.poll()) == null) {
|
||||
readQueue.wait();
|
||||
}
|
||||
|
||||
if (isClosed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a write packet with a given String payload.
|
||||
* @param payload Payload in the form of a String
|
||||
* @throws IOException If the stream fails while sending data
|
||||
* @throws InterruptedException If we are unable to wait to send data
|
||||
*/
|
||||
public void write(String payload) throws IOException, InterruptedException
|
||||
{
|
||||
/* ADB needs null-terminated strings */
|
||||
write(payload.getBytes("UTF-8"), false);
|
||||
write(new byte[]{0}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a write packet with a given byte array payload.
|
||||
* @param payload Payload in the form of a byte array
|
||||
* @throws IOException If the stream fails while sending data
|
||||
* @throws InterruptedException If we are unable to wait to send data
|
||||
*/
|
||||
public void write(byte[] payload) throws IOException, InterruptedException
|
||||
{
|
||||
write(payload, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a write packet and optionally sends it immediately.
|
||||
* @param payload Payload in the form of a byte array
|
||||
* @param flush Specifies whether to send the packet immediately
|
||||
* @throws IOException If the stream fails while sending data
|
||||
* @throws InterruptedException If we are unable to wait to send data
|
||||
*/
|
||||
public void write(byte[] payload, boolean flush) throws IOException, InterruptedException
|
||||
{
|
||||
synchronized (this) {
|
||||
/* Make sure we're ready for a write */
|
||||
while (!isClosed && !writeReady.compareAndSet(true, false))
|
||||
wait();
|
||||
|
||||
if (isClosed) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a WRITE packet and send it */
|
||||
byte[] packet = AdbProtocol.generateWrite(localId, remoteId, payload);
|
||||
adbConn.outputStream.write(packet);
|
||||
|
||||
if (flush)
|
||||
adbConn.outputStream.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream. This sends a close message to the peer.
|
||||
* @throws IOException If the stream fails while sending the close message.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
synchronized (this) {
|
||||
/* This may already be closed by the remote host */
|
||||
if (isClosed)
|
||||
return;
|
||||
|
||||
/* Notify readers/writers that we've closed */
|
||||
notifyClose();
|
||||
}
|
||||
|
||||
byte[] packet = AdbProtocol.generateClose(localId, remoteId);
|
||||
adbConn.outputStream.write(packet);
|
||||
adbConn.outputStream.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives whether the stream is closed or not
|
||||
* @return True if the stream is close, false if not
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return isClosed;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* This package provides a native Java implementation of the ADB protocol.
|
||||
* @author Cameron Gutman
|
||||
*/
|
||||
package com.cgutman.adblib;
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.cgutman.adblib.AdbCrypto;
|
||||
import com.cgutman.androidremotedebugger.adblib.AndroidBase64;
|
||||
|
||||
public class AdbUtils {
|
||||
|
||||
public static final String PUBLIC_KEY_NAME = "public.key";
|
||||
public static final String PRIVATE_KEY_NAME = "private.key";
|
||||
|
||||
public static AdbCrypto readCryptoConfig(File dataDir) {
|
||||
File pubKey = new File(dataDir, PUBLIC_KEY_NAME);
|
||||
File privKey = new File(dataDir, PRIVATE_KEY_NAME);
|
||||
|
||||
AdbCrypto crypto = null;
|
||||
if (pubKey.exists() && privKey.exists())
|
||||
{
|
||||
try {
|
||||
crypto = AdbCrypto.loadAdbKeyPair(new AndroidBase64(), privKey, pubKey);
|
||||
} catch (Exception e) {
|
||||
crypto = null;
|
||||
}
|
||||
}
|
||||
|
||||
return crypto;
|
||||
}
|
||||
|
||||
public static AdbCrypto writeNewCryptoConfig(File dataDir) {
|
||||
File pubKey = new File(dataDir, PUBLIC_KEY_NAME);
|
||||
File privKey = new File(dataDir, PRIVATE_KEY_NAME);
|
||||
|
||||
AdbCrypto crypto = null;
|
||||
|
||||
try {
|
||||
crypto = AdbCrypto.generateAdbKeyPair(new AndroidBase64());
|
||||
crypto.saveAdbKeyPair(privKey, pubKey);
|
||||
} catch (Exception e) {
|
||||
crypto = null;
|
||||
}
|
||||
|
||||
return crypto;
|
||||
}
|
||||
|
||||
public static boolean safeClose(Closeable c) {
|
||||
if (c == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
c.close();
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void safeAsyncClose(final Closeable c) {
|
||||
if (c == null)
|
||||
return;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
c.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.adblib;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import com.cgutman.adblib.AdbBase64;
|
||||
|
||||
public class AndroidBase64 implements AdbBase64 {
|
||||
@Override
|
||||
public String encodeToString(byte[] data) {
|
||||
return Base64.encodeToString(data, Base64.NO_WRAP);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.console;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.Menu;
|
||||
|
||||
public class CommandHistory {
|
||||
private SharedPreferences prefs;
|
||||
private LinkedList<String> previousCommands;
|
||||
private int historyLimit;
|
||||
|
||||
public static CommandHistory loadCommandHistoryFromPrefs(int limit, Context context, String pref) {
|
||||
CommandHistory ch = new CommandHistory(limit);
|
||||
|
||||
ch.prefs = context.getSharedPreferences(pref, 0);
|
||||
int size = ch.prefs.getInt("Size", 0);
|
||||
for (int i = 0; i < size; i++) {
|
||||
String cmd = ch.prefs.getString(""+i, null);
|
||||
if (cmd != null)
|
||||
ch.add(cmd);
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
private CommandHistory(int historyLimit) {
|
||||
this.previousCommands = new LinkedList<String>();
|
||||
this.historyLimit = historyLimit;
|
||||
}
|
||||
|
||||
public void add(String command) {
|
||||
if (previousCommands.size() > historyLimit)
|
||||
previousCommands.removeFirst();
|
||||
|
||||
previousCommands.add(command);
|
||||
}
|
||||
|
||||
public void populateMenu(ContextMenu menu) {
|
||||
/* We iterate backwards because the first item added is the latest in the command list */
|
||||
for (int i = previousCommands.size()-1; i >= 0; i--)
|
||||
menu.add(Menu.NONE, 0, Menu.NONE, previousCommands.get(i));
|
||||
}
|
||||
|
||||
public void save() {
|
||||
SharedPreferences.Editor edit = prefs.edit();
|
||||
for (int i = 0; i < previousCommands.size(); i++)
|
||||
{
|
||||
edit.putString(""+i, previousCommands.get(i));
|
||||
}
|
||||
edit.putInt("Size", previousCommands.size());
|
||||
edit.apply();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.console;
|
||||
|
||||
import android.widget.TextView;
|
||||
|
||||
public class ConsoleBuffer {
|
||||
private char[] buffer;
|
||||
private int amountPopulated;
|
||||
|
||||
public ConsoleBuffer(int bufferSize)
|
||||
{
|
||||
buffer = new char[bufferSize];
|
||||
amountPopulated = 0;
|
||||
}
|
||||
|
||||
public synchronized void append(byte[] asciiData, int offset, int length)
|
||||
{
|
||||
if (amountPopulated + length > buffer.length)
|
||||
{
|
||||
/* Move the old data backwards */
|
||||
System.arraycopy(buffer,
|
||||
length,
|
||||
buffer,
|
||||
0,
|
||||
amountPopulated - length);
|
||||
|
||||
amountPopulated -= length;
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
buffer[amountPopulated++] = (char)asciiData[offset+i];
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updateTextView(TextView textView)
|
||||
{
|
||||
textView.setText(buffer, 0, amountPopulated);
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.devconn;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import com.cgutman.adblib.AdbConnection;
|
||||
import com.cgutman.adblib.AdbCrypto;
|
||||
import com.cgutman.adblib.AdbStream;
|
||||
import com.cgutman.androidremotedebugger.AdbUtils;
|
||||
|
||||
public class DeviceConnection implements Closeable {
|
||||
private static final int CONN_TIMEOUT = 5000;
|
||||
|
||||
private String host;
|
||||
private int port;
|
||||
private DeviceConnectionListener listener;
|
||||
|
||||
private AdbConnection connection;
|
||||
private AdbStream shellStream;
|
||||
|
||||
private boolean closed;
|
||||
private boolean foreground;
|
||||
|
||||
private LinkedBlockingQueue<byte[]> commandQueue = new LinkedBlockingQueue<byte[]>();
|
||||
|
||||
public DeviceConnection(DeviceConnectionListener listener, String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.listener = listener;
|
||||
this.foreground = true; /* Connections start in the foreground */
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public boolean queueCommand(String command) {
|
||||
try {
|
||||
/* Queue it up for sending to the device */
|
||||
commandQueue.add(command.getBytes("UTF-8"));
|
||||
return true;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean queueBytes(byte[] buffer) {
|
||||
/* Queue it up for sending to the device */
|
||||
commandQueue.add(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void startConnect() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean connected = false;
|
||||
Socket socket = new Socket();
|
||||
AdbCrypto crypto;
|
||||
|
||||
/* Load the crypto config */
|
||||
crypto = listener.loadAdbCrypto(DeviceConnection.this);
|
||||
if (crypto == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
/* Establish a connect to the remote host */
|
||||
socket.connect(new InetSocketAddress(host, port), CONN_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
listener.notifyConnectionFailed(DeviceConnection.this, e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
/* Establish the application layer connection */
|
||||
connection = AdbConnection.create(socket, crypto);
|
||||
connection.connect();
|
||||
|
||||
/* Open the shell stream */
|
||||
shellStream = connection.open("shell:");
|
||||
connected = true;
|
||||
} catch (IOException e) {
|
||||
listener.notifyConnectionFailed(DeviceConnection.this, e);
|
||||
} catch (InterruptedException e) {
|
||||
listener.notifyConnectionFailed(DeviceConnection.this, e);
|
||||
} finally {
|
||||
/* Cleanup if the connection failed */
|
||||
if (!connected) {
|
||||
AdbUtils.safeClose(shellStream);
|
||||
|
||||
/* The AdbConnection object will close the underlying socket
|
||||
* but we need to close it ourselves if the AdbConnection object
|
||||
* wasn't successfully constructed.
|
||||
*/
|
||||
if (!AdbUtils.safeClose(connection)) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Notify the listener that the connection is complete */
|
||||
listener.notifyConnectionEstablished(DeviceConnection.this);
|
||||
|
||||
/* Start the receive thread */
|
||||
startReceiveThread();
|
||||
|
||||
/* Enter the blocking send loop */
|
||||
sendLoop();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void sendLoop() {
|
||||
/* We become the send thread */
|
||||
try {
|
||||
for (;;) {
|
||||
/* Get the next command */
|
||||
byte[] command = commandQueue.take();
|
||||
|
||||
/* This may be a close indication */
|
||||
if (shellStream.isClosed()) {
|
||||
listener.notifyStreamClosed(DeviceConnection.this);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Issue it to the device */
|
||||
shellStream.write(command);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
listener.notifyStreamFailed(DeviceConnection.this, e);
|
||||
} catch (InterruptedException e) {
|
||||
} finally {
|
||||
AdbUtils.safeClose(DeviceConnection.this);
|
||||
}
|
||||
}
|
||||
|
||||
private void startReceiveThread() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!shellStream.isClosed()) {
|
||||
byte[] data = shellStream.read();
|
||||
listener.receivedData(DeviceConnection.this, data, 0, data.length);
|
||||
}
|
||||
listener.notifyStreamClosed(DeviceConnection.this);
|
||||
} catch (IOException e) {
|
||||
listener.notifyStreamFailed(DeviceConnection.this, e);
|
||||
} catch (InterruptedException e) {
|
||||
} finally {
|
||||
AdbUtils.safeClose(DeviceConnection.this);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (isClosed()) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
/* Close the stream first */
|
||||
AdbUtils.safeClose(shellStream);
|
||||
|
||||
/* Now the connection (and underlying socket) */
|
||||
AdbUtils.safeClose(connection);
|
||||
|
||||
/* Finally signal the command queue to allow the send thread to terminate */
|
||||
commandQueue.add(new byte[0]);
|
||||
}
|
||||
|
||||
public boolean isForeground() {
|
||||
return foreground;
|
||||
}
|
||||
|
||||
public void setForeground(boolean foreground) {
|
||||
this.foreground = foreground;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.devconn;
|
||||
|
||||
import com.cgutman.adblib.AdbCrypto;
|
||||
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
|
||||
|
||||
public interface DeviceConnectionListener {
|
||||
public void notifyConnectionEstablished(DeviceConnection devConn);
|
||||
|
||||
public void notifyConnectionFailed(DeviceConnection devConn, Exception e);
|
||||
|
||||
public void notifyStreamFailed(DeviceConnection devConn, Exception e);
|
||||
|
||||
public void notifyStreamClosed(DeviceConnection devConn);
|
||||
|
||||
public AdbCrypto loadAdbCrypto(DeviceConnection devConn);
|
||||
|
||||
public boolean canReceiveData();
|
||||
|
||||
public void receivedData(DeviceConnection devConn, byte[] data, int offset, int length);
|
||||
|
||||
public boolean isConsole();
|
||||
|
||||
public void consoleUpdated(DeviceConnection devConn, ConsoleBuffer console);
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import android.app.Service;
|
||||
|
||||
import com.cgutman.adblib.AdbCrypto;
|
||||
import com.cgutman.androidremotedebugger.AdbUtils;
|
||||
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
|
||||
import com.cgutman.androidremotedebugger.devconn.DeviceConnection;
|
||||
import com.cgutman.androidremotedebugger.devconn.DeviceConnectionListener;
|
||||
|
||||
public class ShellListener implements DeviceConnectionListener {
|
||||
private static final int TERM_LENGTH = 25000;
|
||||
|
||||
private final HashMap<DeviceConnection, LinkedList<DeviceConnectionListener>> listenerMap =
|
||||
new HashMap<DeviceConnection, LinkedList<DeviceConnectionListener>>();
|
||||
private final ConcurrentHashMap<DeviceConnection, ConsoleBuffer> consoleMap =
|
||||
new ConcurrentHashMap<DeviceConnection, ConsoleBuffer>();
|
||||
private Service service;
|
||||
|
||||
public ShellListener(Service service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public void addListener(DeviceConnection conn, DeviceConnectionListener listener) {
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(conn);
|
||||
if (listeners != null) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
else {
|
||||
listeners = new LinkedList<DeviceConnectionListener>();
|
||||
listeners.add(listener);
|
||||
listenerMap.put(conn, listeners);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the listener supports console input, we'll tell them about the console buffer
|
||||
* by firing them an initial console updated callback */
|
||||
ConsoleBuffer console = consoleMap.get(conn);
|
||||
if (console != null && listener.isConsole()) {
|
||||
listener.consoleUpdated(conn, console);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(DeviceConnection conn, DeviceConnectionListener listener) {
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(conn);
|
||||
if (listeners != null) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionEstablished(DeviceConnection devConn) {
|
||||
consoleMap.put(devConn, new ConsoleBuffer(TERM_LENGTH));
|
||||
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
|
||||
if (listeners != null) {
|
||||
for (DeviceConnectionListener listener : listeners) {
|
||||
listener.notifyConnectionEstablished(devConn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
|
||||
if (listeners != null) {
|
||||
for (DeviceConnectionListener listener : listeners) {
|
||||
listener.notifyConnectionFailed(devConn, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
|
||||
/* Return if this connection has already "failed" */
|
||||
if (consoleMap.remove(devConn) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
|
||||
if (listeners != null) {
|
||||
for (DeviceConnectionListener listener : listeners) {
|
||||
listener.notifyStreamFailed(devConn, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreamClosed(DeviceConnection devConn) {
|
||||
/* Return if this connection has already "failed" */
|
||||
if (consoleMap.remove(devConn) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
|
||||
if (listeners != null) {
|
||||
for (DeviceConnectionListener listener : listeners) {
|
||||
listener.notifyStreamClosed(devConn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
|
||||
return AdbUtils.readCryptoConfig(service.getFilesDir());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedData(DeviceConnection devConn, byte[] data,
|
||||
int offset, int length) {
|
||||
/* Add data to the console for this connection */
|
||||
ConsoleBuffer console = consoleMap.get(devConn);
|
||||
if (console != null) {
|
||||
/* Hack to remove the bell from the end of the prompt */
|
||||
if (data[offset+length-1] == 0x07) {
|
||||
length--;
|
||||
}
|
||||
|
||||
console.append(data, offset, length);
|
||||
|
||||
/* Attempt to deliver a console update notification */
|
||||
synchronized (listenerMap) {
|
||||
LinkedList<DeviceConnectionListener> listeners = listenerMap.get(devConn);
|
||||
if (listeners != null) {
|
||||
for (DeviceConnectionListener listener : listeners) {
|
||||
if (listener.isConsole()) {
|
||||
listener.consoleUpdated(devConn, console);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReceiveData() {
|
||||
/* We can always receive data */
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleUpdated(DeviceConnection devConsole,
|
||||
ConsoleBuffer console) {
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.service;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.cgutman.adblib.AdbCrypto;
|
||||
import com.cgutman.androidremotedebugger.console.ConsoleBuffer;
|
||||
import com.cgutman.androidremotedebugger.devconn.DeviceConnection;
|
||||
import com.cgutman.androidremotedebugger.devconn.DeviceConnectionListener;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.net.wifi.WifiManager.WifiLock;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
public class ShellService extends Service implements DeviceConnectionListener {
|
||||
|
||||
private ShellServiceBinder binder = new ShellServiceBinder();
|
||||
private ShellListener listener = new ShellListener(this);
|
||||
|
||||
private HashMap<String, DeviceConnection> currentConnectionMap =
|
||||
new HashMap<String, DeviceConnection>();
|
||||
|
||||
private WifiLock wlanLock;
|
||||
private WakeLock wakeLock;
|
||||
|
||||
private final static int FOREGROUND_PLACEHOLDER_ID = 1;
|
||||
private final static int CONN_BASE = 12131;
|
||||
private final static int FAILED_BASE = 12111;
|
||||
private final static String CHANNEL_ID = "connectionInfo";
|
||||
|
||||
private int foregroundId;
|
||||
|
||||
public class ShellServiceBinder extends Binder {
|
||||
public DeviceConnection createConnection(String host, int port) {
|
||||
DeviceConnection conn = new DeviceConnection(listener, host, port);
|
||||
listener.addListener(conn, ShellService.this);
|
||||
return conn;
|
||||
}
|
||||
|
||||
public DeviceConnection findConnection(String host, int port) {
|
||||
String connStr = host+":"+port;
|
||||
return currentConnectionMap.get(connStr);
|
||||
}
|
||||
|
||||
public void notifyPausingActivity(DeviceConnection devConn) {
|
||||
devConn.setForeground(false);
|
||||
}
|
||||
|
||||
public void notifyResumingActivity(DeviceConnection devConn) {
|
||||
devConn.setForeground(true);
|
||||
}
|
||||
|
||||
public void notifyDestroyingActivity(DeviceConnection devConn) {
|
||||
/* If we're pausing before destruction after the connection is closed, remove the failure
|
||||
* notification */
|
||||
if (devConn.isClosed()) {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(getFailedNotificationId(devConn));
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(DeviceConnection conn, DeviceConnectionListener listener) {
|
||||
ShellService.this.listener.addListener(conn, listener);
|
||||
}
|
||||
|
||||
public void removeListener(DeviceConnection conn, DeviceConnectionListener listener) {
|
||||
ShellService.this.listener.removeListener(conn, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent arg0) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
/* Stop the the service if no connections remain */
|
||||
if (currentConnectionMap.isEmpty()) {
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (foregroundId == 0) {
|
||||
// If we're not already running in the foreground, use a placeholder
|
||||
// notification until a real connection is established. After connection
|
||||
// establishment, the real notification will replace this one.
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
|
||||
}
|
||||
|
||||
// Don't restart if we've been killed. We will have already lost our connections
|
||||
// when we died, so we'll just be running doing nothing if the OS restarted us.
|
||||
return Service.START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Connection Info", NotificationManager.IMPORTANCE_DEFAULT);
|
||||
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
wlanLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, "RemoteADBShell:ShellService");
|
||||
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "RemoteADBShell:ShellService");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (wlanLock.isHeld()) {
|
||||
wlanLock.release();
|
||||
}
|
||||
if (wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private int getFailedNotificationId(DeviceConnection devConn) {
|
||||
return FAILED_BASE + getConnectionString(devConn).hashCode();
|
||||
}
|
||||
|
||||
private int getConnectedNotificationId(DeviceConnection devConn) {
|
||||
return CONN_BASE + getConnectionString(devConn).hashCode();
|
||||
}
|
||||
|
||||
private Notification createForegroundPlaceholderNotification() {
|
||||
return new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setOngoing(true)
|
||||
.setSilent(true)
|
||||
.setContentTitle("Remote ADB Shell")
|
||||
.setContentText("Connecting...")
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Notification createConnectionNotification(DeviceConnection devConn, boolean connected) {
|
||||
String ticker;
|
||||
String message;
|
||||
|
||||
if (connected) {
|
||||
ticker = "Connection Established";
|
||||
message = "Connected to "+getConnectionString(devConn);
|
||||
}
|
||||
else {
|
||||
ticker = "Connection Terminated";
|
||||
message = "Connection to "+getConnectionString(devConn)+" failed";
|
||||
}
|
||||
|
||||
return new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setTicker("Remote ADB Shell - "+ticker)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setOngoing(connected)
|
||||
.setAutoCancel(!connected)
|
||||
.setSilent(connected)
|
||||
.setContentTitle("Remote ADB Shell")
|
||||
.setContentText(message)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void updateNotification(DeviceConnection devConn, boolean connected) {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
removeNotification(devConn);
|
||||
}
|
||||
|
||||
private void removeNotification(DeviceConnection devConn) {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
/* Removing failure notifications is easy */
|
||||
nm.cancel(getFailedNotificationId(devConn));
|
||||
|
||||
/* Connected notifications is a bit more complex */
|
||||
if (getConnectedNotificationId(devConn) == foregroundId) {
|
||||
/* We're the foreground notification, so we need to switch in another
|
||||
* notification to take our place */
|
||||
|
||||
/* Search for a new device connection to promote */
|
||||
DeviceConnection newConn = null;
|
||||
for (DeviceConnection conn : currentConnectionMap.values()) {
|
||||
if (devConn == conn) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
newConn = conn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newConn == null) {
|
||||
/* None found, so we're done in foreground */
|
||||
stopForeground(true);
|
||||
foregroundId = 0;
|
||||
}
|
||||
else {
|
||||
/* Found one, so cancel this guy's original notification
|
||||
* and start it as foreground */
|
||||
foregroundId = getConnectedNotificationId(newConn);
|
||||
nm.cancel(foregroundId);
|
||||
startForeground(foregroundId, createConnectionNotification(newConn, true));
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* This just a normal connected notification */
|
||||
nm.cancel(getConnectedNotificationId(devConn));
|
||||
}
|
||||
}
|
||||
|
||||
private String getConnectionString(DeviceConnection devConn) {
|
||||
return devConn.getHost()+":"+devConn.getPort();
|
||||
}
|
||||
|
||||
private synchronized void addNewConnection(DeviceConnection devConn) {
|
||||
if (currentConnectionMap.isEmpty()) {
|
||||
wakeLock.acquire();
|
||||
wlanLock.acquire();
|
||||
}
|
||||
|
||||
currentConnectionMap.put(getConnectionString(devConn), devConn);
|
||||
}
|
||||
|
||||
private synchronized void removeConnection(DeviceConnection devConn) {
|
||||
currentConnectionMap.remove(getConnectionString(devConn));
|
||||
|
||||
/* Stop the the service if no connections remain */
|
||||
if (currentConnectionMap.isEmpty()) {
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionEstablished(DeviceConnection devConn) {
|
||||
addNewConnection(devConn);
|
||||
updateNotification(devConn, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
|
||||
/* No notification is displaying here */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
|
||||
updateNotification(devConn, false);
|
||||
removeConnection(devConn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStreamClosed(DeviceConnection devConn) {
|
||||
updateNotification(devConn, false);
|
||||
removeConnection(devConn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receivedData(DeviceConnection devConn, byte[] data, int offset,
|
||||
int length) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReceiveData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consoleUpdated(DeviceConnection devConn,
|
||||
ConsoleBuffer console) {
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
|
||||
public class Dialog implements Runnable {
|
||||
private String title, message;
|
||||
private Activity activity;
|
||||
private boolean endAfterDismiss;
|
||||
|
||||
AlertDialog alert;
|
||||
|
||||
private static ArrayList<Dialog> rundownDialogs = new ArrayList<Dialog>();
|
||||
|
||||
public Dialog(Activity activity, String title, String message, boolean endAfterDismiss)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.endAfterDismiss = endAfterDismiss;
|
||||
}
|
||||
|
||||
public static void closeDialogs()
|
||||
{
|
||||
for (Dialog d : rundownDialogs)
|
||||
d.alert.dismiss();
|
||||
|
||||
rundownDialogs.clear();
|
||||
}
|
||||
|
||||
public static void displayDialog(Activity activity, String title, String message, boolean endAfterDismiss)
|
||||
{
|
||||
activity.runOnUiThread(new Dialog(activity, title, message, endAfterDismiss));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// If we're dying, don't bother creating a dialog
|
||||
if (activity.isFinishing())
|
||||
return;
|
||||
|
||||
alert = new AlertDialog.Builder(activity).create();
|
||||
|
||||
alert.setTitle(title);
|
||||
alert.setMessage(message);
|
||||
alert.setCancelable(false);
|
||||
alert.setCanceledOnTouchOutside(false);
|
||||
|
||||
alert.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
alert.dismiss();
|
||||
rundownDialogs.remove(this);
|
||||
|
||||
if (endAfterDismiss)
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
|
||||
rundownDialogs.add(this);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.cgutman.androidremotedebugger.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
|
||||
public class SpinnerDialog implements Runnable,OnCancelListener {
|
||||
private String title, message;
|
||||
private Activity activity;
|
||||
private ProgressDialog progress;
|
||||
private boolean finish;
|
||||
|
||||
private static ArrayList<SpinnerDialog> rundownDialogs = new ArrayList<SpinnerDialog>();
|
||||
|
||||
public SpinnerDialog(Activity activity, String title, String message, boolean finish)
|
||||
{
|
||||
this.activity = activity;
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.progress = null;
|
||||
this.finish = finish;
|
||||
}
|
||||
|
||||
public static SpinnerDialog displayDialog(Activity activity, String title, String message, boolean finish)
|
||||
{
|
||||
SpinnerDialog spinner = new SpinnerDialog(activity, title, message, finish);
|
||||
activity.runOnUiThread(spinner);
|
||||
return spinner;
|
||||
}
|
||||
|
||||
public static void closeDialogs()
|
||||
{
|
||||
for (SpinnerDialog d : rundownDialogs)
|
||||
d.progress.dismiss();
|
||||
|
||||
rundownDialogs.clear();
|
||||
}
|
||||
|
||||
public void dismiss()
|
||||
{
|
||||
// Running again with progress != null will destroy it
|
||||
activity.runOnUiThread(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (progress == null)
|
||||
{
|
||||
// If we're dying, don't bother creating a dialog
|
||||
if (activity.isFinishing())
|
||||
return;
|
||||
|
||||
progress = new ProgressDialog(activity);
|
||||
|
||||
progress.setTitle(title);
|
||||
progress.setMessage(message);
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progress.setOnCancelListener(this);
|
||||
|
||||
// If we want to finish the activity when this is killed, make it cancellable
|
||||
if (finish)
|
||||
{
|
||||
progress.setCancelable(true);
|
||||
progress.setCanceledOnTouchOutside(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.setCancelable(false);
|
||||
}
|
||||
|
||||
progress.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
progress.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
// This will only be called if finish was true, so we don't need to check again
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
@@ -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 ");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#include "androidactivityresultreceiver.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
AndroidActivityResultReceiver::AndroidActivityResultReceiver() { qDebug() << "AndroidActivityResultReceiver"; }
|
||||
|
||||
void AndroidActivityResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode,
|
||||
const QAndroidJniObject &data) {
|
||||
qDebug() << "AndroidActivityResultReceiver::handleActivityResult" << receiverRequestCode << resultCode;
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/MediaProjection", "startService",
|
||||
"(Landroid/content/Context;ILandroid/content/Intent;)V",
|
||||
QtAndroid::androidContext().object(), resultCode, data.object<jobject>());
|
||||
}
|
||||
#endif
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef ANDROIDACTIVITYRESULTRECEIVER_H
|
||||
#define ANDROIDACTIVITYRESULTRECEIVER_H
|
||||
#include <QDebug>
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QtAndroidExtras/QAndroidActivityResultReceiver>
|
||||
#include <QtAndroidExtras/QAndroidJniEnvironment>
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
#include <QtAndroidExtras/QtAndroid>
|
||||
|
||||
class AndroidActivityResultReceiver : public QAndroidActivityResultReceiver {
|
||||
public:
|
||||
AndroidActivityResultReceiver();
|
||||
virtual void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data);
|
||||
};
|
||||
#endif
|
||||
#endif // ANDROIDACTIVITYRESULTRECEIVER_H
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "androidadblog.h"
|
||||
|
||||
androidadblog::androidadblog() { }
|
||||
|
||||
void androidadblog::run() {
|
||||
while (1) {
|
||||
runAdbTailCommand("logcat");
|
||||
}
|
||||
}
|
||||
|
||||
void androidadblog::runAdbTailCommand(QString command) {
|
||||
#ifdef Q_OS_ANDROID
|
||||
auto process = new QProcess;
|
||||
QObject::connect(process, &QProcess::readyReadStandardOutput, [process, this]() {
|
||||
QString output = process->readAllStandardOutput();
|
||||
qDebug() << "adbLogCat STDOUT << " << output;
|
||||
});
|
||||
QObject::connect(process, &QProcess::readyReadStandardError, [process, this]() {
|
||||
auto output = process->readAllStandardError();
|
||||
qDebug() << "adbLogCat ERROR << " << output;
|
||||
});
|
||||
QStringList arguments;
|
||||
arguments.append("*:e");
|
||||
process->start("logcat", arguments);
|
||||
process->waitForFinished(-1);
|
||||
#endif
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#ifndef ANDROIDADBLOG_H
|
||||
#define ANDROIDADBLOG_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QThread>
|
||||
|
||||
class androidadblog : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit androidadblog();
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
void runAdbTailCommand(QString command);
|
||||
};
|
||||
|
||||
#endif // ANDROIDADBLOG_H
|
||||
408
src/apexbike.cpp
408
src/apexbike.cpp
@@ -1,408 +0,0 @@
|
||||
#include "apexbike.h"
|
||||
#include "ios/lockscreen.h"
|
||||
#include "keepawakehelper.h"
|
||||
#include "virtualbike.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
apexbike::apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
initDone = false;
|
||||
connect(refresh, &QTimer::timeout, this, &apexbike::update);
|
||||
refresh->start(200ms);
|
||||
}
|
||||
|
||||
void apexbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
|
||||
// one for the resistance changed event (spontaneous), and one for the other ones.
|
||||
if (wait_for_response) {
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
} else {
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
}
|
||||
|
||||
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
|
||||
m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gattWriteCharacteristic.isValid()) {
|
||||
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic,
|
||||
QByteArray((const char *)data, data_len));
|
||||
|
||||
if (!disable_log) {
|
||||
qDebug() << QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ') +
|
||||
QStringLiteral(" // ") + info;
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void apexbike::forceResistance(resistance_t requestResistance) {}
|
||||
|
||||
void apexbike::sendPoll() {
|
||||
uint8_t noOpData[] = {0xaa, 0x55, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xc3, 0x3c};
|
||||
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, false);
|
||||
}
|
||||
|
||||
void apexbike::update() {
|
||||
if (m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit();
|
||||
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
|
||||
gattNotify1Characteristic.isValid() && initDone) {
|
||||
update_metrics(true, watts());
|
||||
|
||||
// sending poll every 5 seconds
|
||||
if (sec1Update++ >= (5000 / refresh->interval())) {
|
||||
sec1Update = 0;
|
||||
sendPoll();
|
||||
// updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance > max_resistance)
|
||||
requestResistance = max_resistance;
|
||||
else if (requestResistance <= 0)
|
||||
requestResistance = 1;
|
||||
|
||||
if (requestResistance != currentResistance().value()) {
|
||||
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
|
||||
forceResistance(requestResistance);
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
if (requestStart != -1) {
|
||||
qDebug() << QStringLiteral("starting...");
|
||||
|
||||
// btinit();
|
||||
|
||||
requestStart = -1;
|
||||
emit bikeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
qDebug() << QStringLiteral("stopping...");
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void apexbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
|
||||
}
|
||||
|
||||
void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
|
||||
qDebug() << " << " + newValue.toHex(' ');
|
||||
|
||||
lastPacket = newValue;
|
||||
|
||||
if (newValue.length() != 10 && newValue.at(2) != 0x31) {
|
||||
return;
|
||||
}
|
||||
|
||||
Resistance = newValue.at(5);
|
||||
emit resistanceRead(Resistance.value());
|
||||
m_pelotonResistance = Resistance.value();
|
||||
|
||||
qDebug() << QStringLiteral("Current resistance: ") + QString::number(Resistance.value());
|
||||
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = ((uint8_t)newValue.at(4));
|
||||
}
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed = 0.37497622 * ((double)Cadence.value());
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
if (watts())
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (heartRateBeltName.startsWith(QLatin1String("Disabled"))) {
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
lockscreen h;
|
||||
long appleWatchHeartRate = h.heartRate();
|
||||
h.setKcal(KCal.value());
|
||||
h.setDistance(Distance.value());
|
||||
Heart = appleWatchHeartRate;
|
||||
qDebug() << "Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
|
||||
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
|
||||
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
|
||||
// echelon just send the resistance values when it changes
|
||||
Resistance = Resistance.value();
|
||||
m_pelotonResistance = m_pelotonResistance.value();
|
||||
|
||||
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
|
||||
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
|
||||
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
|
||||
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
|
||||
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
|
||||
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
|
||||
|
||||
if (m_control->error() != QLowEnergyController::NoError) {
|
||||
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void apexbike::btinit() {
|
||||
uint8_t initData1[] = {0xeb, 0x50, 0x51, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2};
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
|
||||
initDone = true;
|
||||
|
||||
if (lastResistanceBeforeDisconnection != -1) {
|
||||
qDebug() << QStringLiteral("forcing resistance to ") + QString::number(lastResistanceBeforeDisconnection) +
|
||||
QStringLiteral(". It was the last value before the disconnection.");
|
||||
forceResistance(lastResistanceBeforeDisconnection);
|
||||
lastResistanceBeforeDisconnection = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void apexbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff2);
|
||||
QBluetoothUuid _gattNotify1CharacteristicId((quint16)0xfff1);
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
|
||||
|
||||
if (state == QLowEnergyService::ServiceDiscovered) {
|
||||
// qDebug() << gattCommunicationChannelService->characteristics();
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotify1Characteristic.isValid());
|
||||
|
||||
// establish hook into notifications
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
|
||||
&apexbike::characteristicChanged);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
|
||||
&apexbike::characteristicWritten);
|
||||
connect(gattCommunicationChannelService,
|
||||
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
|
||||
this, &apexbike::errorService);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
|
||||
&apexbike::descriptorWritten);
|
||||
|
||||
// ******************************************* virtual bike init *************************************
|
||||
if (!firstStateChanged && !virtualBike
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
&& !h
|
||||
#endif
|
||||
#endif
|
||||
) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence) {
|
||||
qDebug() << "ios_peloton_workaround activated!";
|
||||
h = new lockscreen();
|
||||
h->virtualbike_ios();
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
if (virtual_device_enabled) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&apexbike::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &apexbike::changeInclination);
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(
|
||||
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void apexbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' ');
|
||||
|
||||
initRequest = true;
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
|
||||
void apexbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
|
||||
}
|
||||
|
||||
void apexbike::serviceScanDone(void) {
|
||||
qDebug() << QStringLiteral("serviceScanDone");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &apexbike::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void apexbike::errorService(QLowEnergyService::ServiceError err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
qDebug() << QStringLiteral("apexbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString();
|
||||
}
|
||||
|
||||
void apexbike::error(QLowEnergyController::Error err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
qDebug() << QStringLiteral("apexbike::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString();
|
||||
}
|
||||
|
||||
void apexbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')';
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &apexbike::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &apexbike::serviceScanDone);
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, &apexbike::error);
|
||||
connect(m_control, &QLowEnergyController::stateChanged, this, &apexbike::controllerStateChanged);
|
||||
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
qDebug() << QStringLiteral("Cannot connect to remote device.");
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
qDebug() << QStringLiteral("Controller connected. Search services...");
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
qDebug() << QStringLiteral("LowEnergy controller disconnected");
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool apexbike::connected() {
|
||||
if (!m_control) {
|
||||
return false;
|
||||
}
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void *apexbike::VirtualBike() { return virtualBike; }
|
||||
|
||||
void *apexbike::VirtualDevice() { return VirtualBike(); }
|
||||
|
||||
uint16_t apexbike::watts() {
|
||||
return wattFromHR(true);
|
||||
}
|
||||
|
||||
void apexbike::controllerStateChanged(QLowEnergyController::ControllerState state) {
|
||||
qDebug() << QStringLiteral("controllerStateChanged") << state;
|
||||
if (state == QLowEnergyController::UnconnectedState && m_control) {
|
||||
lastResistanceBeforeDisconnection = Resistance.value();
|
||||
qDebug() << QStringLiteral("trying to connect back again...");
|
||||
initDone = false;
|
||||
m_control->connectToDevice();
|
||||
}
|
||||
}
|
||||
102
src/apexbike.h
102
src/apexbike.h
@@ -1,102 +0,0 @@
|
||||
#ifndef APEXBIKE_H
|
||||
#define APEXBIKE_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "bike.h"
|
||||
#include "virtualbike.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class apexbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
bool connected();
|
||||
|
||||
void *VirtualBike();
|
||||
void *VirtualDevice();
|
||||
|
||||
private:
|
||||
const resistance_t max_resistance = 32;
|
||||
void btinit();
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
void forceResistance(resistance_t requestResistance);
|
||||
void sendPoll();
|
||||
uint16_t watts();
|
||||
|
||||
QTimer *refresh;
|
||||
virtualbike *virtualBike = nullptr;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t counterPoll = 1;
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
resistance_t lastResistanceBeforeDisconnection = -1;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
Q_SIGNALS:
|
||||
void disconnected();
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void stateChanged(QLowEnergyService::ServiceState state);
|
||||
void controllerStateChanged(QLowEnergyController::ControllerState state);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
};
|
||||
|
||||
#endif // APEXBIKE_H
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
71
src/bike.cpp
71
src/bike.cpp
@@ -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;}
|
||||
|
||||
31
src/bike.h
31
src/bike.h
@@ -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
|
||||
|
||||
1488
src/bluetooth.cpp
1488
src/bluetooth.cpp
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
#include "computrainerbike.h"
|
||||
#include "ios/lockscreen.h"
|
||||
#include "keepawakehelper.h"
|
||||
#include "virtualbike.h"
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <QtXml>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
QSettings settings;
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
target_watts.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
initDone = false;
|
||||
connect(refresh, &QTimer::timeout, this, &computrainerbike::update);
|
||||
refresh->start(50ms);
|
||||
|
||||
QString computrainerSerialPort =
|
||||
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
|
||||
|
||||
myComputrainer = new Computrainer(this, computrainerSerialPort);
|
||||
myComputrainer->start();
|
||||
|
||||
ergModeSupported = true; // IMPORTANT, only for this bike
|
||||
|
||||
initRequest = true;
|
||||
|
||||
// ******************************************* virtual bike init *************************************
|
||||
if (!firstStateChanged && !virtualBike
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
&& !h
|
||||
#endif
|
||||
#endif
|
||||
) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence) {
|
||||
qDebug() << "ios_peloton_workaround activated!";
|
||||
h = new lockscreen();
|
||||
h->virtualbike_ios();
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
if (virtual_device_enabled) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,& computrainerbike::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &computrainerbike::changeInclination);
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
|
||||
resistance_t computrainerbike::resistanceFromPowerRequest(uint16_t power) {
|
||||
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
|
||||
|
||||
QSettings settings;
|
||||
|
||||
double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
|
||||
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
|
||||
|
||||
for (resistance_t i = 1; i < max_resistance; i++) {
|
||||
if (((wattsFromResistance(i) * watt_gain) + watt_offset) <= power &&
|
||||
((wattsFromResistance(i + 1) * watt_gain) + watt_offset) >= power) {
|
||||
qDebug() << QStringLiteral("resistanceFromPowerRequest")
|
||||
<< ((wattsFromResistance(i) * watt_gain) + watt_offset)
|
||||
<< ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) << power;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (power < ((wattsFromResistance(1) * watt_gain) + watt_offset))
|
||||
return 1;
|
||||
else
|
||||
return max_resistance;
|
||||
}
|
||||
|
||||
uint16_t computrainerbike::wattsFromResistance(resistance_t resistance) {
|
||||
|
||||
if (currentCadence().value() == 0)
|
||||
return 0;
|
||||
|
||||
switch (resistance) {
|
||||
case 0:
|
||||
case 1:
|
||||
// -13.5 + 0.999x + 0.00993x²
|
||||
return (-13.5 + (0.999 * currentCadence().value()) + (0.00993 * pow(currentCadence().value(), 2)));
|
||||
case 2:
|
||||
// -17.7 + 1.2x + 0.0116x²
|
||||
return (-17.7 + (1.2 * currentCadence().value()) + (0.0116 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 3:
|
||||
// -17.5 + 1.24x + 0.014x²
|
||||
return (-17.5 + (1.24 * currentCadence().value()) + (0.014 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 4:
|
||||
// -20.9 + 1.43x + 0.016x²
|
||||
return (-20.9 + (1.43 * currentCadence().value()) + (0.016 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 5:
|
||||
// -27.9 + 1.75x+0.0172x²
|
||||
return (-27.9 + (1.75 * currentCadence().value()) + (0.0172 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 6:
|
||||
// -26.7 + 1.9x + 0.0201x²
|
||||
return (-26.7 + (1.9 * currentCadence().value()) + (0.0201 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 7:
|
||||
// -33.5 + 2.23x + 0.0225x²
|
||||
return (-33.5 + (2.23 * currentCadence().value()) + (0.0225 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 8:
|
||||
// -36.5+2.5x+0.0262x²
|
||||
return (-36.5 + (2.5 * currentCadence().value()) + (0.0262 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 9:
|
||||
// -38+2.62x+0.0305x²
|
||||
return (-38.0 + (2.62 * currentCadence().value()) + (0.0305 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 10:
|
||||
// -41.2+2.85x+0.0327x²
|
||||
return (-41.2 + (2.85 * currentCadence().value()) + (0.0327 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 11:
|
||||
// -43.4+3.01x+0.0359x²
|
||||
return (-43.4 + (3.01 * currentCadence().value()) + (0.0359 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 12:
|
||||
// -46.8+3.23x+0.0364x²
|
||||
return (-46.8 + (3.23 * currentCadence().value()) + (0.0364 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 13:
|
||||
// -49+3.39x+0.0371x²
|
||||
return (-49.0 + (3.39 * currentCadence().value()) + (0.0371 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 14:
|
||||
// -53.4+3.55x+0.0383x²
|
||||
return (-53.4 + (3.55 * currentCadence().value()) + (0.0383 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 15:
|
||||
// -49.9+3.37x+0.0429x²
|
||||
return (-49.9 + (3.37 * currentCadence().value()) + (0.0429 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 16:
|
||||
default:
|
||||
// -47.1+3.25x+0.0464x²
|
||||
return (-47.1 + (3.25 * currentCadence().value()) + (0.0464 * pow(currentCadence().value(), 2)));
|
||||
}
|
||||
}
|
||||
|
||||
// must be double because it's an inclination
|
||||
void computrainerbike::forceResistance(double requestResistance) {
|
||||
if(myComputrainer->getMode() != CT_SSMODE)
|
||||
myComputrainer->setMode(CT_SSMODE);
|
||||
myComputrainer->setGradient(requestResistance);
|
||||
qDebug() << "forceResistance" << requestResistance;
|
||||
}
|
||||
|
||||
void computrainerbike::innerWriteResistance() {
|
||||
QSettings settings;
|
||||
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
|
||||
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance > max_resistance) {
|
||||
requestResistance = max_resistance;
|
||||
} else if (requestResistance < min_resistance) {
|
||||
requestResistance = min_resistance;
|
||||
} else if (requestResistance == 0) {
|
||||
requestResistance = 1;
|
||||
}
|
||||
|
||||
if (requestResistance != currentResistance().value()) {
|
||||
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
|
||||
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) &&
|
||||
(requestPower == 0 || requestPower == -1)) {
|
||||
forceResistance(requestResistance);
|
||||
}
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
|
||||
if (requestPower > 0) {
|
||||
if(myComputrainer->getMode() != CT_ERGOMODE)
|
||||
myComputrainer->setMode(CT_ERGOMODE);
|
||||
myComputrainer->setLoad(requestPower);
|
||||
qDebug() << "change inclination due to request power = " << requestPower;
|
||||
}
|
||||
|
||||
if (requestInclination != -100) {
|
||||
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
|
||||
forceResistance(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
|
||||
// i'm using the gears in the inclination
|
||||
requestInclination = -100;
|
||||
}
|
||||
}
|
||||
|
||||
void computrainerbike::update() {
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit();
|
||||
emit connectedAndDiscovered();
|
||||
} else {
|
||||
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
bool disable_hr_frommachinery =
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
|
||||
int Buttons, Status;
|
||||
bool calibration;
|
||||
double Power, HeartRate, cadence, speed, RRC, Load, Gradient;
|
||||
uint8_t ss[24];
|
||||
// get latest telemetry
|
||||
myComputrainer->getTelemetry(Power, HeartRate, cadence, speed, RRC, calibration, Buttons, ss, Status);
|
||||
|
||||
Speed = speed;
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
emit debug("Current Distance: " + QString::number(Distance.value()));
|
||||
Cadence = cadence;
|
||||
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
m_watt = Power;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
|
||||
Inclination = Gradient;
|
||||
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Gradient));
|
||||
|
||||
if (watts())
|
||||
KCal += ((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
/*
|
||||
Resistance = resistance;
|
||||
m_pelotonResistance = (100 / 32) * Resistance.value();
|
||||
emit resistanceRead(Resistance.value()); */
|
||||
|
||||
if (!disable_hr_frommachinery) {
|
||||
Heart = HeartRate;
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (disable_hr_frommachinery && heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
lockscreen h;
|
||||
long appleWatchHeartRate = h.heartRate();
|
||||
h.setKcal(KCal.value());
|
||||
h.setDistance(Distance.value());
|
||||
Heart = appleWatchHeartRate;
|
||||
debug("Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
|
||||
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
|
||||
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
|
||||
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
|
||||
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); */
|
||||
|
||||
update_metrics(false, watts());
|
||||
|
||||
// updating the treadmill console every second
|
||||
if (sec1Update++ == (500 / refresh->interval())) {
|
||||
sec1Update = 0;
|
||||
// updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
innerWriteResistance();
|
||||
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
|
||||
// btinit();
|
||||
|
||||
requestStart = -1;
|
||||
emit bikeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool computrainerbike::inclinationAvailableByHardware() {
|
||||
QSettings settings;
|
||||
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
|
||||
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
|
||||
|
||||
if (proform_studio || proform_tdf_10)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
resistance_t computrainerbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
if (pelotonResistance <= 10) {
|
||||
return 1;
|
||||
}
|
||||
if (pelotonResistance <= 20) {
|
||||
return 2;
|
||||
}
|
||||
if (pelotonResistance <= 25) {
|
||||
return 3;
|
||||
}
|
||||
if (pelotonResistance <= 30) {
|
||||
return 4;
|
||||
}
|
||||
if (pelotonResistance <= 35) {
|
||||
return 5;
|
||||
}
|
||||
if (pelotonResistance <= 40) {
|
||||
return 6;
|
||||
}
|
||||
if (pelotonResistance <= 45) {
|
||||
return 7;
|
||||
}
|
||||
if (pelotonResistance <= 50) {
|
||||
return 8;
|
||||
}
|
||||
if (pelotonResistance <= 55) {
|
||||
return 9;
|
||||
}
|
||||
if (pelotonResistance <= 60) {
|
||||
return 10;
|
||||
}
|
||||
if (pelotonResistance <= 65) {
|
||||
return 11;
|
||||
}
|
||||
if (pelotonResistance <= 70) {
|
||||
return 12;
|
||||
}
|
||||
if (pelotonResistance <= 75) {
|
||||
return 13;
|
||||
}
|
||||
if (pelotonResistance <= 80) {
|
||||
return 14;
|
||||
}
|
||||
if (pelotonResistance <= 85) {
|
||||
return 15;
|
||||
}
|
||||
if (pelotonResistance <= 100) {
|
||||
return 16;
|
||||
}
|
||||
return Resistance.value();
|
||||
}
|
||||
|
||||
void computrainerbike::btinit() { initDone = true; }
|
||||
|
||||
void computrainerbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
|
||||
}
|
||||
|
||||
bool computrainerbike::connected() { return true; }
|
||||
|
||||
void *computrainerbike::VirtualBike() { return virtualBike; }
|
||||
|
||||
void *computrainerbike::VirtualDevice() { return VirtualBike(); }
|
||||
|
||||
uint16_t computrainerbike::watts() { return m_watt.value(); }
|
||||
@@ -1,95 +0,0 @@
|
||||
#ifndef COMPUTRAINERBIKE_H
|
||||
#define COMPUTRAINERBIKE_H
|
||||
|
||||
#include <QAbstractOAuth2>
|
||||
#include <QObject>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
#include "Computrainer.h"
|
||||
#include "bike.h"
|
||||
#include "virtualbike.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class computrainerbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance);
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power);
|
||||
resistance_t maxResistance() { return max_resistance; }
|
||||
bool inclinationAvailableByHardware();
|
||||
bool connected();
|
||||
|
||||
void *VirtualBike();
|
||||
void *VirtualDevice();
|
||||
|
||||
private:
|
||||
resistance_t max_resistance = 100;
|
||||
resistance_t min_resistance = -20;
|
||||
uint16_t wattsFromResistance(resistance_t resistance);
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
QTime GetElapsedFromPacket(QByteArray packet);
|
||||
void btinit();
|
||||
void startDiscover();
|
||||
void sendPoll();
|
||||
uint16_t watts();
|
||||
void forceResistance(double requestResistance);
|
||||
void innerWriteResistance();
|
||||
|
||||
QTimer *refresh;
|
||||
virtualbike *virtualBike = nullptr;
|
||||
uint8_t counterPoll = 0;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QString lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
metric target_watts;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
Computrainer *myComputrainer = nullptr;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif // COMPUTRAINERBIKE_H
|
||||
@@ -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
Reference in New Issue
Block a user