Compare commits
287 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a3235c7d3 | ||
|
|
a5d823549a | ||
|
|
90970f75c8 | ||
|
|
a175cc7f45 | ||
|
|
f15437f84e | ||
|
|
5b80303a92 | ||
|
|
9350e342b2 | ||
|
|
a0c3576b2e | ||
|
|
c33242f077 | ||
|
|
65e1a7522c | ||
|
|
429cb2077a | ||
|
|
49b47cd432 | ||
|
|
34780cb2b0 | ||
|
|
f2e8d3e9e8 | ||
|
|
64e4ee5974 | ||
|
|
11173726fe | ||
|
|
3ce5f320c7 | ||
|
|
e7153fd087 | ||
|
|
a3b929fcc7 | ||
|
|
7846341fd7 | ||
|
|
4d2ead7e1b | ||
|
|
2e69a155fc | ||
|
|
821d49e8e6 | ||
|
|
c1800f65ea | ||
|
|
9781f5de7d | ||
|
|
e10d0d1127 | ||
|
|
32213b6a31 | ||
|
|
55047173a2 | ||
|
|
07fe27abf5 | ||
|
|
ae0001b8fa | ||
|
|
9a342a4724 | ||
|
|
b2dcbef3b8 | ||
|
|
667a384232 | ||
|
|
e40a170b61 | ||
|
|
0d3ecd702d | ||
|
|
3ce4443adb | ||
|
|
1837c0d120 | ||
|
|
55a5def6a5 | ||
|
|
96176bd36c | ||
|
|
0e400e967e | ||
|
|
20d6ec74d8 | ||
|
|
0aa1693dde | ||
|
|
fab495d68f | ||
|
|
75882d07b8 | ||
|
|
279adb0ea2 | ||
|
|
fe69cc2c8f | ||
|
|
3ca7e6ecb5 | ||
|
|
b9e08d3cce | ||
|
|
02cb7003cc | ||
|
|
1858fbb7d8 | ||
|
|
bf31f00ce7 | ||
|
|
60fa28417c | ||
|
|
b1ce0973e7 | ||
|
|
a208b26a39 | ||
|
|
79f8942b06 | ||
|
|
6030616209 | ||
|
|
7e846d4228 | ||
|
|
b43dd6f80d | ||
|
|
07676e35c6 | ||
|
|
53dc952ece | ||
|
|
a48a40b8f2 | ||
|
|
825d51f86b | ||
|
|
87be3f3837 | ||
|
|
e3b7089d20 | ||
|
|
e9c114d285 | ||
|
|
e5412146f7 | ||
|
|
4fa78ff8b4 | ||
|
|
31c85fdfd5 | ||
|
|
d10b0da265 | ||
|
|
468c118ded | ||
|
|
904251e05c | ||
|
|
3dded9c9d3 | ||
|
|
e48622a831 | ||
|
|
88e14ae23b | ||
|
|
05a442832d | ||
|
|
3b88809614 | ||
|
|
d5e777fe96 | ||
|
|
85578558fb | ||
|
|
97a63846e7 | ||
|
|
cf7f6ca1c2 | ||
|
|
55d632a01f | ||
|
|
7550022673 | ||
|
|
70f37dbebb | ||
|
|
37c3703283 | ||
|
|
1260e1efc4 | ||
|
|
62a76f5f13 | ||
|
|
147ca95be1 | ||
|
|
53fde43c3c | ||
|
|
09defca49b | ||
|
|
541b150763 | ||
|
|
ac77d22eef | ||
|
|
aeb2d5d1bd | ||
|
|
60b00b978e | ||
|
|
e924694fea | ||
|
|
d510e61234 | ||
|
|
7f58393b5b | ||
|
|
4e7b73ab8d | ||
|
|
7b2a81e2f5 | ||
|
|
0fa8873e19 | ||
|
|
eb0dd0c618 | ||
|
|
b7fdbbed90 | ||
|
|
13f341b6a9 | ||
|
|
90523d388c | ||
|
|
32b4ba21de | ||
|
|
fbe4571734 | ||
|
|
8a248f7451 | ||
|
|
bee124bdcf | ||
|
|
d015149365 | ||
|
|
27f207b022 | ||
|
|
54c7acf263 | ||
|
|
53e25c8592 | ||
|
|
24600b0a01 | ||
|
|
850ea9144a | ||
|
|
bde4c5f5cc | ||
|
|
66f6f6ca97 | ||
|
|
209da708c9 | ||
|
|
8243c936b5 | ||
|
|
d35b1fa1ca | ||
|
|
9a6f4617b6 | ||
|
|
fee67bb812 | ||
|
|
895d2c31a0 | ||
|
|
7d37e333f1 | ||
|
|
70cf5040ee | ||
|
|
53b2bd4516 | ||
|
|
d77fba6734 | ||
|
|
a7dcac02df | ||
|
|
bb0a9cde92 | ||
|
|
f279c3689d | ||
|
|
5b36ad9e4f | ||
|
|
d5ddc4cabd | ||
|
|
ea62337b16 | ||
|
|
15f24b8d0c | ||
|
|
dacbb475c8 | ||
|
|
6f2b19b4aa | ||
|
|
b71ca62110 | ||
|
|
24da757c5e | ||
|
|
fe1ec40f6c | ||
|
|
9cbd054ab4 | ||
|
|
62ae1f5c6d | ||
|
|
d57f265315 | ||
|
|
ce56464046 | ||
|
|
a162e0dcfb | ||
|
|
f0074697d6 | ||
|
|
e25b32bba4 | ||
|
|
ddbd41e79f | ||
|
|
cecd624d13 | ||
|
|
074f5d60f9 | ||
|
|
8795e5ec3c | ||
|
|
b23183e308 | ||
|
|
663ed67a02 | ||
|
|
586ba40527 | ||
|
|
f5d85eb1ae | ||
|
|
1af0246490 | ||
|
|
40336b70c8 | ||
|
|
0460de0fe7 | ||
|
|
61fde67eaf | ||
|
|
e4ee05d2a7 | ||
|
|
f49d1e69ff | ||
|
|
4118ed914b | ||
|
|
5564720b56 | ||
|
|
6d88cef84c | ||
|
|
671c262288 | ||
|
|
d76ec7e32e | ||
|
|
5d3b9b3645 | ||
|
|
f3e6fa9d61 | ||
|
|
4da9566713 | ||
|
|
a43b7b393c | ||
|
|
a6c369cb0b | ||
|
|
60a8f7b93f | ||
|
|
9f49d0eee9 | ||
|
|
7b4a3d3aca | ||
|
|
6aeb3c475f | ||
|
|
04c8cb8be5 | ||
|
|
34e69f55ae | ||
|
|
19dcb2e600 | ||
|
|
45d118b90e | ||
|
|
384deeda16 | ||
|
|
dc5290ced9 | ||
|
|
900f364cfe | ||
|
|
fd819219da | ||
|
|
91db440047 | ||
|
|
98e5e50017 | ||
|
|
a93408aecd | ||
|
|
20af3107b7 | ||
|
|
23309060a4 | ||
|
|
26a64bce34 | ||
|
|
117fce8f7c | ||
|
|
ebc842060c | ||
|
|
d172350a13 | ||
|
|
8426f4640b | ||
|
|
e31c8b6ff9 | ||
|
|
bb2909750f | ||
|
|
11d560d14b | ||
|
|
e774fbf575 | ||
|
|
4c2d82abf2 | ||
|
|
b5cd1d5915 | ||
|
|
c5dd48219a | ||
|
|
2320ef4124 | ||
|
|
d3d54b19dc | ||
|
|
30a6008c82 | ||
|
|
0ca04756f3 | ||
|
|
8a1ee08d50 | ||
|
|
9fae936586 | ||
|
|
63c36bbafc | ||
|
|
2a59e75e57 | ||
|
|
15fdaec3c6 | ||
|
|
29a2173e35 | ||
|
|
88dfacb0c3 | ||
|
|
06cb2d9586 | ||
|
|
b28effc4c0 | ||
|
|
0ddd953b2e | ||
|
|
d5e2e98429 | ||
|
|
1e1c977332 | ||
|
|
df16d94438 | ||
|
|
9425e751f2 | ||
|
|
eb8d354111 | ||
|
|
78c9c86227 | ||
|
|
7cc40ffe30 | ||
|
|
9e6a5ca4af | ||
|
|
37ec561409 | ||
|
|
ba8df2c2eb | ||
|
|
4b86f77a22 | ||
|
|
e43276e52b | ||
|
|
33fdd4c979 | ||
|
|
87f8887ef3 | ||
|
|
ed7e4c6bf2 | ||
|
|
12992df557 | ||
|
|
8d42d530cc | ||
|
|
7b9bd00ff4 | ||
|
|
5e7a8d938b | ||
|
|
a0c1e1b645 | ||
|
|
aa53956a35 | ||
|
|
cd1c10a090 | ||
|
|
c21e337bdd | ||
|
|
1d23ac4b81 | ||
|
|
bbeaa5ec95 | ||
|
|
9f6a4de4ac | ||
|
|
ee1c3e0118 | ||
|
|
81ac8909c8 | ||
|
|
3a45935617 | ||
|
|
c89c381177 | ||
|
|
ea57069f33 | ||
|
|
bf40c460a5 | ||
|
|
338b19f664 | ||
|
|
9f9000427f | ||
|
|
92cd9baea3 | ||
|
|
e21ad70ea9 | ||
|
|
d7ac459a3d | ||
|
|
d58db4100f | ||
|
|
3c7cb254e6 | ||
|
|
259b53e8e0 | ||
|
|
b73092bd8f | ||
|
|
787b9aa2c2 | ||
|
|
73a0bd7c65 | ||
|
|
d068526e55 | ||
|
|
8945063f30 | ||
|
|
4625bccad3 | ||
|
|
5c723375d7 | ||
|
|
1212bc83f8 | ||
|
|
efc9788c89 | ||
|
|
42c43158e6 | ||
|
|
ff354fd20d | ||
|
|
d21f92727e | ||
|
|
632991e58e | ||
|
|
1d6c46a32b | ||
|
|
3ba0219ce4 | ||
|
|
21ec2a890e | ||
|
|
b434d1f1e6 | ||
|
|
373eb3fbf9 | ||
|
|
acfdff7b5c | ||
|
|
38a41451f3 | ||
|
|
c2c5b7746f | ||
|
|
4944f6d48d | ||
|
|
33a478b1ae | ||
|
|
99fd62c8ec | ||
|
|
4712d5780a | ||
|
|
eff5d1d2f3 | ||
|
|
22b6de14f1 | ||
|
|
76d3139d79 | ||
|
|
8fb8ed6f44 | ||
|
|
483cc45643 | ||
|
|
3d152b903a | ||
|
|
1974dd26ee | ||
|
|
78f81261d9 | ||
|
|
d542aa819b | ||
|
|
5774e725a7 | ||
|
|
cddd74e0d2 |
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: cagnulein
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
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: # 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']
|
||||
103
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "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
|
||||
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
|
||||
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Runs a set of commands using the runners shell
|
||||
- name: Install Qt Linux Desktop
|
||||
uses: jurplel/install-qt-action@v2
|
||||
with:
|
||||
version: '5.12.9'
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
modules: 'qtcharts debug_info'
|
||||
dir: '${{ github.workspace }}/output/linux-desktop/'
|
||||
cached: ${{ steps.cache-qt-linux-desktop.outputs.cache-hit }}
|
||||
|
||||
- name: Compile Linux Desktop
|
||||
run: cd src; qmake; make -j4
|
||||
|
||||
- name: Archive linux-desktop binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: linux-desktop-binary
|
||||
path: src/qdomyos-zwift
|
||||
|
||||
- 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
|
||||
72
README.md
@@ -1,25 +1,85 @@
|
||||
# qdomyos-zwift
|
||||
Zwift bridge for Domyos treadmills
|
||||
Zwift bridge for Treadmills and Bike!
|
||||
|
||||
<a href="https://www.buymeacoffee.com/cagnulein" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
UI on Linux
|
||||
|
||||

|
||||
|
||||
UI on MacOS
|
||||
|
||||
### Features
|
||||
|
||||
1. Domyos compatible
|
||||
2. Toorx TRX Route Key comaptible
|
||||
3. Zwift compatible
|
||||
4. Create, load and save train programs
|
||||
5. Measure distance, elevation gain and watts
|
||||
6. Gpx import (with difficulty slider)
|
||||
7. Realtime Charts
|
||||
|
||||

|
||||
|
||||
### Installation
|
||||
### Installation from source
|
||||
|
||||
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
|
||||
|
||||
$ sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default
|
||||
|
||||
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
|
||||
|
||||
$ sudo apt upgrade && sudo apt update # this is very important on raspberry pi: you need the bluetooth firmware updated!
|
||||
$ cd src
|
||||
|
||||
$ sudo apt install libqt5bluetooth5
|
||||
$ qmake
|
||||
|
||||
$ sudo hciconfig hci0 leadv 0
|
||||
$ make -j4
|
||||
|
||||
$ sudo ./qdomyos-zwift
|
||||
|
||||
### MacOs installation
|
||||
|
||||
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 relase for MacOs
|
||||
|
||||
### Tested on
|
||||
|
||||
Raspberry PI 0W and Domyos Intense Run
|
||||
- Raspberry PI 0W and Domyos Intense Run
|
||||
|
||||
- MacBook Air 2011 and Domyos Intense Run
|
||||
|
||||
- Raspberry 3b+ and Domyos T900C
|
||||
|
||||
- Raspberry 3b+ and Toorx TRX Route Key
|
||||
|
||||
|
||||
### Your machine is not compatible?
|
||||
|
||||
Open an issue and follow these steps!
|
||||
|
||||
1. first of all you need an android device (phone or tablet)
|
||||
2. you need to become developer on your phone https://wccftech.com/how-to/how-to-enable-developer-options-on-android-10-tutorial/
|
||||
3. Go to Settings
|
||||
4. Go into developer options
|
||||
5. Enable the option Enable Bluetooth HCI snoop log
|
||||
6. restart your phone
|
||||
7. open your machine app and play with it collecting inclination and speed
|
||||
8. Disable the option Enable Bluetooth HCI snoop log
|
||||
9. on your phone you should have a file called btsnoop_hci.log
|
||||
10. attach the log file in a new issue with a short description of the steps you did in the app when you used it
|
||||
|
||||
### No gui version
|
||||
|
||||
run as
|
||||
|
||||
$ sudo ./qdomyos-zwift -no-gui
|
||||
|
||||
### Reference
|
||||
|
||||
|
||||
10
docker/linux/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM debian:stable
|
||||
MAINTAINER cagnulein
|
||||
|
||||
ENV MAKEFLAGS -j8
|
||||
WORKDIR /usr/local/src
|
||||
|
||||
# utils
|
||||
RUN apt -y update
|
||||
RUN apt -y upgrade
|
||||
RUN apt -y install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default
|
||||
BIN
docs/CPP_v1.1.pdf
Normal file
BIN
docs/CPS_v1.1.pdf
Normal file
BIN
docs/CSCP_SPEC_V10.pdf
Normal file
BIN
docs/CSCS_SPEC_V10.pdf
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- <?xml-stylesheet type="text/xsl" href="FieldBasedDisplay.xslt"?> --><!--Copyright 2016 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="Cycling Power Control Point"
|
||||
type="org.bluetooth.characteristic.cycling_power_control_point" uuid="2A66" last-modified="2016-05-03"
|
||||
approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The Cycling Power Control Point characteristic is used to request a specific function to be executed
|
||||
on the receiving device.
|
||||
</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Op Codes">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<Enumerations>
|
||||
<Enumeration key="1" value="Set Cumulative Value"
|
||||
description="Initiate the procedure to set a cumulative value. The new value is sent as parameter following op code (parameter defined per service). The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="2" value="Update Sensor Location"
|
||||
description="Update to the location of the Sensor with the value sent as parameter to this op code. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="3" value="Request Supported Sensor Locations"
|
||||
description="Request a list of supported locations where the Sensor can be attached. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including a list of supported Sensor locations in the Response Parameter."/>
|
||||
<Enumeration key="4" value="Set Crank Length"
|
||||
description="Initiate the procedure to set the crank length value to Sensor. The new value is sent as a parameter with preceding Op Code 0x04 operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="5" value="Request Crank Length"
|
||||
description="Request the current crank length value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the Crank Length in the Response Parameter."/>
|
||||
<Enumeration key="6" value="Set Chain Length"
|
||||
description="Initiate the procedure to set the chain length value to Sensor. The new value is sent as a parameter with preceding Op Code 0x06 operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="7" value="Request Chain Length"
|
||||
description="Request the current chain length value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the chain length in the Response Parameter."/>
|
||||
<Enumeration key="8" value="Set Chain Weight"
|
||||
description="Initiate the procedure to set the chain weight value to Sensor. The new value is sent as a parameter with preceding Op Code 0x08 operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="9" value="Request Chain Weight"
|
||||
description="Request the current chain weight value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the chain weight in the Response Parameter."/>
|
||||
<Enumeration key="10" value="Set Span Length"
|
||||
description="Initiate the procedure to set the span length value to Sensor. The new value is sent as a parameter with preceding Op Code 0x0A operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="11" value="Request Span Length"
|
||||
description="Request the current span length value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the span length in the Response Parameter."/>
|
||||
<Enumeration key="12" value="Start Offset Compensation"
|
||||
description="Starts the offset compensation process of the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the raw force or a raw torque in the Response Parameter (defined per Service)."/>
|
||||
<Enumeration key="13" value="Mask Cycling Power Measurement Characteristic Content"
|
||||
description="Initiate the procedure to set the content of Cycling Power Measurement Characteristic. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
|
||||
<Enumeration key="14" value="Request Sampling Rate"
|
||||
description="Request the sampling rate value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the sampling rate in the Response Parameter."/>
|
||||
<Enumeration key="15" value="Request Factory Calibration Date"
|
||||
description="Request the Factory calibration date set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the Factory calibration date in the Response Parameter."/>
|
||||
<Enumeration key="16" value="Start Enhanced Offset Compensation"
|
||||
description="Starts the offset compensation process of the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the raw force or a raw torque in the Response Parameter and an option for a manufacturer specific value (defined per Service)."/>
|
||||
<Enumeration key="32" value="Response Code"
|
||||
description="The Response Code is followed by the Request Op Code, the Response Value and optionally, the Response Parameter."/>
|
||||
<ReservedForFutureUse start="0" end="0"/>
|
||||
<ReservedForFutureUse start="17" end="31"/>
|
||||
<ReservedForFutureUse start="33" end="255"/>
|
||||
</Enumerations>
|
||||
</Field>
|
||||
<Field name="Parameter Value"><!--<InformativeText>Parameter Value for "Set Cumulative Value" Op Code</InformativeText>-->
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>variable</Format>
|
||||
<Description>Defined per Service specification.</Description>
|
||||
</Field>
|
||||
<Field name="Request Op Code">
|
||||
<InformativeText>The Request Op Code is a sub field of the Parameter Value for "Response Code" Op Code.
|
||||
<br>
|
||||
C1: This Field is Mandatory for "Response Code" Op Code, otherwise this field is Excluded.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<Description>Refer to the Op Code table above for additional information on the possible values for this
|
||||
field.
|
||||
</Description>
|
||||
</Field>
|
||||
<Field name="Response Value">
|
||||
<InformativeText>The Response Value is a sub field of the Parameter Value for "Response Code" Op Code
|
||||
<br>
|
||||
C1: This Field is Mandatory for "Response Code" Op Code, otherwise this field is Excluded.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<Enumerations>
|
||||
<Enumeration key="1" value="Success" description="Response for successful operation. "/>
|
||||
<Enumeration key="2" value="Op Code not Supported"
|
||||
description="Response if unsupported Op Code is received."/>
|
||||
<Enumeration key="3" value="Invalid Parameter"
|
||||
description="Response if Parameter received does not meet the requirements of the service or is outside of the supported range of the Sensor."/>
|
||||
<Enumeration key="4" value="Operation Failed"
|
||||
description="Response if the requested procedure failed."/>
|
||||
<ReservedForFutureUse start="0" end="0"/>
|
||||
<ReservedForFutureUse start="5" end="255"/>
|
||||
</Enumerations>
|
||||
</Field>
|
||||
<Field name="Response Parameter">
|
||||
<InformativeText>The Response Parameter is a sub field of the Parameter Value for "Response Code" Op Code.
|
||||
<br>
|
||||
C2:This Field is Optional for "Response Code" Op Code, otherwise this field is Excluded.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>variable</Format>
|
||||
<Description>Note: The Response Parameter Value of the response to the Control Point is a variable length
|
||||
field to allow a list of different values defined by the Service Specification
|
||||
</Description>
|
||||
</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>
|
||||
151
docs/org.bluetooth.characteristic.cycling_power_feature.xml
Normal file
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- <?xml-stylesheet type="text/xsl" href="FieldBasedDisplay.xslt"?> --><!--Copyright 2016 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="Cycling Power Feature"
|
||||
type="org.bluetooth.characteristic.cycling_power_feature" uuid="2A65" last-modified="2016-05-03"
|
||||
approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The CP Feature characteristic is used to report a list of features supported by the device.</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Cycling Power Feature">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>32bit</Format>
|
||||
<BitField>
|
||||
<Bit index="0" size="1" name="Pedal Power Balance Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="1" size="1" name="Accumulated Torque Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="2" size="1" name="Wheel Revolution Data Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="3" size="1" name="Crank Revolution Data Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="4" size="1" name="Extreme Magnitudes Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="5" size="1" name="Extreme Angles Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="6" size="1" name="Top and Bottom Dead Spot Angles Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="7" size="1" name="Accumulated Energy Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="8" size="1" name="Offset Compensation Indicator Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="9" size="1" name="Offset Compensation Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="10" size="1" name="Cycling Power Measurement Characteristic Content Masking Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="11" size="1" name="Multiple Sensor Locations Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="12" size="1" name="Crank Length Adjustment Supported ">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="13" size="1" name="Chain Length Adjustment Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="14" size="1" name="Chain Weight Adjustment Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="15" size="1" name="Span Length Adjustment Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="16" size="1" name="Sensor Measurement Context">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="Force based"/>
|
||||
<Enumeration key="1" value="Torque based"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="17" size="1" name="Instantaneous Measurement Direction Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="18" size="1" name="Factory Calibration Date Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="19" size="1" name="Enhanced Offset Compensation Supported">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="20" size="2" name="Distribute System Support">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="Unspecified (legacy sensor)"/>
|
||||
<Enumeration key="1" value="Not for use in a distributed system"/>
|
||||
<Enumeration key="2" value="Can be used in a distributed system"/>
|
||||
<Enumeration key="3" value="RFU"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<ReservedForFutureUse index="22" size="10"/>
|
||||
</BitField>
|
||||
</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>
|
||||
304
docs/org.bluetooth.characteristic.cycling_power_measurement.xml
Normal file
@@ -0,0 +1,304 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--Copyright 2011 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="Cycling Power Measurement"
|
||||
type="org.bluetooth.characteristic.cycling_power_measurement" uuid="2A63" last-modified="2014-07-02"
|
||||
approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The Cycling Power Measurement characteristic is a variable length structure containing a Flags field,
|
||||
an Instantaneous Power field and, based on the contents of the Flags field, may contain one or more
|
||||
additional fields as shown in the table below.
|
||||
</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Flags">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>16bit</Format>
|
||||
<BitField>
|
||||
<Bit index="0" size="1" name="Pedal Power Balance Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="1" size="1" name="Pedal Power Balance Reference">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="Unknown"/>
|
||||
<Enumeration key="1" value="Left"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="2" size="1" name="Accumulated Torque Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="3" size="1" name="Accumulated Torque Source">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="Wheel Based"/>
|
||||
<Enumeration key="1" value="Crank Based"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="4" size="1" name="Wheel Revolution Data Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="5" size="1" name="Crank Revolution Data Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="6" size="1" name="Extreme Force Magnitudes Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="7" size="1" name="Extreme Torque Magnitudes Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="8" size="1" name="Extreme Angles Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="9" size="1" name="Top Dead Spot Angle Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="10" size="1" name="Bottom Dead Spot Angle Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="11" size="1" name="Accumulated Energy Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="12" size="1" name="Offset Compensation Indicator ">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<ReservedForFutureUse index="13" size="3"/>
|
||||
</BitField>
|
||||
<b>C1:These Fields are dependent upon the Flags field</b>
|
||||
<p></p>
|
||||
</Field>
|
||||
<Field name="Instantaneous Power">
|
||||
<InformativeText>
|
||||
Unit is in watts with a resolution of 1.
|
||||
</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.power.watt</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Pedal Power Balance">
|
||||
<InformativeText>
|
||||
Unit is in percentage with a resolution of 1/2.
|
||||
</InformativeText>
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>uint8</Format>
|
||||
<Unit>org.bluetooth.unit.percentage</Unit>
|
||||
<BinaryExponent>-1</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Accumulated Torque">
|
||||
<InformativeText>
|
||||
Unit is in newton metres with a resolution of 1/32.
|
||||
</InformativeText>
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
|
||||
<BinaryExponent>-5</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Wheel Revolution Data - Cumulative Wheel Revolutions">
|
||||
<InformativeText>
|
||||
Unitless
|
||||
<br>C1:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint32</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Wheel Revolution Data - Last Wheel Event Time">
|
||||
<InformativeText>
|
||||
Unit is in seconds with a resolution of 1/2048.
|
||||
<br>C1:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
<BinaryExponent>-11</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Crank Revolution Data- Cumulative Crank Revolutions">
|
||||
<InformativeText>
|
||||
Unitless
|
||||
<br>C2:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Crank Revolution Data- Last Crank Event Time">
|
||||
<InformativeText>
|
||||
Unit is in seconds with a resolution of 1/1024.
|
||||
<br>C2:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
<BinaryExponent>-10</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Extreme Force Magnitudes - Maximum Force Magnitude">
|
||||
<InformativeText>
|
||||
Unit is in newtons with a resolution of 1.
|
||||
<br>C3:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C3</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.force.newton</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Extreme Force Magnitudes - Minimum Force Magnitude">
|
||||
<InformativeText>
|
||||
Unit is in newtons with a resolution of 1.
|
||||
<br>C3:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C3</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.force.newton</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Extreme Torque Magnitudes- Maximum Torque Magnitude">
|
||||
<InformativeText>
|
||||
Unit is in newton metres with a resolution of 1/32.
|
||||
<br>C4:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C4</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
|
||||
<BinaryExponent>-5</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Extreme Torque Magnitudes- Minimum Torque Magnitude">
|
||||
<InformativeText>
|
||||
Unit is in newton metres with a resolution of 1/32.
|
||||
<br>C4:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C4</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
|
||||
<BinaryExponent>-5</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Extreme Angles - Maximum Angle">
|
||||
<InformativeText>
|
||||
Unit is in degrees with a resolution of 1
|
||||
<br>C5: When present, this field and the "Extreme Angles - Minimum Angle" field are always present as a
|
||||
pair and are concatenated into a UINT24 value (3 octets). As an example, if the Maximum Angle is
|
||||
0xABC and the Minimum Angle is 0x123, the transmitted value is 0x123ABC.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C5</Requirement>
|
||||
<Format>uint12</Format>
|
||||
<Unit>org.bluetooth.unit.plane_angle.degree
|
||||
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
|
||||
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
|
||||
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
|
||||
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
|
||||
position. The left crank sensor (if fitted) detects the 0? when the crank it
|
||||
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
|
||||
crank it is attached to is in its 12 o'clock position; thus, there is a constant
|
||||
180? difference between the right crank and the left crank position signals.
|
||||
</Description>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Extreme Angles - Minimum Angle">
|
||||
<InformativeText>
|
||||
Unit is in degrees with a resolution of 1.
|
||||
<br>C5: When present, this field and the "Extreme Angles - Maximum Angle" field are always present as a
|
||||
pair and are concatenated into a UINT24 value (3 octets). As an example, if the Maximum Angle is
|
||||
0xABC and the Minimum Angle is 0x123, the transmitted value is 0x123ABC.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C5</Requirement>
|
||||
<Format>uint12</Format>
|
||||
<Unit>org.bluetooth.unit.plane_angle.degree
|
||||
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
|
||||
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
|
||||
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
|
||||
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
|
||||
position. The left crank sensor (if fitted) detects the 0? when the crank it
|
||||
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
|
||||
crank it is attached to is in its 12 o'clock position; thus, there is a constant
|
||||
180? difference between the right crank and the left crank position signals.
|
||||
</Description>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Top Dead Spot Angle">
|
||||
<InformativeText>
|
||||
Unit is in degrees with a resolution of 1.
|
||||
</InformativeText>
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.plane_angle.degree
|
||||
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
|
||||
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
|
||||
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
|
||||
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
|
||||
position. The left crank sensor (if fitted) detects the 0? when the crank it
|
||||
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
|
||||
crank it is attached to is in its 12 o'clock position; thus, there is a constant
|
||||
180? difference between the right crank and the left crank position signals.
|
||||
</Description>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Bottom Dead Spot Angle">
|
||||
<InformativeText>
|
||||
Unit is in degrees with a resolution of 1.
|
||||
</InformativeText>
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.plane_angle.degree
|
||||
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
|
||||
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
|
||||
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
|
||||
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
|
||||
position. The left crank sensor (if fitted) detects the 0? when the crank it
|
||||
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
|
||||
crank it is attached to is in its 12 o'clock position; thus, there is a constant
|
||||
180? difference between the right crank and the left crank position signals.
|
||||
</Description>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Accumulated Energy">
|
||||
<InformativeText>
|
||||
Unit is in kilojoules with a resolution of 1.
|
||||
</InformativeText>
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.energy.joule</Unit>
|
||||
<DecimalExponent>3</DecimalExponent>
|
||||
</Field>
|
||||
</Value>
|
||||
<Note>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</Note>
|
||||
</Characteristic>
|
||||
128
docs/org.bluetooth.characteristic.cycling_power_vector.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--Copyright 2011 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="Cycling Power Vector"
|
||||
type="org.bluetooth.characteristic.cycling_power_vector" uuid="2A64" last-modified="2014-07-02"
|
||||
approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The Cycling Power Vector characteristic is a variable length structure containing a Flags fieldand
|
||||
based on the contents of the Flags field, may contain one or more additional fields as shown in the table
|
||||
below.
|
||||
</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Flags">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>8bit</Format>
|
||||
<BitField>
|
||||
<Bit index="0" size="1" name="Crank Revolution Data Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="1" size="1" name="First Crank Measurement Angle Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="2" size="1" name="Instantaneous Force Magnitude Array Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="3" size="1" name="Instantaneous Torque Magnitude Array Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False"/>
|
||||
<Enumeration key="1" value="True"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="4" size="2" name="Instantaneous Measurement Direction">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="Unknown"/>
|
||||
<Enumeration key="1" value="Tangential Component"/>
|
||||
<Enumeration key="2" value="Radial Component"/>
|
||||
<Enumeration key="3" value="Lateral Component"/>
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<ReservedForFutureUse index="6" size="2"/>
|
||||
</BitField>
|
||||
<br>C1:These Fields are dependent upon the Flags field</br>
|
||||
<p></p>
|
||||
</Field>
|
||||
<Field name="Crank Revolution Data - Cumulative Crank Revolutions">
|
||||
<InformativeText>
|
||||
Unitless
|
||||
<br>C1:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Crank Revolution Data - Last Crank Event Time">
|
||||
<InformativeText>
|
||||
Unit is in seconds with a resolution of 1/1024.
|
||||
<br>C1:When present, these fields are always present as a pair.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
<BinaryExponent>-10</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="First Crank Measurement Angle ">
|
||||
<InformativeText>
|
||||
Unit is in degrees with a resolution of 1.
|
||||
</InformativeText>
|
||||
<Requirement>Optional</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.plane_angle.degree
|
||||
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
|
||||
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
|
||||
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
|
||||
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
|
||||
position. The left crank sensor (if fitted) detects the 0? when the crank it
|
||||
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
|
||||
crank it is attached to is in its 12 o'clock position; thus, there is a constant
|
||||
180? difference between the right crank and the left crank position signals.
|
||||
</Description>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Instantaneous Force Magnitude Array">
|
||||
<InformativeText>
|
||||
The unit is in newtons with a resolution of 1
|
||||
<br>Array Order - Older is towards the LSO and Newer is towards the MSO</br>
|
||||
<br>C2: These fields are mutually exclusive. When this field is present, the presence of the
|
||||
Instantaneous Torque Magnitude Array is excluded.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.force.newton</Unit>
|
||||
<DecimalExponent>0</DecimalExponent>
|
||||
<Repeated>1</Repeated>
|
||||
</Field>
|
||||
<Field name="Instantaneous Torque Magnitude Array">
|
||||
<InformativeText>
|
||||
Unit is in newton/meter with a resolution of 1/32
|
||||
<br>Array Order - Older is towards the LSO and Newer is towards the MSO</br>
|
||||
<br>C2: These fields are mutually exclusive. When this field is present, the presence of the
|
||||
Instantaneous Force Magnitude Array is excluded.
|
||||
</br>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
|
||||
<BinaryExponent>-5</BinaryExponent>
|
||||
<Repeated>1</Repeated>
|
||||
</Field>
|
||||
</Value>
|
||||
<Note>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</Note>
|
||||
</Characteristic>
|
||||
219
docs/org.bluetooth.characteristic.indoor_bike_data.xml
Normal file
@@ -0,0 +1,219 @@
|
||||
<?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="Indoor Bike Data"
|
||||
type="org.bluetooth.characteristic.indoor_bike_data" uuid="2AD2"
|
||||
last-modified="2017-02-14" approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The Indoor Bike Data characteristic is used to send
|
||||
training-related data to the Client from an indoor bike
|
||||
(Server).</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Flags">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>16bit</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="Instantaneous Cadence present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C2" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="2" size="1" name="Average Speed present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C3" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="3" size="1" name="Average Candence present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C4" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="4" size="1" name="Total Distance Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C5" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="5" size="1" name="Resistance Level present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C6" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="6" size="1" name="Instantaneous Power present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C7" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="7" size="1" name="Average Power present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C8" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="8" size="1" name="Expended Energy present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C9" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="9" size="1" name="Heart Rate present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C10" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="10" size="1"
|
||||
name="Metabolic Equivalent present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C11" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="11" size="1" name="Elapsed Time present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C12" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="12" size="1" name="Remaining Time present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C13" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<ReservedForFutureUse index="13" size="3" />
|
||||
</BitField>
|
||||
</Field>
|
||||
<Field name="Instantaneous Speed">
|
||||
<InformativeText>Kilometer per hour with a resolution of
|
||||
0.01</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<DecimalExponent>-2</DecimalExponent>
|
||||
<Unit>org.bluetooth.unit.velocity.kilometre_per_hour</Unit>
|
||||
</Field>
|
||||
<Field name="Average Speed">
|
||||
<InformativeText>Kilometer per hour with a resolution of
|
||||
0.01</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<DecimalExponent>-2</DecimalExponent>
|
||||
<Unit>org.bluetooth.unit.velocity.kilometre_per_hour</Unit>
|
||||
</Field>
|
||||
<Field name="Instantaneous Cadence">
|
||||
<InformativeText>1/minute with a resolution of
|
||||
0.5</InformativeText>
|
||||
<Requirement>C3</Requirement>
|
||||
<BinaryExponent>-1</BinaryExponent>
|
||||
<Format>uint16</Format>
|
||||
<Unit>
|
||||
org.bluetooth.unit.angular_velocity.revolution_per_minute</Unit>
|
||||
</Field>
|
||||
<Field name="Average Cadence">
|
||||
<InformativeText>1/minute with a resolution of
|
||||
0.5</InformativeText>
|
||||
<Requirement>C4</Requirement>
|
||||
<BinaryExponent>-1</BinaryExponent>
|
||||
<Format>uint16</Format>
|
||||
<Unit>
|
||||
org.bluetooth.unit.angular_velocity.revolution_per_minute</Unit>
|
||||
</Field>
|
||||
<Field name="Total Distance">
|
||||
<InformativeText>Meters with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C5</Requirement>
|
||||
<Format>uint24</Format>
|
||||
<Unit>org.bluetooth.unit.length.metre</Unit>
|
||||
</Field>
|
||||
<Field name="Resistance Level">
|
||||
<InformativeText>Unitless with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C6</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
</Field>
|
||||
<Field name="Instantaneous Power">
|
||||
<InformativeText>Watts with a resolution of
|
||||
1</InformativeText>
|
||||
<Requirement>C7</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>C8</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>C9</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>C9</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>C9</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>C10</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>C11</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>C12</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>C13</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>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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="Supported Resistance Level Range"
|
||||
type="org.bluetooth.characteristic.supported_resistance_level_range"
|
||||
uuid="2AD6" last-modified="2017-02-14" approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>The Supported Resistance Level Range characteristic is
|
||||
used to send the supported resistance level range as well as
|
||||
the minimum resistance increment supported by the
|
||||
Server.</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Minimum Resistance Level">
|
||||
<InformativeText>Unitless with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Maximum Resistance Level">
|
||||
<InformativeText>Unitless with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
</Field>
|
||||
<Field name="Minimum Increment">
|
||||
<InformativeText>Unitless with a resolution of
|
||||
0.1</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
<DecimalExponent>-1</DecimalExponent>
|
||||
</Field>
|
||||
</Value>
|
||||
</Characteristic>
|
||||
BIN
docs/realtime-chart.png
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
docs/treadmill-bridge-schema.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/ui-mac.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
docs/ui.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
114
src/Home.qml
Normal file
@@ -0,0 +1,114 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
HomeForm{
|
||||
objectName: "home"
|
||||
signal start_clicked;
|
||||
signal stop_clicked;
|
||||
signal plus_clicked(string name)
|
||||
signal minus_clicked(string name)
|
||||
|
||||
start.onClicked: { start_clicked(); }
|
||||
stop.onClicked: { stop_clicked(); }
|
||||
|
||||
Component.onCompleted: { console.log("completed"); }
|
||||
|
||||
GridView {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175
|
||||
cellHeight: 125
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { (parent.width % 175) / 2; }
|
||||
anchors.topMargin: 150
|
||||
id: gridView
|
||||
objectName: "gridview"
|
||||
|
||||
// highlight: Rectangle {
|
||||
// width: 150
|
||||
// height: 150
|
||||
// color: "lightsteelblue"
|
||||
// }
|
||||
delegate: Item {
|
||||
id: id1
|
||||
width: 175
|
||||
height: 125
|
||||
|
||||
Component.onCompleted: console.log("completed " + objectName)
|
||||
|
||||
Rectangle {
|
||||
width: 173
|
||||
height: 123
|
||||
radius: 3
|
||||
border.width: 1
|
||||
color: Material.backgroundColor
|
||||
}
|
||||
|
||||
Image {
|
||||
id: myIcon
|
||||
x: 5
|
||||
anchors {
|
||||
top: myValue.bottom
|
||||
}
|
||||
width: 48
|
||||
height: 48
|
||||
source: icon
|
||||
}
|
||||
Text {
|
||||
objectName: "value"
|
||||
id: myValue
|
||||
color: Material.textSelectionColor
|
||||
y: 0
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: 48
|
||||
font.bold: true
|
||||
}
|
||||
Text {
|
||||
id: myText
|
||||
anchors {
|
||||
top: myIcon.top
|
||||
}
|
||||
font.bold: true
|
||||
color: "white"
|
||||
text: name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55
|
||||
anchors.topMargin: 20
|
||||
}
|
||||
RoundButton {
|
||||
objectName: minusName
|
||||
text: "-"
|
||||
onClicked: minus_clicked(objectName)
|
||||
visible: writable
|
||||
anchors.top: myValue.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
RoundButton {
|
||||
objectName: plusName
|
||||
text: "+"
|
||||
onClicked: plus_clicked(objectName)
|
||||
visible: writable
|
||||
anchors.top: myValue.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
/*MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: parent.GridView.view.currentIndex = index
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/HomeForm.ui.qml
Normal file
@@ -0,0 +1,120 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtGraphicalEffects 1.12
|
||||
|
||||
Page {
|
||||
|
||||
title: qsTr("qDomyos-Zwift")
|
||||
id: page
|
||||
|
||||
property alias start: start
|
||||
property alias stop: stop
|
||||
property alias row: row
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 120
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 100
|
||||
spacing: 5
|
||||
padding: 5
|
||||
|
||||
Rectangle {
|
||||
width: 50
|
||||
height: 100
|
||||
color: Material.backgroundColor
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: treadmill_connection
|
||||
width: 48
|
||||
height: 48
|
||||
source: "icons/icons/bluetooth-icon.png"
|
||||
enabled: rootItem.device
|
||||
smooth: true
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: treadmill_connection
|
||||
source: treadmill_connection
|
||||
color: treadmill_connection.enabled ? "#00000000" : "#B0D3d3d3"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 120
|
||||
height: 100
|
||||
color: Material.backgroundColor
|
||||
RoundButton {
|
||||
icon.source: "icons/icons/start.png"
|
||||
icon.height: 46
|
||||
icon.width: 46
|
||||
text: "Start"
|
||||
enabled: true
|
||||
id: start
|
||||
width: 120
|
||||
height: 96
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 120
|
||||
height: 100
|
||||
color: Material.backgroundColor
|
||||
RoundButton {
|
||||
icon.source: "icons/icons/stop.png"
|
||||
icon.height: 46
|
||||
icon.width: 46
|
||||
text: "Stop"
|
||||
enabled: true
|
||||
id: stop
|
||||
width: 120
|
||||
height: 96
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: item2
|
||||
width: 50
|
||||
height: 100
|
||||
color: Material.backgroundColor
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: zwift_connection
|
||||
width: 48
|
||||
height: 48
|
||||
source: "icons/icons/zwift-on.png"
|
||||
enabled: rootItem.zwift
|
||||
smooth: true
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: zwift_connection
|
||||
source: zwift_connection
|
||||
color: zwift_connection.enabled ? "#00000000" : "#B0D3d3d3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row1
|
||||
width: parent.width
|
||||
anchors.bottom: row.bottom
|
||||
anchors.bottomMargin: -10
|
||||
|
||||
Label {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: rootItem.info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;autoSize:true;formeditorZoom:0.6600000262260437;height:480;width:640}
|
||||
}
|
||||
##^##*/
|
||||
|
||||
14
src/Page1Form.ui.qml
Normal file
@@ -0,0 +1,14 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
|
||||
Page {
|
||||
width: 600
|
||||
height: 400
|
||||
|
||||
title: qsTr("Page 1")
|
||||
|
||||
Label {
|
||||
text: qsTr("You are on Page 1.")
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
14
src/Page2Form.ui.qml
Normal file
@@ -0,0 +1,14 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
|
||||
Page {
|
||||
width: 600
|
||||
height: 400
|
||||
|
||||
title: qsTr("Page 2")
|
||||
|
||||
Label {
|
||||
text: qsTr("You are on Page 2.")
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
79
src/android/AndroidManifest.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" 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.
|
||||
Remove the comment if you do not require these default features. -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
<application android:hardwareAccelerated="true" android:debuggable="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon">
|
||||
<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"/>
|
||||
</intent-filter>
|
||||
<!-- Application arguments -->
|
||||
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
|
||||
<!-- Application arguments -->
|
||||
<meta-data android:name="android.app.lib_name" android:value="qdomyos-zwift"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
<!-- Run with local libs -->
|
||||
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Used to specify custom system library path to run with local system libs -->
|
||||
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
|
||||
<!-- Messages maps -->
|
||||
<!-- Splash screen -->
|
||||
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
|
||||
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
|
||||
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
|
||||
are done populating your window with content. -->
|
||||
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
|
||||
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
|
||||
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
|
||||
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
|
||||
<!-- Splash screen -->
|
||||
<!-- Background running -->
|
||||
<!-- Warning: changing this value to true may cause unexpected crashes if the
|
||||
application still try to draw after
|
||||
"applicationStateChanged(Qt::ApplicationSuspended)"
|
||||
signal is sent! -->
|
||||
<meta-data android:name="android.app.background_running" android:value="false"/>
|
||||
<!-- Background running -->
|
||||
<!-- auto screen scale factor -->
|
||||
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="full"/>
|
||||
<!-- auto screen scale factor -->
|
||||
<!-- extract android style -->
|
||||
<!-- available android:values :
|
||||
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
|
||||
* full - useful QWidget & Quick Controls 1 apps
|
||||
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
|
||||
* 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 -->
|
||||
</activity>
|
||||
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
|
||||
</manifest>
|
||||
68
src/android/build.gradle
Normal file
@@ -0,0 +1,68 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.0'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
}
|
||||
|
||||
android {
|
||||
/*******************************************************
|
||||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qt5AndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
*
|
||||
* are defined in gradle.properties file. This file is
|
||||
* updated by QtCreator and androiddeployqt tools.
|
||||
* Changing them manually might break the compilation!
|
||||
*******************************************************/
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
buildToolsVersion '28.0.3'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qt5AndroidDir + '/res', 'res']
|
||||
resources.srcDirs = ['resources']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
// Do not compress Qt binary resources file
|
||||
aaptOptions {
|
||||
noCompress 'rcc'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
resConfig "en"
|
||||
minSdkVersion = qtMinSdkVersion
|
||||
targetSdkVersion = qtTargetSdkVersion
|
||||
}
|
||||
}
|
||||
BIN
src/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
src/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
172
src/android/gradlew
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
src/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
22
src/android/res/values/libs.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<array name="qt_sources">
|
||||
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
|
||||
</array>
|
||||
|
||||
<!-- The following is handled automatically by the deployment tool. It should
|
||||
not be edited manually. -->
|
||||
|
||||
<array name="bundled_libs">
|
||||
<!-- %%INSERT_EXTRA_LIBS%% -->
|
||||
</array>
|
||||
|
||||
<array name="qt_libs">
|
||||
<!-- %%INSERT_QT_LIBS%% -->
|
||||
</array>
|
||||
|
||||
<array name="load_local_libs">
|
||||
<!-- %%INSERT_LOCAL_LIBS%% -->
|
||||
</array>
|
||||
|
||||
</resources>
|
||||
8
src/android/src/MyActivity.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
public class MyActivity extends org.qtproject.qt5.android.bindings.QtActivity {
|
||||
@Override
|
||||
public void onCreate(android.os.Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
this.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
122
src/bike.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include <QDebug>
|
||||
#include "bike.h"
|
||||
|
||||
bike::bike()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void bike::changeResistance(int8_t resistance) { requestResistance = resistance;}
|
||||
double bike::currentCrankRevolutions() { return CrankRevs;}
|
||||
uint16_t bike::lastCrankEventTime() { return LastCrankEventTime;}
|
||||
int8_t bike::currentResistance() { return Resistance;}
|
||||
uint8_t bike::currentCadence() { return Cadence;}
|
||||
uint8_t bike::fanSpeed() { return FanSpeed; }
|
||||
bool bike::connected() { return false; }
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
|
||||
|
||||
uint16_t bike::watts()
|
||||
{
|
||||
const uint8_t max_resistance = 15;
|
||||
// ref https://translate.google.com/translate?hl=it&sl=en&u=https://support.wattbike.com/hc/en-us/articles/115001881825-Power-Resistance-and-Cadence-Tables&prev=search&pto=aue
|
||||
|
||||
const uint16_t watt_cad40_min = 25;
|
||||
const uint16_t watt_cad40_max = 55;
|
||||
|
||||
const uint16_t watt_cad45_min = 35;
|
||||
const uint16_t watt_cad45_max = 65;
|
||||
|
||||
const uint16_t watt_cad50_min = 40;
|
||||
const uint16_t watt_cad50_max = 80;
|
||||
|
||||
const uint16_t watt_cad55_min = 50;
|
||||
const uint16_t watt_cad55_max = 105;
|
||||
|
||||
const uint16_t watt_cad60_min = 60;
|
||||
const uint16_t watt_cad60_max = 125;
|
||||
|
||||
const uint16_t watt_cad65_min = 70;
|
||||
const uint16_t watt_cad65_max = 160;
|
||||
|
||||
const uint16_t watt_cad70_min = 85;
|
||||
const uint16_t watt_cad70_max = 190;
|
||||
|
||||
const uint16_t watt_cad75_min = 100;
|
||||
const uint16_t watt_cad75_max = 240;
|
||||
|
||||
const uint16_t watt_cad80_min = 115;
|
||||
const uint16_t watt_cad80_max = 280;
|
||||
|
||||
const uint16_t watt_cad85_min = 130;
|
||||
const uint16_t watt_cad85_max = 340;
|
||||
|
||||
const uint16_t watt_cad90_min = 150;
|
||||
const uint16_t watt_cad90_max = 390;
|
||||
|
||||
const uint16_t watt_cad95_min = 175;
|
||||
const uint16_t watt_cad95_max = 450;
|
||||
|
||||
const uint16_t watt_cad100_min = 195;
|
||||
const uint16_t watt_cad100_max = 520;
|
||||
|
||||
const uint16_t watt_cad105_min = 210;
|
||||
const uint16_t watt_cad105_max = 600;
|
||||
|
||||
const uint16_t watt_cad110_min = 245;
|
||||
const uint16_t watt_cad110_max = 675;
|
||||
|
||||
const uint16_t watt_cad115_min = 270;
|
||||
const uint16_t watt_cad115_max = 760;
|
||||
|
||||
const uint16_t watt_cad120_min = 300;
|
||||
const uint16_t watt_cad120_max = 850;
|
||||
|
||||
const uint16_t watt_cad125_min = 330;
|
||||
const uint16_t watt_cad125_max = 945;
|
||||
|
||||
const uint16_t watt_cad130_min = 360;
|
||||
const uint16_t watt_cad130_max = 1045;
|
||||
|
||||
if(currentSpeed() <= 0) return 0;
|
||||
|
||||
if(currentCadence() < 41)
|
||||
return((((watt_cad40_max-watt_cad40_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad40_min);
|
||||
else if(currentCadence() < 46)
|
||||
return((((watt_cad45_max-watt_cad45_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad45_min);
|
||||
else if(currentCadence() < 51)
|
||||
return((((watt_cad50_max-watt_cad50_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad50_min);
|
||||
else if(currentCadence() < 56)
|
||||
return((((watt_cad55_max-watt_cad55_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad55_min);
|
||||
else if(currentCadence() < 61)
|
||||
return((((watt_cad60_max-watt_cad60_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad60_min);
|
||||
else if(currentCadence() < 66)
|
||||
return((((watt_cad65_max-watt_cad65_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad65_min);
|
||||
else if(currentCadence() < 71)
|
||||
return((((watt_cad70_max-watt_cad70_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad70_min);
|
||||
else if(currentCadence() < 76)
|
||||
return((((watt_cad75_max-watt_cad75_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad75_min);
|
||||
else if(currentCadence() < 81)
|
||||
return((((watt_cad80_max-watt_cad80_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad80_min);
|
||||
else if(currentCadence() < 86)
|
||||
return((((watt_cad85_max-watt_cad85_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad85_min);
|
||||
else if(currentCadence() < 91)
|
||||
return((((watt_cad90_max-watt_cad90_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad90_min);
|
||||
else if(currentCadence() < 96)
|
||||
return((((watt_cad95_max-watt_cad95_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad95_min);
|
||||
else if(currentCadence() < 101)
|
||||
return((((watt_cad100_max-watt_cad100_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad100_min);
|
||||
else if(currentCadence() < 106)
|
||||
return((((watt_cad105_max-watt_cad105_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad105_min);
|
||||
else if(currentCadence() < 111)
|
||||
return((((watt_cad110_max-watt_cad110_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad110_min);
|
||||
else if(currentCadence() < 116)
|
||||
return((((watt_cad115_max-watt_cad115_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad115_min);
|
||||
else if(currentCadence() < 121)
|
||||
return((((watt_cad120_max-watt_cad120_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad120_min);
|
||||
else if(currentCadence() < 126)
|
||||
return((((watt_cad125_max-watt_cad125_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad125_min);
|
||||
else
|
||||
return((((watt_cad130_max-watt_cad130_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad130_min);
|
||||
return 0;
|
||||
}
|
||||
36
src/bike.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef BIKE_H
|
||||
#define BIKE_H
|
||||
|
||||
#include <QObject>
|
||||
#include "bluetoothdevice.h"
|
||||
|
||||
class bike:public bluetoothdevice
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
bike();
|
||||
virtual int8_t currentResistance();
|
||||
virtual uint8_t currentCadence();
|
||||
virtual uint8_t fanSpeed();
|
||||
virtual double currentCrankRevolutions();
|
||||
virtual uint16_t lastCrankEventTime();
|
||||
virtual bool connected();
|
||||
uint16_t watts();
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType();
|
||||
|
||||
public slots:
|
||||
virtual void changeResistance(int8_t res);
|
||||
|
||||
signals:
|
||||
void bikeStarted();
|
||||
|
||||
protected:
|
||||
uint8_t Cadence = 0;
|
||||
int8_t Resistance = 0;
|
||||
uint16_t LastCrankEventTime = 0;
|
||||
int8_t requestResistance = -1;
|
||||
double CrankRevs = 0;
|
||||
};
|
||||
|
||||
#endif // BIKE_H
|
||||
133
src/bluetooth.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "bluetooth.h"
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool noHeartService, uint32_t pollDeviceTime, bool noConsole, bool testResistance) : QObject(nullptr)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
|
||||
filterDevice = deviceName;
|
||||
this->testResistance = testResistance;
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
this->pollDeviceTime = pollDeviceTime;
|
||||
this->noConsole = noConsole;
|
||||
this->logs = logs;
|
||||
|
||||
#ifndef WIN32
|
||||
if(!QBluetoothLocalDevice::allDevices().count())
|
||||
{
|
||||
debug("no bluetooth dongle found!");
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Create a discovery agent and connect to its signals
|
||||
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
|
||||
connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
|
||||
this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
|
||||
|
||||
// Start a discovery
|
||||
discoveryAgent->start();
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::debug(QString text)
|
||||
{
|
||||
QString debug = QDateTime::currentDateTime().toString() + " " + QString::number(QDateTime::currentMSecsSinceEpoch()) + " " + text;
|
||||
if(logs)
|
||||
qDebug() << debug;
|
||||
}
|
||||
|
||||
void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
{
|
||||
emit deviceFound(device.name());
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')' + " " + device.majorDeviceClass() + ":" + device.minorDeviceClass());
|
||||
/* only on qt 5.12
|
||||
foreach(quint16 i, device.manufacturerIds())
|
||||
{
|
||||
debug("manufacturer id: " + QString::number(i) + " -> " + device.manufacturerData(i));
|
||||
}*/
|
||||
|
||||
bool filter = true;
|
||||
if(filterDevice.length())
|
||||
{
|
||||
filter = (device.name().compare(filterDevice, Qt::CaseInsensitive) == 0);
|
||||
}
|
||||
|
||||
if(device.name().startsWith("Domyos-Bike") && !device.name().startsWith("DomyosBridge") && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
domyosBike = new domyosbike(noWriteResistance, noHeartService, testResistance);
|
||||
emit(deviceConnected());
|
||||
connect(domyosBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(domyosBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
domyosBike->deviceDiscovered(device);
|
||||
}
|
||||
else if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge") && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
domyos = new domyostreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
emit(deviceConnected());
|
||||
connect(domyos, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(domyos, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
domyos->deviceDiscovered(device);
|
||||
}
|
||||
else if((device.name().startsWith("TRX ROUTE KEY")) && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
toorx = new toorxtreadmill();
|
||||
emit(deviceConnected());
|
||||
connect(toorx, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(toorx, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
toorx->deviceDiscovered(device);
|
||||
}
|
||||
else if((device.name().startsWith("TOORX")) && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
trxappgateusb = new trxappgateusbtreadmill();
|
||||
emit(deviceConnected());
|
||||
connect(trxappgateusb, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(trxappgateusb, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
trxappgateusb->deviceDiscovered(device);
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::restart()
|
||||
{
|
||||
if(domyos)
|
||||
{
|
||||
delete domyos;
|
||||
domyos = 0;
|
||||
}
|
||||
if(domyosBike)
|
||||
{
|
||||
delete domyosBike;
|
||||
domyosBike = 0;
|
||||
}
|
||||
if(toorx)
|
||||
{
|
||||
delete toorx;
|
||||
toorx = 0;
|
||||
}
|
||||
if(trxappgateusb)
|
||||
{
|
||||
delete trxappgateusb;
|
||||
trxappgateusb = 0;
|
||||
}
|
||||
discoveryAgent->start();
|
||||
}
|
||||
|
||||
bluetoothdevice* bluetooth::device()
|
||||
{
|
||||
if(domyos)
|
||||
return domyos;
|
||||
else if(domyosBike)
|
||||
return domyosBike;
|
||||
else if(toorx)
|
||||
return toorx;
|
||||
else if(trxappgateusb)
|
||||
return trxappgateusb;
|
||||
return nullptr;
|
||||
}
|
||||
62
src/bluetooth.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef BLUETOOTH_H
|
||||
#define BLUETOOTH_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QFile>
|
||||
|
||||
#include "treadmill.h"
|
||||
#include "domyostreadmill.h"
|
||||
#include "domyosbike.h"
|
||||
#include "trxappgateusbtreadmill.h"
|
||||
#include "toorxtreadmill.h"
|
||||
#include "bluetoothdevice.h"
|
||||
|
||||
class bluetooth : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit bluetooth(bool logs, QString deviceName = "", bool noWriteResistance = false, bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false, bool testResistance = false);
|
||||
bluetoothdevice* device();
|
||||
|
||||
private:
|
||||
QFile* debugCommsLog = 0;
|
||||
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
|
||||
domyostreadmill* domyos = 0;
|
||||
domyosbike* domyosBike = 0;
|
||||
toorxtreadmill* toorx = 0;
|
||||
trxappgateusbtreadmill* trxappgateusb = 0;
|
||||
QString filterDevice = "";
|
||||
bool testResistance = false;
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool noConsole = false;
|
||||
bool logs = true;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
|
||||
signals:
|
||||
void deviceConnected();
|
||||
void deviceFound(QString name);
|
||||
|
||||
public slots:
|
||||
void restart();
|
||||
void debug(QString string);
|
||||
|
||||
private slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // BLUETOOTH_H
|
||||
20
src/bluetoothdevice.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "bluetoothdevice.h"
|
||||
#include <QTime>
|
||||
|
||||
bluetoothdevice::bluetoothdevice()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
|
||||
void bluetoothdevice::start(){ requestStart = 1; }
|
||||
void bluetoothdevice::stop(){ requestStop = 1; }
|
||||
unsigned char bluetoothdevice::currentHeart(){ return Heart; }
|
||||
double bluetoothdevice::currentSpeed(){ return Speed; }
|
||||
QTime bluetoothdevice::currentPace(){ return QTime(0, (int)(1.0 / (Speed / 60.0)), (((double)(1.0 / (Speed / 60.0)) - ((double)((int)(1.0 / (Speed / 60.0))))) * 60.0), 0 ); }
|
||||
double bluetoothdevice::odometer(){ return Distance; }
|
||||
double bluetoothdevice::calories(){ return KCal; }
|
||||
uint8_t bluetoothdevice::fanSpeed() { return FanSpeed; };
|
||||
void* bluetoothdevice::VirtualDevice() { return nullptr; }
|
||||
bool bluetoothdevice::changeFanSpeed(uint8_t speed) { Q_UNUSED(speed); return false; }
|
||||
bool bluetoothdevice::connected() { return false; }
|
||||
49
src/bluetoothdevice.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef BLUETOOTHDEVICE_H
|
||||
#define BLUETOOTHDEVICE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class bluetoothdevice : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
bluetoothdevice();
|
||||
virtual unsigned char currentHeart();
|
||||
virtual double currentSpeed();
|
||||
virtual QTime currentPace();
|
||||
virtual double odometer();
|
||||
virtual double calories();
|
||||
virtual uint8_t fanSpeed();
|
||||
virtual bool connected();
|
||||
virtual void* VirtualDevice();
|
||||
uint16_t watts(double weight=75.0);
|
||||
virtual bool changeFanSpeed(uint8_t speed);
|
||||
|
||||
enum BLUETOOTH_TYPE {
|
||||
UNKNOWN = 0,
|
||||
TREADMILL,
|
||||
BIKE,
|
||||
ROWING
|
||||
};
|
||||
|
||||
virtual BLUETOOTH_TYPE deviceType();
|
||||
|
||||
public slots:
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
|
||||
protected:
|
||||
double elapsed = 0;
|
||||
double Speed = 0;
|
||||
double KCal = 0;
|
||||
double Distance = 0;
|
||||
uint8_t FanSpeed = 0;
|
||||
uint8_t Heart = 0;
|
||||
int8_t requestStart = -1;
|
||||
int8_t requestStop = -1;
|
||||
int8_t requestIncreaseFan = -1;
|
||||
int8_t requestDecreaseFan = -1;
|
||||
|
||||
};
|
||||
|
||||
#endif // BLUETOOTHDEVICE_H
|
||||
163
src/charts.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "charts.h"
|
||||
#include "ui_charts.h"
|
||||
|
||||
charts::charts(MainWindow *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::charts)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
this->parent = parent;
|
||||
|
||||
chart = new QtCharts::QChart();
|
||||
chart_view = new QtCharts::QChartView(chart, ui->widget);
|
||||
ui->widget->setVisible(false);
|
||||
|
||||
chart_series_speed = new QtCharts::QLineSeries();
|
||||
chart_series_pace = new QtCharts::QLineSeries();
|
||||
chart_series_inclination = new QtCharts::QLineSeries();
|
||||
chart_series_heart = new QtCharts::QLineSeries();
|
||||
chart_series_watt = new QtCharts::QLineSeries();
|
||||
chart_series_resistance = new QtCharts::QLineSeries();
|
||||
|
||||
chart_series_speed->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_speed->setPointLabelsColor(Qt::black);
|
||||
chart_series_speed->setPointLabelsFormat("@yPoint km/h");
|
||||
chart_series_pace->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_pace->setPointLabelsColor(Qt::black);
|
||||
chart_series_pace->setPointLabelsFormat("@yPoint min/km");
|
||||
chart_series_inclination->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_inclination->setPointLabelsColor(Qt::black);
|
||||
chart_series_inclination->setPointLabelsFormat("@yPoint%");
|
||||
chart_series_heart->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_heart->setPointLabelsColor(Qt::black);
|
||||
chart_series_heart->setPointLabelsFormat("@yPoint bpm");
|
||||
chart_series_watt->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_watt->setPointLabelsColor(Qt::black);
|
||||
chart_series_watt->setPointLabelsFormat("@yPoint W");
|
||||
chart_series_resistance->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_resistance->setPointLabelsColor(Qt::black);
|
||||
chart_series_resistance->setPointLabelsFormat("@yPoint lvl");
|
||||
chart_series_speed->setName("Speed (km/h)");
|
||||
chart_series_pace->setName("Pace (min/km)");
|
||||
chart_series_inclination->setName("Inclination (%)");
|
||||
chart_series_heart->setName("Heart (bpm)");
|
||||
chart_series_watt->setName("Watt (W)");
|
||||
chart_series_resistance->setName("Resistance (lvl)");
|
||||
|
||||
chart->legend()->setAlignment(Qt::AlignBottom);
|
||||
chart_view->setRenderHint(QPainter::Antialiasing);
|
||||
chart_view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
ui->frame->layout()->addWidget(chart_view);
|
||||
}
|
||||
|
||||
void charts::update()
|
||||
{
|
||||
if(chart->series().count())
|
||||
{
|
||||
if(ui->speed->isChecked())
|
||||
chart->removeSeries(chart_series_speed);
|
||||
if(ui->pace->isChecked())
|
||||
chart->removeSeries(chart_series_pace);
|
||||
if(ui->inclination->isChecked())
|
||||
chart->removeSeries(chart_series_inclination);
|
||||
if(ui->heart->isChecked())
|
||||
chart->removeSeries(chart_series_heart);
|
||||
if(ui->watt->isChecked())
|
||||
chart->removeSeries(chart_series_watt);
|
||||
if(ui->resistance->isChecked())
|
||||
chart->removeSeries(chart_series_resistance);
|
||||
}
|
||||
chart_series_inclination->clear();
|
||||
chart_series_speed->clear();
|
||||
chart_series_pace->clear();
|
||||
chart_series_heart->clear();
|
||||
chart_series_watt->clear();
|
||||
chart_series_resistance->clear();
|
||||
const int maxQueue = 100;
|
||||
|
||||
for(int g=0; g<(parent->Session.count() > maxQueue ? maxQueue : parent->Session.count()); g++)
|
||||
{
|
||||
int index = g + (parent->Session.count() > maxQueue ? parent->Session.count() % maxQueue : 0);
|
||||
if(ui->inclination->isChecked())
|
||||
chart_series_inclination->append(g, static_cast<double>(parent->Session[index].inclination));
|
||||
if(ui->speed->isChecked())
|
||||
chart_series_speed->append(g, static_cast<qreal>(parent->Session[index].speed));
|
||||
if(ui->pace->isChecked())
|
||||
chart_series_pace->append(g, static_cast<qreal>(parent->Session[index].pace));
|
||||
if(ui->heart->isChecked())
|
||||
chart_series_heart->append(g, static_cast<qreal>(parent->Session[index].heart));
|
||||
if(ui->watt->isChecked())
|
||||
chart_series_watt->append(g, static_cast<qreal>(parent->Session[index].watt));
|
||||
if(ui->resistance->isChecked())
|
||||
chart_series_resistance->append(g, static_cast<qreal>(parent->Session[index].resistance));
|
||||
}
|
||||
|
||||
if(ui->inclination->isChecked())
|
||||
chart->addSeries(chart_series_inclination);
|
||||
if(ui->speed->isChecked())
|
||||
chart->addSeries(chart_series_speed);
|
||||
if(ui->pace->isChecked())
|
||||
chart->addSeries(chart_series_pace);
|
||||
if(ui->heart->isChecked())
|
||||
chart->addSeries(chart_series_heart);
|
||||
if(ui->watt->isChecked())
|
||||
chart->addSeries(chart_series_watt);
|
||||
if(ui->resistance->isChecked())
|
||||
chart->addSeries(chart_series_resistance);
|
||||
chart->createDefaultAxes();
|
||||
}
|
||||
|
||||
charts::~charts()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void charts::on_valueOnChart_stateChanged(int arg1)
|
||||
{
|
||||
Q_UNUSED(arg1);
|
||||
|
||||
if(ui->valueOnChart->checkState() == Qt::Checked)
|
||||
{
|
||||
chart_series_speed->setPointLabelsVisible(true);
|
||||
chart_series_pace->setPointLabelsVisible(true);
|
||||
chart_series_inclination->setPointLabelsVisible(true); // is false by default
|
||||
chart_series_heart->setPointLabelsVisible(true); // is false by default
|
||||
chart_series_watt->setPointLabelsVisible(true); // is false by default
|
||||
chart_series_resistance->setPointLabelsVisible(true); // is false by default
|
||||
}
|
||||
else
|
||||
{
|
||||
chart_series_speed->setPointLabelsVisible(false);
|
||||
chart_series_pace->setPointLabelsVisible(false);
|
||||
chart_series_inclination->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_heart->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_watt->setPointLabelsVisible(false); // is false by default
|
||||
chart_series_resistance->setPointLabelsVisible(false); // is false by default
|
||||
}
|
||||
}
|
||||
|
||||
void charts::on_speed_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void charts::on_Inclination_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void charts::on_watt_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void charts::on_resistance_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void charts::on_heart_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
48
src/charts.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef CHARTS_H
|
||||
#define CHARTS_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QtCharts>
|
||||
#include "mainwindow.h"
|
||||
|
||||
namespace Ui {
|
||||
class charts;
|
||||
}
|
||||
|
||||
class charts : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit charts(MainWindow *parent = nullptr);
|
||||
void update();
|
||||
~charts();
|
||||
|
||||
private slots:
|
||||
void on_valueOnChart_stateChanged(int arg1);
|
||||
|
||||
void on_speed_clicked();
|
||||
|
||||
void on_Inclination_clicked();
|
||||
|
||||
void on_watt_clicked();
|
||||
|
||||
void on_resistance_clicked();
|
||||
|
||||
void on_heart_clicked();
|
||||
|
||||
private:
|
||||
Ui::charts *ui;
|
||||
MainWindow* parent = 0;
|
||||
|
||||
QtCharts::QChart* chart = 0;
|
||||
QtCharts::QChartView* chart_view = 0;
|
||||
QtCharts::QLineSeries* chart_series_speed = 0;
|
||||
QtCharts::QLineSeries* chart_series_inclination = 0;
|
||||
QtCharts::QLineSeries* chart_series_heart = 0;
|
||||
QtCharts::QLineSeries* chart_series_watt = 0;
|
||||
QtCharts::QLineSeries* chart_series_resistance = 0;
|
||||
QtCharts::QLineSeries* chart_series_pace = 0;
|
||||
};
|
||||
|
||||
#endif // CHARTS_H
|
||||
210
src/charts.ui
Normal file
@@ -0,0 +1,210 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>charts</class>
|
||||
<widget class="QDialog" name="charts">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>640</width>
|
||||
<height>480</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Charts</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="speed">
|
||||
<property name="text">
|
||||
<string>Speed</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/icons/icons/speed.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="inclination">
|
||||
<property name="text">
|
||||
<string>Inclination</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/icons/icons/inclination.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="watt">
|
||||
<property name="text">
|
||||
<string>Watt</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/icons/icons/watt.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="resistance">
|
||||
<property name="text">
|
||||
<string>Resistance</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/icons/icons/inclination.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="heart">
|
||||
<property name="text">
|
||||
<string>Heart</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/icons/icons/heart_red.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pace">
|
||||
<property name="text">
|
||||
<string>Pace</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normalon>:/icons/icons/pace.png</normalon>
|
||||
</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="valueOnChart">
|
||||
<property name="text">
|
||||
<string>Value on Chart</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
518
src/domyosbike.cpp
Normal file
@@ -0,0 +1,518 @@
|
||||
#include "domyosbike.h"
|
||||
#include "virtualbike.h"
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance)
|
||||
{
|
||||
refresh = new QTimer(this);
|
||||
this->testResistance = testResistance;
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
initDone = false;
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
refresh->start(200);
|
||||
}
|
||||
|
||||
void domyosbike::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
|
||||
{
|
||||
QEventLoop loop;
|
||||
if(wait_for_response)
|
||||
{
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
}
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
|
||||
|
||||
if(!disable_log)
|
||||
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void domyosbike::updateDisplay(uint16_t elapsed)
|
||||
{
|
||||
uint8_t display[] = {0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
|
||||
0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00};
|
||||
|
||||
display[3] = (elapsed / 60) & 0xFF; // high byte for elapsed time (in seconds)
|
||||
display[4] = (elapsed % 60 & 0xFF); // low byte for elasped time (in seconds)
|
||||
|
||||
display[7] = ((uint8_t)((uint16_t)(currentSpeed()) >> 8)) & 0xFF;
|
||||
display[8] = (uint8_t)(currentSpeed()) & 0xFF;
|
||||
|
||||
display[12] = currentHeart();
|
||||
|
||||
//display[13] = ((((uint8_t)calories())) >> 8) & 0xFF;
|
||||
//display[14] = (((uint8_t)calories())) & 0xFF;
|
||||
|
||||
display[16] = (uint8_t)currentCadence();
|
||||
|
||||
display[19] = ((((uint16_t)calories())) >> 8) & 0xFF;
|
||||
display[20] = (((uint16_t)calories())) & 0xFF;
|
||||
|
||||
for(uint8_t i=0; i<sizeof(display)-1; i++)
|
||||
{
|
||||
display[26] += display[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed) );
|
||||
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed) );
|
||||
|
||||
//if(bike_type == CHANG_YOW)
|
||||
{
|
||||
uint8_t display2[] = {0xf0, 0xcd, 0x01, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
|
||||
|
||||
display2[3] = ((((uint16_t)(odometer() * 10))) >> 8) & 0xFF;
|
||||
display2[4] = (((uint16_t)(odometer() * 10))) & 0xFF;
|
||||
|
||||
for(uint8_t i=0; i<sizeof(display2)-1; i++)
|
||||
{
|
||||
display2[26] += display2[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(display2, 20, "updateDisplay2");
|
||||
writeCharacteristic(&display2[20], sizeof (display2) - 20, "updateDisplay2");
|
||||
}
|
||||
}
|
||||
|
||||
void domyosbike::forceResistance(int8_t requestResistance)
|
||||
{
|
||||
uint8_t write[] = {0xf0, 0xad, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff,
|
||||
0xff, 0xff, 0x00};
|
||||
|
||||
write[10] = requestResistance;
|
||||
|
||||
for(uint8_t i=0; i<sizeof(write)-1; i++)
|
||||
{
|
||||
write[22] += write[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(write, 20, "forceResistance " + QString::number(requestResistance));
|
||||
writeCharacteristic(&write[20], sizeof (write) - 20, "forceResistance " + QString::number(requestResistance));
|
||||
}
|
||||
|
||||
void domyosbike::update()
|
||||
{
|
||||
static QDateTime lastTime;
|
||||
static bool first = true;
|
||||
uint8_t noOpData[] = { 0xf0, 0xac, 0x9c };
|
||||
|
||||
// stop tape
|
||||
uint8_t initDataF0C800B8[] = { 0xf0, 0xc8, 0x00, 0xb8 };
|
||||
|
||||
static uint8_t sec1 = 0;
|
||||
|
||||
if(m_control->state() == QLowEnergyController::UnconnectedState)
|
||||
{
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if(initRequest)
|
||||
{
|
||||
initRequest = false;
|
||||
//if(bike_type == CHANG_YOW)
|
||||
btinit_changyow(false);
|
||||
//else
|
||||
// btinit_telink(false);
|
||||
}
|
||||
else if(btbike.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
gattNotifyCharacteristic.isValid() &&
|
||||
initDone)
|
||||
{
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
if(currentSpeed() > 0.0 && !first)
|
||||
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
|
||||
lastTime = current;
|
||||
|
||||
// updating the treadmill console every second
|
||||
if(sec1++ == (500 / refresh->interval()))
|
||||
{
|
||||
sec1 = 0;
|
||||
updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), "noOp", true);
|
||||
|
||||
if(testResistance)
|
||||
{
|
||||
if((((int)elapsed) % 5) == 0)
|
||||
{
|
||||
uint8_t new_res = currentResistance() + 1;
|
||||
if(new_res > 15)
|
||||
new_res = 1;
|
||||
forceResistance(new_res);
|
||||
}
|
||||
}
|
||||
|
||||
if(requestResistance != -1)
|
||||
{
|
||||
if(requestResistance > 15) requestResistance = 15;
|
||||
else if(requestResistance == 0) requestResistance = 1;
|
||||
|
||||
if(requestResistance != currentResistance())
|
||||
{
|
||||
debug("writing resistance " + QString::number(requestResistance));
|
||||
forceResistance(requestResistance);
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
if(requestStart != -1)
|
||||
{
|
||||
debug("starting...");
|
||||
|
||||
//if(bike_type == CHANG_YOW)
|
||||
btinit_changyow(true);
|
||||
//else
|
||||
// btinit_telink(true);
|
||||
|
||||
requestStart = -1;
|
||||
emit bikeStarted();
|
||||
}
|
||||
if(requestStop != -1)
|
||||
{
|
||||
debug("stopping...");
|
||||
writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
void domyosbike::serviceDiscovered(const QBluetoothUuid &gatt)
|
||||
{
|
||||
debug("serviceDiscovered " + gatt.toString());
|
||||
}
|
||||
|
||||
static QByteArray lastPacket;
|
||||
void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
static QDateTime lastRefresh = QDateTime::currentDateTime();
|
||||
|
||||
debug(" << " + newValue.toHex(' '));
|
||||
|
||||
if (lastPacket.length() && lastPacket == newValue)
|
||||
return;
|
||||
|
||||
lastPacket = newValue;
|
||||
if (newValue.length() != 26)
|
||||
return;
|
||||
|
||||
if (newValue.at(22) == 0x06)
|
||||
{
|
||||
debug("start button pressed!");
|
||||
requestStart = 1;
|
||||
}
|
||||
else if (newValue.at(22) == 0x07)
|
||||
{
|
||||
debug("stop button pressed!");
|
||||
requestStop = 1;
|
||||
}
|
||||
|
||||
/*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the inclination and speed status
|
||||
return;*/
|
||||
|
||||
double speed = GetSpeedFromPacket(newValue);
|
||||
double kcal = GetKcalFromPacket(newValue);
|
||||
double distance = GetDistanceFromPacket(newValue);
|
||||
|
||||
Cadence = newValue.at(9);
|
||||
Resistance = newValue.at(14);
|
||||
if(Resistance < 1)
|
||||
{
|
||||
debug("invalid resistance value " + QString::number(Resistance) + " putting to default");
|
||||
Resistance = 1;
|
||||
}
|
||||
Heart = newValue.at(18);
|
||||
|
||||
CrankRevs += ((double)(lastRefresh.msecsTo(QDateTime::currentDateTime())) * ((double)Cadence / 60000.0) );
|
||||
LastCrankEventTime += (uint16_t)((lastRefresh.msecsTo(QDateTime::currentDateTime())) * 1.024);
|
||||
lastRefresh = QDateTime::currentDateTime();
|
||||
|
||||
debug("Current speed: " + QString::number(speed));
|
||||
debug("Current cadence: " + QString::number(Cadence));
|
||||
debug("Current resistance: " + QString::number(Resistance));
|
||||
debug("Current heart: " + QString::number(Heart));
|
||||
debug("Current KCal: " + QString::number(kcal));
|
||||
debug("Current Distance: " + QString::number(distance));
|
||||
debug("Current CrankRevs: " + QString::number(CrankRevs));
|
||||
debug("Last CrankEventTime: " + QString::number(LastCrankEventTime));
|
||||
debug("Current Watt: " + QString::number(watts()));
|
||||
|
||||
if(m_control->error() != QLowEnergyController::NoError)
|
||||
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
|
||||
|
||||
Speed = speed;
|
||||
KCal = kcal;
|
||||
Distance = distance;
|
||||
}
|
||||
|
||||
double domyosbike::GetSpeedFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(6) << 8) | packet.at(7);
|
||||
double data = (double)convertedData / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
double domyosbike::GetKcalFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(10) << 8) | packet.at(11);
|
||||
return (double)convertedData;
|
||||
}
|
||||
|
||||
double domyosbike::GetDistanceFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(12) << 8) | packet.at(13);
|
||||
double data = ((double)convertedData) / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
void domyosbike::btinit_changyow(bool startTape)
|
||||
{
|
||||
// set speed and incline to 0
|
||||
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
|
||||
uint8_t initData2[] = { 0xf0, 0xc9, 0xb9 };
|
||||
|
||||
// main startup sequence
|
||||
uint8_t initDataStart[] = { 0xf0, 0xa3, 0x93 };
|
||||
uint8_t initDataStart2[] = { 0xf0, 0xa4, 0x94 };
|
||||
uint8_t initDataStart3[] = { 0xf0, 0xa5, 0x95 };
|
||||
uint8_t initDataStart4[] = { 0xf0, 0xab, 0x9b };
|
||||
uint8_t initDataStart5[] = { 0xf0, 0xc4, 0x03, 0xb7 };
|
||||
uint8_t initDataStart6[] =
|
||||
{
|
||||
0xf0, 0xad, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff
|
||||
};
|
||||
uint8_t initDataStart7[] = { 0xff, 0xff, 0x8b }; // power on bt icon
|
||||
uint8_t initDataStart8[] =
|
||||
{
|
||||
0xf0, 0xcb, 0x02, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00
|
||||
};
|
||||
uint8_t initDataStart9[] = { 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xb6 }; // power on bt word
|
||||
uint8_t initDataStart10[] =
|
||||
{
|
||||
0xf0, 0xad, 0xff, 0xff, 0x00, 0x05, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0xff
|
||||
};
|
||||
uint8_t initDataStart11[] = { 0xff, 0xff, 0x94 }; // start tape
|
||||
uint8_t initDataStart12[] =
|
||||
{
|
||||
0xf0, 0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x14, 0x01, 0xff, 0xff
|
||||
};
|
||||
uint8_t initDataStart13[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbd };
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), "init", false, true);
|
||||
writeCharacteristic(initDataStart, sizeof(initDataStart), "init", false, true);
|
||||
writeCharacteristic(initDataStart2, sizeof(initDataStart2), "init", false, true);
|
||||
writeCharacteristic(initDataStart3, sizeof(initDataStart3), "init", false, true);
|
||||
writeCharacteristic(initDataStart4, sizeof(initDataStart4), "init", false, true);
|
||||
writeCharacteristic(initDataStart5, sizeof(initDataStart5), "init", false, true);
|
||||
writeCharacteristic(initDataStart6, sizeof(initDataStart6), "init", false, false);
|
||||
writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
|
||||
writeCharacteristic(initDataStart8, sizeof(initDataStart8), "init", false, false);
|
||||
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
|
||||
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
|
||||
if(startTape)
|
||||
{
|
||||
writeCharacteristic(initDataStart11, sizeof(initDataStart11), "init", false, true);
|
||||
writeCharacteristic(initDataStart12, sizeof(initDataStart12), "init", false, false);
|
||||
writeCharacteristic(initDataStart13, sizeof(initDataStart13), "init", false, true);
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void domyosbike::btinit_telink(bool startTape)
|
||||
{
|
||||
Q_UNUSED(startTape)
|
||||
|
||||
// set speed and incline to 0
|
||||
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
|
||||
uint8_t initData2[] = { 0xf0, 0xc9, 0xb9 };
|
||||
uint8_t noOpData[] = { 0xf0, 0xac, 0x9c };
|
||||
|
||||
// main startup sequence
|
||||
uint8_t initDataStart[] = { 0xf0, 0xcc, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xb8 };
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init");
|
||||
writeCharacteristic(initData2, sizeof(initData2), "init");
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), "noOp");
|
||||
writeCharacteristic(initDataStart, sizeof(initDataStart), "init");
|
||||
updateDisplay(0);
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
|
||||
void domyosbike::stateChanged(QLowEnergyService::ServiceState state)
|
||||
{
|
||||
QBluetoothUuid _gattWriteCharacteristicId((QString)"49535343-8841-43f4-a8d4-ecbe34729bb3");
|
||||
QBluetoothUuid _gattNotifyCharacteristicId((QString)"49535343-1e4d-4bd9-ba61-23c647249616");
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
if(state == QLowEnergyService::ServiceDiscovered)
|
||||
{
|
||||
//qDebug() << gattCommunicationChannelService->characteristics();
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotifyCharacteristic.isValid());
|
||||
|
||||
// establish hook into notifications
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
|
||||
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
|
||||
this, SLOT(errorService(QLowEnergyService::ServiceError)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
|
||||
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
|
||||
|
||||
// ******************************************* virtual bike init *************************************
|
||||
static uint8_t first = 0;
|
||||
if(!first)
|
||||
{
|
||||
debug("creating virtual bike interface...");
|
||||
virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
connect(virtualBike,&virtualbike::debug ,this,&domyosbike::debug);
|
||||
}
|
||||
first = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void domyosbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
|
||||
{
|
||||
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
}
|
||||
|
||||
void domyosbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
Q_UNUSED(characteristic);
|
||||
debug("characteristicWritten " + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void domyosbike::serviceScanDone(void)
|
||||
{
|
||||
debug("serviceScanDone");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((QString)"49535343-fe7d-4ae5-8fa9-9fafd205e455");
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void domyosbike::errorService(QLowEnergyService::ServiceError err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
debug("domyosbike::errorService" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
}
|
||||
|
||||
void domyosbike::error(QLowEnergyController::Error err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
debug("domyosbike::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
|
||||
m_control->disconnect();
|
||||
}
|
||||
|
||||
void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
{
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("Domyos-Bike") && !device.name().startsWith("DomyosBridge"))
|
||||
{
|
||||
btbike = device;
|
||||
|
||||
if(device.address().toString().startsWith("57"))
|
||||
{
|
||||
debug("domyos telink bike found");
|
||||
bike_type = TELINK;
|
||||
}
|
||||
else
|
||||
{
|
||||
debug("domyos changyow bike found");
|
||||
bike_type = CHANG_YOW;
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(btbike, this);
|
||||
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
|
||||
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
|
||||
connect(m_control, SIGNAL(discoveryFinished()),
|
||||
this, SLOT(serviceScanDone()));
|
||||
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
|
||||
this, SLOT(error(QLowEnergyController::Error)));
|
||||
|
||||
connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
debug("Cannot connect to remote device.");
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
debug("Controller connected. Search services...");
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
debug("LowEnergy controller disconnected");
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool domyosbike::connected()
|
||||
{
|
||||
if(!m_control)
|
||||
return false;
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void* domyosbike::VirtualBike()
|
||||
{
|
||||
return virtualBike;
|
||||
}
|
||||
|
||||
void* domyosbike::VirtualDevice()
|
||||
{
|
||||
return VirtualBike();
|
||||
}
|
||||
95
src/domyosbike.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#ifndef DOMYOSBIKE_H
|
||||
#define DOMYOSBIKE_H
|
||||
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "virtualbike.h"
|
||||
#include "bike.h"
|
||||
|
||||
class domyosbike : public bike
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyosbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false);
|
||||
bool connected();
|
||||
|
||||
void* VirtualBike();
|
||||
void* VirtualDevice();
|
||||
|
||||
private:
|
||||
double GetSpeedFromPacket(QByteArray packet);
|
||||
double GetInclinationFromPacket(QByteArray packet);
|
||||
double GetKcalFromPacket(QByteArray packet);
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
void forceResistance(int8_t requestResistance);
|
||||
void updateDisplay(uint16_t elapsed);
|
||||
void btinit_changyow(bool startTape);
|
||||
void btinit_telink(bool startTape);
|
||||
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log=false, bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
|
||||
QTimer* refresh;
|
||||
virtualbike* virtualBike = 0;
|
||||
|
||||
QBluetoothDeviceInfo btbike;
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotifyCharacteristic;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool testResistance = false;
|
||||
|
||||
enum _BIKE_TYPE {
|
||||
CHANG_YOW,
|
||||
TELINK,
|
||||
};
|
||||
_BIKE_TYPE bike_type = CHANG_YOW;
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void stateChanged(QLowEnergyService::ServiceState state);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
};
|
||||
|
||||
#endif // DOMYOSBIKE_H
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "domyostreadmill.h"
|
||||
#include "virtualtreadmill.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
// set speed and incline to 0
|
||||
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
|
||||
@@ -11,17 +14,6 @@ uint8_t noOpData[] = { 0xf0, 0xac, 0x9c };
|
||||
// stop tape
|
||||
uint8_t initDataF0C800B8[] = { 0xf0, 0xc8, 0x00, 0xb8 };
|
||||
|
||||
#if 0
|
||||
uint8_t initDataStart[] = { 0xf0, 0xc8, 0x00, 0xb8 };
|
||||
uint8_t initDataStart2[] = { 0xf0, 0xcb, 0x01, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00 };
|
||||
uint8_t initDataStart3[] = { 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xb6 };
|
||||
uint8_t initDataStart4[] = { 0xf0, 0xc8, 0x00, 0xb8 };
|
||||
uint8_t initDataStart5[] = { 0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00 };
|
||||
uint8_t initDataStart6[] = { 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc1 };
|
||||
#endif
|
||||
|
||||
// main startup sequence
|
||||
uint8_t initDataStart[] = { 0xf0, 0xa3, 0x93 };
|
||||
uint8_t initDataStart2[] = { 0xf0, 0xa4, 0x94 };
|
||||
@@ -57,129 +49,253 @@ QBluetoothUuid _gattCommunicationChannelServiceId((QString)"49535343-fe7d-4ae5-8
|
||||
QBluetoothUuid _gattWriteCharacteristicId((QString)"49535343-8841-43f4-a8d4-ecbe34729bb3");
|
||||
QBluetoothUuid _gattNotifyCharacteristicId((QString)"49535343-1e4d-4bd9-ba61-23c647249616");
|
||||
|
||||
QBluetoothDeviceInfo treadmill;
|
||||
QBluetoothDeviceInfo bttreadmill;
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotifyCharacteristic;
|
||||
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
extern volatile double currentSpeed;
|
||||
extern volatile double currentIncline;
|
||||
extern volatile uint8_t currentHeart;
|
||||
extern volatile double requestSpeed;
|
||||
extern volatile double requestIncline;
|
||||
extern volatile int8_t requestStart;
|
||||
extern volatile int8_t requestStop;
|
||||
|
||||
domyostreadmill::domyostreadmill()
|
||||
domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService)
|
||||
{
|
||||
QTimer* refresh = new QTimer(this);
|
||||
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
|
||||
// Create a discovery agent and connect to its signals
|
||||
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
|
||||
connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
|
||||
this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
|
||||
|
||||
// Start a discovery
|
||||
discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
|
||||
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
refresh->start(200);
|
||||
refresh->start(pollDeviceTime);
|
||||
}
|
||||
|
||||
void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline, uint16_t elapsed)
|
||||
void domyostreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
|
||||
{
|
||||
uint8_t writeIncline[] = {0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
|
||||
(uint8_t)(requestSpeed * 10), 0x01, 0xff, 0xff, 0xff, 0xff, 0x00};
|
||||
QEventLoop loop;
|
||||
if(wait_for_response)
|
||||
{
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
}
|
||||
|
||||
writeIncline[3] = (elapsed >> 8) & 0xFF; // high byte for elapsed time (in seconds)
|
||||
writeIncline[4] = (elapsed & 0xFF); // low byte for elasped time (in seconds)
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
|
||||
|
||||
writeIncline[12] = currentHeart;
|
||||
if(!disable_log)
|
||||
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
|
||||
|
||||
writeIncline[16] = (uint8_t)(requestIncline * 10);
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void domyostreadmill::updateDisplay(uint16_t elapsed)
|
||||
{
|
||||
uint8_t display[] = {0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x01, 0x01, 0x00,
|
||||
0x0c, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00};
|
||||
|
||||
if(elapsed > 5999) // 99:59
|
||||
{
|
||||
display[3] = ((elapsed / 60) / 60) & 0xFF; // high byte for elapsed time (in seconds)
|
||||
display[4] = ((elapsed / 60) % 60) & 0xFF; // low byte for elasped time (in seconds)
|
||||
}
|
||||
else
|
||||
{
|
||||
display[3] = (elapsed / 60) & 0xFF; // high byte for elapsed time (in seconds)
|
||||
display[4] = (elapsed % 60 & 0xFF); // low byte for elasped time (in seconds)
|
||||
}
|
||||
|
||||
if(odometer() < 10.0)
|
||||
{
|
||||
display[7] = ((uint8_t)((uint16_t)(odometer() * 100) >> 8)) & 0xFF;
|
||||
display[8] = (uint8_t)(odometer() * 100) & 0xFF;
|
||||
display[9] = 0x02; // decimal position
|
||||
}
|
||||
else if(odometer() < 100.0)
|
||||
{
|
||||
display[7] = ((uint8_t)(odometer() * 10) >> 8) & 0xFF;
|
||||
display[8] = (uint8_t)(odometer() * 10) & 0xFF;
|
||||
display[9] = 0x01; // decimal position
|
||||
}
|
||||
else
|
||||
{
|
||||
display[7] = ((uint8_t)(odometer()) >> 8) & 0xFF;
|
||||
display[8] = (uint8_t)(odometer()) & 0xFF;
|
||||
display[9] = 0x00; // decimal position
|
||||
}
|
||||
|
||||
display[12] = currentHeart();
|
||||
|
||||
display[23] = ((uint8_t)(calories()) >> 8) & 0xFF;
|
||||
display[24] = (uint8_t)(calories()) & 0xFF;
|
||||
|
||||
for(uint8_t i=0; i<sizeof(display)-1; i++)
|
||||
{
|
||||
display[26] += display[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed) );
|
||||
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed) );
|
||||
}
|
||||
|
||||
void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline)
|
||||
{
|
||||
uint8_t writeIncline[] = {0xf0, 0xad, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00};
|
||||
|
||||
writeIncline[4] = ((uint16_t)(requestSpeed*10) >> 8) & 0xFF;
|
||||
writeIncline[5] = ((uint16_t)(requestSpeed*10) & 0xFF);
|
||||
|
||||
writeIncline[13] = ((uint16_t)(requestIncline*10) >> 8) & 0xFF;
|
||||
writeIncline[14] = ((uint16_t)(requestIncline*10) & 0xFF);
|
||||
|
||||
for(uint8_t i=0; i<sizeof(writeIncline)-1; i++)
|
||||
{
|
||||
//qDebug() << QString::number(writeIncline[i], 16);
|
||||
writeIncline[26] += writeIncline[i]; // the last byte is a sort of a checksum
|
||||
writeIncline[22] += writeIncline[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
//qDebug() << "writeIncline crc" << QString::number(writeIncline[26], 16);
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)writeIncline, sizeof(writeIncline)));
|
||||
|
||||
writeCharacteristic(writeIncline, 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline));
|
||||
writeCharacteristic(&writeIncline[20], sizeof (writeIncline) - 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline));
|
||||
}
|
||||
|
||||
bool domyostreadmill::changeFanSpeed(uint8_t speed)
|
||||
{
|
||||
uint8_t fanSpeed[] = {0xf0, 0xca, 0x00, 0x00};
|
||||
|
||||
if(speed > 5) return false;
|
||||
|
||||
fanSpeed[2] = speed;
|
||||
|
||||
for(uint8_t i=0; i<sizeof(fanSpeed)-1; i++)
|
||||
{
|
||||
fanSpeed[3] += fanSpeed[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(fanSpeed, 4, "changeFanSpeed speed=" + QString::number(speed));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void domyostreadmill::update()
|
||||
{
|
||||
static uint8_t first = 0;
|
||||
static virtualtreadmill* v;
|
||||
static uint32_t counter = 0;
|
||||
Q_UNUSED(v);
|
||||
//qDebug() << treadmill.isValid() << m_control->state() << gattCommunicationChannelService << gattWriteCharacteristic.isValid() << gattNotifyCharacteristic.isValid() << initDone;
|
||||
if(treadmill.isValid() &&
|
||||
(m_control->state() == QLowEnergyController::ConnectedState || m_control->state() == QLowEnergyController::DiscoveredState) &&
|
||||
static uint8_t sec1 = 0;
|
||||
static QDateTime lastTime;
|
||||
static bool first = true;
|
||||
|
||||
if(m_control->state() == QLowEnergyController::UnconnectedState)
|
||||
{
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if(initRequest)
|
||||
{
|
||||
initRequest = false;
|
||||
btinit(false);
|
||||
}
|
||||
else if(bttreadmill.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
gattNotifyCharacteristic.isValid() &&
|
||||
initDone)
|
||||
{
|
||||
counter++;
|
||||
if(!first)
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
if(currentSpeed() > 0.0 && !first)
|
||||
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
|
||||
lastTime = current;
|
||||
|
||||
// updating the treadmill console every second
|
||||
if(sec1++ >= (500 / refresh->interval()))
|
||||
{
|
||||
qDebug() << "creating virtual treadmill interface...";
|
||||
v = new virtualtreadmill();
|
||||
if(incompletePackets == false && noConsole == false)
|
||||
{
|
||||
sec1 = 0;
|
||||
updateDisplay(elapsed);
|
||||
}
|
||||
}
|
||||
first = 1;
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)noOpData, sizeof(noOpData)));
|
||||
|
||||
if(incompletePackets == false)
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), "noOp", true);
|
||||
|
||||
// byte 3 - 4 = elapsed time
|
||||
// byte 17 = inclination
|
||||
if(incompletePackets == false)
|
||||
{
|
||||
if(requestSpeed != -1)
|
||||
{
|
||||
if(requestSpeed != currentSpeed())
|
||||
{
|
||||
debug("writing speed " + QString::number(requestSpeed));
|
||||
double inc = Inclination;
|
||||
if(requestInclination != -1)
|
||||
{
|
||||
inc = requestInclination;
|
||||
requestInclination = -1;
|
||||
}
|
||||
forceSpeedOrIncline(requestSpeed, inc);
|
||||
}
|
||||
requestSpeed = -1;
|
||||
}
|
||||
if(requestInclination != -1)
|
||||
{
|
||||
if(requestInclination != currentInclination())
|
||||
{
|
||||
debug("writing incline " + QString::number(requestInclination));
|
||||
double speed = currentSpeed();
|
||||
if(requestSpeed != -1)
|
||||
{
|
||||
speed = requestSpeed;
|
||||
requestSpeed = -1;
|
||||
}
|
||||
forceSpeedOrIncline(speed, requestInclination);
|
||||
}
|
||||
requestInclination = -1;
|
||||
}
|
||||
if(requestStart != -1)
|
||||
{
|
||||
debug("starting...");
|
||||
btinit(true);
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if(requestStop != -1)
|
||||
{
|
||||
debug("stopping...");
|
||||
writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
if(requestIncreaseFan != -1)
|
||||
{
|
||||
debug("increasing fan speed...");
|
||||
changeFanSpeed(FanSpeed + 1);
|
||||
requestIncreaseFan = -1;
|
||||
}
|
||||
else if(requestDecreaseFan != -1)
|
||||
{
|
||||
debug("decreasing fan speed...");
|
||||
changeFanSpeed(FanSpeed - 1);
|
||||
requestDecreaseFan = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(requestSpeed != -1)
|
||||
{
|
||||
qDebug() << "writing speed" << requestSpeed;
|
||||
forceSpeedOrIncline(requestSpeed, currentIncline, counter/5);
|
||||
requestSpeed = -1;
|
||||
}
|
||||
if(requestIncline != -1)
|
||||
{
|
||||
qDebug() << "writing incline" << requestIncline;
|
||||
forceSpeedOrIncline(currentSpeed, requestIncline, counter/5);
|
||||
requestIncline = -1;
|
||||
}
|
||||
if(requestStart != -1)
|
||||
{
|
||||
qDebug() << "starting...(TODO)";
|
||||
requestStart = -1;
|
||||
}
|
||||
if(requestStop != -1)
|
||||
{
|
||||
qDebug() << "stopping...";
|
||||
/*gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData1, sizeof(initData1)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData2, sizeof(initData2)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart, sizeof(initDataStart)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart2, sizeof(initDataStart2)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart3, sizeof(initDataStart3)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart4, sizeof(initDataStart4)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart5, sizeof(initDataStart5)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart6, sizeof(initDataStart6)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart7, sizeof(initDataStart7)));*/
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataF0C800B8, sizeof(initDataF0C800B8)));
|
||||
requestStop = -1;
|
||||
}
|
||||
elevationAcc += (currentSpeed() / 3600.0) * 1000 * (currentInclination() / 100) * (refresh->interval() / 1000);
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
void domyostreadmill::serviceDiscovered(const QBluetoothUuid &gatt)
|
||||
{
|
||||
qDebug() << "serviceDiscovered" << gatt;
|
||||
debug("serviceDiscovered " + gatt.toString());
|
||||
}
|
||||
|
||||
static QByteArray lastPacket;
|
||||
@@ -187,40 +303,100 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
|
||||
{
|
||||
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
static QDateTime lastTime;
|
||||
static bool first = true;
|
||||
QByteArray value = newValue;
|
||||
|
||||
if (lastPacket.length() && lastPacket == newValue)
|
||||
debug(" << " + QString::number(value.length()) + " " + value.toHex(' '));
|
||||
|
||||
if (lastPacket.length() && lastPacket == value)
|
||||
return;
|
||||
|
||||
lastPacket = newValue;
|
||||
if (newValue.length() != 26)
|
||||
return;
|
||||
QByteArray startBytes;
|
||||
startBytes.append(0xf0);
|
||||
startBytes.append(0xbc);
|
||||
|
||||
if (newValue.at(22) == 0x07)
|
||||
QByteArray startBytes2;
|
||||
startBytes2.append(0xf0);
|
||||
startBytes2.append(0xdb);
|
||||
|
||||
// on some treadmills, the 26bytes has splitted in 2 packets
|
||||
if((lastPacket.length() == 20 && lastPacket.startsWith(startBytes) && value.length() == 6) ||
|
||||
(lastPacket.length() == 20 && lastPacket.startsWith(startBytes2) && value.length() == 7))
|
||||
{
|
||||
qDebug() << "STOP PRESSED!";
|
||||
requestStop = 1;
|
||||
incompletePackets = false;
|
||||
debug("...final bytes received");
|
||||
lastPacket.append(value);
|
||||
value = lastPacket;
|
||||
}
|
||||
|
||||
/*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the inclination and speed status
|
||||
lastPacket = value;
|
||||
|
||||
if (value.length() != 26)
|
||||
{
|
||||
// semaphore for any writing packets (for example, update display)
|
||||
if(value.length() == 20 && (value.startsWith(startBytes) || value.startsWith(startBytes2)))
|
||||
{
|
||||
debug("waiting for other bytes...");
|
||||
incompletePackets = true;
|
||||
}
|
||||
|
||||
debug("packet ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.at(22) == 0x06)
|
||||
{
|
||||
debug("start button pressed!");
|
||||
requestStart = 1;
|
||||
}
|
||||
else if (value.at(22) == 0x07)
|
||||
{
|
||||
debug("stop button pressed!");
|
||||
requestStop = 1;
|
||||
}
|
||||
else if (value.at(22) == 0x0b)
|
||||
{
|
||||
debug("increase speed fan pressed!");
|
||||
requestIncreaseFan = 1;
|
||||
}
|
||||
else if (value.at(22) == 0x0a)
|
||||
{
|
||||
debug("decrease speed fan pressed!");
|
||||
requestDecreaseFan = 1;
|
||||
}
|
||||
|
||||
/*if ((uint8_t)value.at(1) != 0xbc && value.at(2) != 0x04) // intense run, these are the bytes for the inclination and speed status
|
||||
return;*/
|
||||
|
||||
double speed = GetSpeedFromPacket(newValue);
|
||||
double incline = GetInclinationFromPacket(newValue);
|
||||
//var isStartPressed = GetIsStartPressedFromPacket(currentPacket);
|
||||
//var isStopPressed = GetIsStopPressedFromPacket(currentPacket);
|
||||
double speed = GetSpeedFromPacket(value);
|
||||
double incline = GetInclinationFromPacket(value);
|
||||
double kcal = GetKcalFromPacket(value);
|
||||
double distance = GetDistanceFromPacket(value);
|
||||
|
||||
#if DEBUG
|
||||
Debug.WriteLine(args.CharacteristicValue.ToArray().HexDump());
|
||||
#endif
|
||||
Heart = value.at(18);
|
||||
FanSpeed = value.at(23);
|
||||
|
||||
currentHeart = newValue.at(18);
|
||||
if(!first)
|
||||
DistanceCalculated += ((speed / 3600.0) / ( 1000.0 / (lastTime.msecsTo(QDateTime::currentDateTime()))));
|
||||
|
||||
qDebug() << "Current speed: " << speed;
|
||||
qDebug() << "Current incline: " << incline;
|
||||
qDebug() << "Current heart:" << currentHeart;
|
||||
debug("Current speed: " + QString::number(speed));
|
||||
debug("Current incline: " + QString::number(incline));
|
||||
debug("Current heart: " + QString::number(Heart));
|
||||
debug("Current KCal: " + QString::number(kcal));
|
||||
debug("Current Distance: " + QString::number(distance));
|
||||
debug("Current Distance Calculated: " + QString::number(DistanceCalculated));
|
||||
|
||||
currentSpeed = speed;
|
||||
currentIncline = incline;
|
||||
if(m_control->error() != QLowEnergyController::NoError)
|
||||
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
|
||||
|
||||
Speed = speed;
|
||||
Inclination = incline;
|
||||
KCal = kcal;
|
||||
Distance = distance;
|
||||
|
||||
lastTime = QDateTime::currentDateTime();
|
||||
first = false;
|
||||
}
|
||||
|
||||
double domyostreadmill::GetSpeedFromPacket(QByteArray packet)
|
||||
@@ -230,94 +406,157 @@ double domyostreadmill::GetSpeedFromPacket(QByteArray packet)
|
||||
return data;
|
||||
}
|
||||
|
||||
double domyostreadmill::GetKcalFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(10) << 8) | packet.at(11);
|
||||
return (double)convertedData;
|
||||
}
|
||||
|
||||
double domyostreadmill::GetDistanceFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(12) << 8) | packet.at(13);
|
||||
double data = ((double)convertedData) / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
double domyostreadmill::GetInclinationFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(2) << 8) | packet.at(3);
|
||||
qDebug() << convertedData;
|
||||
double data = ((double)convertedData - 1000.0f) / 10.0f;
|
||||
if (data < 0) return 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
void domyostreadmill::btinit(bool startTape)
|
||||
{
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
writeCharacteristic(initData2, sizeof(initData2), "init", false, true);
|
||||
writeCharacteristic(initDataStart, sizeof(initDataStart), "init", false, true);
|
||||
writeCharacteristic(initDataStart2, sizeof(initDataStart2), "init", false, true);
|
||||
writeCharacteristic(initDataStart3, sizeof(initDataStart3), "init", false, true);
|
||||
writeCharacteristic(initDataStart4, sizeof(initDataStart4), "init", false, true);
|
||||
writeCharacteristic(initDataStart5, sizeof(initDataStart5), "init", false, true);
|
||||
writeCharacteristic(initDataStart6, sizeof(initDataStart6), "init", false, false);
|
||||
writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
|
||||
writeCharacteristic(initDataStart8, sizeof(initDataStart8), "init", false, false);
|
||||
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
|
||||
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
|
||||
if(startTape)
|
||||
{
|
||||
writeCharacteristic(initDataStart11, sizeof(initDataStart11), "init", false, true);
|
||||
writeCharacteristic(initDataStart12, sizeof(initDataStart12), "init", false, false);
|
||||
writeCharacteristic(initDataStart13, sizeof(initDataStart13), "init", false, true);
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void domyostreadmill::stateChanged(QLowEnergyService::ServiceState state)
|
||||
{
|
||||
qDebug() << "stateChanged" << state;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
if(state == QLowEnergyService::ServiceDiscovered)
|
||||
{
|
||||
//qDebug() << gattCommunicationChannelService->characteristics();
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotifyCharacteristic.isValid());
|
||||
|
||||
// establish hook into notifications
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
|
||||
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
|
||||
this, SLOT(errorService(QLowEnergyService::ServiceError)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
|
||||
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
|
||||
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
static uint8_t first = 0;
|
||||
if(!first)
|
||||
{
|
||||
debug("creating virtual treadmill interface...");
|
||||
virtualTreadMill = new virtualtreadmill(this, noHeartService);
|
||||
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&domyostreadmill::debug);
|
||||
}
|
||||
first = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
// await _gattNotifyCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData1, sizeof(initData1)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData2, sizeof(initData2)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart, sizeof(initDataStart)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart2, sizeof(initDataStart2)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart3, sizeof(initDataStart3)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart4, sizeof(initDataStart4)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart5, sizeof(initDataStart5)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart6, sizeof(initDataStart6)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart7, sizeof(initDataStart7)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart8, sizeof(initDataStart8)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart9, sizeof(initDataStart9)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart10, sizeof(initDataStart10)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart11, sizeof(initDataStart11)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart12, sizeof(initDataStart12)));
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart13, sizeof(initDataStart13)));
|
||||
|
||||
initDone = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void domyostreadmill::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
|
||||
{
|
||||
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
}
|
||||
|
||||
void domyostreadmill::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
Q_UNUSED(characteristic);
|
||||
debug("characteristicWritten " + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void domyostreadmill::serviceScanDone(void)
|
||||
{
|
||||
qDebug() << "serviceScanDone";
|
||||
debug("serviceScanDone");
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void domyostreadmill::errorService(QLowEnergyService::ServiceError err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
debug("domyostreadmill::errorService " + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
}
|
||||
|
||||
void domyostreadmill::error(QLowEnergyController::Error err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
debug("domyostreadmill::error " + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
m_control->disconnect();
|
||||
}
|
||||
|
||||
void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
{
|
||||
qDebug() << "Found new device:" << device.name() << '(' << device.address().toString() << ')';
|
||||
if(device.name().startsWith("Domyos"))
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge"))
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
treadmill = device;
|
||||
m_control = QLowEnergyController::createCentral(treadmill, this);
|
||||
bttreadmill = device;
|
||||
m_control = QLowEnergyController::createCentral(bttreadmill, this);
|
||||
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
|
||||
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
|
||||
connect(m_control, SIGNAL(discoveryFinished()),
|
||||
this, SLOT(serviceScanDone()));
|
||||
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
|
||||
this, SLOT(error(QLowEnergyController::Error)));
|
||||
|
||||
connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
qDebug() << "Cannot connect to remote device.";
|
||||
exit(1);
|
||||
debug("Cannot connect to remote device.");
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
qDebug() << "Controller connected. Search services...";
|
||||
debug("Controller connected. Search services...");
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
qDebug() << "LowEnergy controller disconnected";
|
||||
exit(2);
|
||||
debug("LowEnergy controller disconnected");
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
@@ -325,3 +564,25 @@ void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool domyostreadmill::connected()
|
||||
{
|
||||
if(!m_control)
|
||||
return false;
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void* domyostreadmill::VirtualTreadMill()
|
||||
{
|
||||
return virtualTreadMill;
|
||||
}
|
||||
|
||||
void* domyostreadmill::VirtualDevice()
|
||||
{
|
||||
return VirtualTreadMill();
|
||||
}
|
||||
|
||||
double domyostreadmill::odometer()
|
||||
{
|
||||
return DistanceCalculated;
|
||||
}
|
||||
|
||||
@@ -18,33 +18,65 @@
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class domyostreadmill : QObject
|
||||
#include "virtualtreadmill.h"
|
||||
#include "treadmill.h"
|
||||
|
||||
class domyostreadmill : public treadmill
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyostreadmill();
|
||||
domyostreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false);
|
||||
bool connected();
|
||||
bool changeFanSpeed(uint8_t speed);
|
||||
double odometer();
|
||||
|
||||
void* VirtualTreadMill();
|
||||
void* VirtualDevice();
|
||||
|
||||
private:
|
||||
double GetSpeedFromPacket(QByteArray packet);
|
||||
double GetInclinationFromPacket(QByteArray packet);
|
||||
void forceSpeedOrIncline(double requestSpeed, double requestIncline, uint16_t elapsed);
|
||||
double GetKcalFromPacket(QByteArray packet);
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
void forceSpeedOrIncline(double requestSpeed, double requestIncline);
|
||||
void updateDisplay(uint16_t elapsed);
|
||||
void btinit(bool startTape);
|
||||
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log = false, bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
double DistanceCalculated = 0;
|
||||
volatile bool incompletePackets = false;
|
||||
bool noConsole = false;
|
||||
bool noHeartService = false;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
|
||||
QTimer* refresh;
|
||||
virtualtreadmill* virtualTreadMill = 0;
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void stateChanged(QLowEnergyService::ServiceState state);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
};
|
||||
|
||||
#endif // DOMYOSTREADMILL_H
|
||||
|
||||
49
src/gpx.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "gpx.h"
|
||||
#include <QDomDocument>
|
||||
#include <QDebug>
|
||||
#include "math.h"
|
||||
|
||||
gpx::gpx(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QList<gpx_altitude_point_for_treadmill> gpx::open(QString gpx)
|
||||
{
|
||||
QFile input(gpx);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
QDomDocument doc;
|
||||
doc.setContent(&input);
|
||||
QDomNodeList points = doc.elementsByTagName("trkpt");
|
||||
for (int i = 0; i < points.size(); i++)
|
||||
{
|
||||
QDomNode point = points.item(i);
|
||||
QDomNamedNodeMap att = point.attributes();
|
||||
QString lat = att.namedItem("lat").nodeValue();
|
||||
QString lon = att.namedItem("lon").nodeValue();
|
||||
QDomElement ele = point.firstChildElement("ele");
|
||||
QDomElement time = point.firstChildElement("time");
|
||||
gpx_point g;
|
||||
//2020-10-10T10:54:45
|
||||
g.time = QDateTime::fromString(time.text(), Qt::ISODate);
|
||||
g.p.setAltitude(ele.text().toFloat());
|
||||
g.p.setLatitude(lat.toFloat());
|
||||
g.p.setLongitude(lon.toFloat());
|
||||
this->points.append(g);
|
||||
}
|
||||
|
||||
const uint8_t secondsInclination = 60;
|
||||
QList<gpx_altitude_point_for_treadmill> inclinationList;
|
||||
for(int32_t i=secondsInclination; i<this->points.count(); i+=secondsInclination)
|
||||
{
|
||||
double distance = this->points[i].p.distanceTo(this->points[i-secondsInclination].p);
|
||||
double elevation = this->points[i].p.altitude() - this->points[i-secondsInclination].p.altitude();
|
||||
|
||||
gpx_altitude_point_for_treadmill g;
|
||||
g.seconds = secondsInclination;
|
||||
g.speed = (distance / 1000.0) * (3600 / secondsInclination);
|
||||
g.inclination = (elevation / distance) * 100;
|
||||
inclinationList.append(g);
|
||||
}
|
||||
return inclinationList;
|
||||
}
|
||||
38
src/gpx.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef GPX_H
|
||||
#define GPX_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QTime>
|
||||
#include <QGeoCoordinate>
|
||||
|
||||
class gpx_altitude_point_for_treadmill
|
||||
{
|
||||
public:
|
||||
uint32_t seconds;
|
||||
float inclination;
|
||||
float speed;
|
||||
};
|
||||
|
||||
class gpx_point
|
||||
{
|
||||
public:
|
||||
QDateTime time;
|
||||
QGeoCoordinate p;
|
||||
};
|
||||
|
||||
class gpx : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit gpx(QObject *parent = nullptr);
|
||||
QList<gpx_altitude_point_for_treadmill> open(QString gpx);
|
||||
|
||||
private:
|
||||
QList<gpx_point> points;
|
||||
|
||||
signals:
|
||||
|
||||
};
|
||||
|
||||
#endif // GPX_H
|
||||
252
src/homeform.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#include "homeform.h"
|
||||
#include <QQmlContext>
|
||||
#include <QTime>
|
||||
|
||||
DataObject::DataObject(QString name, QString icon, QString value, bool writable, QString id)
|
||||
{
|
||||
m_name = name;
|
||||
m_icon = icon;
|
||||
m_value = value;
|
||||
m_writable = writable;
|
||||
m_id = id;
|
||||
|
||||
emit plusNameChanged(plusName());
|
||||
emit minusNameChanged(minusName());
|
||||
}
|
||||
|
||||
void DataObject::setValue(QString v) {m_value = v; emit valueChanged(m_value);}
|
||||
|
||||
homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
|
||||
{
|
||||
this->bluetoothManager = bl;
|
||||
connect(bluetoothManager, SIGNAL(deviceFound(QString)), this, SLOT(deviceFound(QString)));
|
||||
engine->rootContext()->setContextProperty("rootItem", (QObject *)this);
|
||||
|
||||
dataList = {
|
||||
speed,
|
||||
inclination,
|
||||
cadence,
|
||||
elevation,
|
||||
calories,
|
||||
odometer,
|
||||
pace,
|
||||
resistance,
|
||||
watt,
|
||||
heart,
|
||||
fan
|
||||
};
|
||||
|
||||
engine->rootContext()->setContextProperty("appModel", QVariant::fromValue(dataList));
|
||||
|
||||
QObject *rootObject = engine->rootObjects().first();
|
||||
QObject *home = rootObject->findChild<QObject*>("home");
|
||||
QObject::connect(home, SIGNAL(start_clicked()),
|
||||
this, SLOT(Start()));
|
||||
QObject::connect(home, SIGNAL(stop_clicked()),
|
||||
this, SLOT(Stop()));
|
||||
QObject::connect(home, SIGNAL(plus_clicked(QString)),
|
||||
this, SLOT(Plus(QString)));
|
||||
QObject::connect(home, SIGNAL(minus_clicked(QString)),
|
||||
this, SLOT(Minus(QString)));
|
||||
|
||||
timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &homeform::update);
|
||||
timer->start(1000);
|
||||
}
|
||||
|
||||
void homeform::deviceFound(QString name)
|
||||
{
|
||||
m_info = name + " founded";
|
||||
emit infoChanged(m_info);
|
||||
}
|
||||
|
||||
void homeform::Plus(QString name)
|
||||
{
|
||||
if(name.contains("speed"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(name.contains("inclination"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(name.contains("resistance"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(name.contains("fan"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << name << "not handled";
|
||||
}
|
||||
}
|
||||
|
||||
void homeform::Minus(QString name)
|
||||
{
|
||||
if(name.contains("speed"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() - 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(name.contains("inclination"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() - 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(name.contains("resistance"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(name.contains("fan"))
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << name << "not handled";
|
||||
}
|
||||
}
|
||||
|
||||
void homeform::Start()
|
||||
{
|
||||
//trainProgram->restart();
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->start();
|
||||
}
|
||||
|
||||
void homeform::Stop()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->stop();
|
||||
}
|
||||
|
||||
void homeform::update()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
double inclination = 0;
|
||||
double resistance = 0;
|
||||
double watts = 0;
|
||||
double pace = 0;
|
||||
|
||||
speed->setValue(QString::number(bluetoothManager->device()->currentSpeed(), 'f', 2));
|
||||
heart->setValue(QString::number(bluetoothManager->device()->currentHeart()));
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer(), 'f', 2));
|
||||
calories->setValue(QString::number(bluetoothManager->device()->calories(), 'f', 0));
|
||||
fan->setValue(QString::number(bluetoothManager->device()->fanSpeed()));
|
||||
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
|
||||
if(pace < 0) pace = 0;
|
||||
watts = ((treadmill*)bluetoothManager->device())->watts(/*weight->text().toFloat()*/); // TODO: add weight to settings
|
||||
inclination = ((treadmill*)bluetoothManager->device())->currentInclination();
|
||||
this->pace->setValue(((treadmill*)bluetoothManager->device())->currentPace().toString("m:ss"));
|
||||
watt->setValue(QString::number(watts, 'f', 0));
|
||||
this->inclination->setValue(QString::number(inclination, 'f', 1));
|
||||
elevation->setValue(QString::number(((treadmill*)bluetoothManager->device())->elevationGain(), 'f', 1));
|
||||
}
|
||||
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
resistance = ((bike*)bluetoothManager->device())->currentResistance();
|
||||
watts = ((bike*)bluetoothManager->device())->watts();
|
||||
watt->setValue(QString::number(watts));
|
||||
this->resistance->setValue(QString::number(resistance));
|
||||
}
|
||||
/*
|
||||
if(trainProgram)
|
||||
{
|
||||
trainProgramElapsedTime->setText(trainProgram->totalElapsedTime().toString("hh:mm:ss"));
|
||||
trainProgramCurrentRowElapsedTime->setText(trainProgram->currentRowElapsedTime().toString("hh:mm:ss"));
|
||||
trainProgramDuration->setText(trainProgram->duration().toString("hh:mm:ss"));
|
||||
|
||||
double distance = trainProgram->totalDistance();
|
||||
if(distance > 0)
|
||||
{
|
||||
trainProgramTotalDistance->setText(QString::number(distance));
|
||||
}
|
||||
else
|
||||
trainProgramTotalDistance->setText("N/A");
|
||||
}
|
||||
*/
|
||||
|
||||
SessionLine s(
|
||||
bluetoothManager->device()->currentSpeed(),
|
||||
inclination,
|
||||
bluetoothManager->device()->odometer(),
|
||||
watts,
|
||||
resistance,
|
||||
bluetoothManager->device()->currentHeart(),
|
||||
pace);
|
||||
|
||||
Session.append(s);
|
||||
}
|
||||
|
||||
emit changeOfdevice();
|
||||
emit changeOfzwift();
|
||||
}
|
||||
|
||||
bool homeform::getDevice()
|
||||
{
|
||||
if(!this->bluetoothManager->device())
|
||||
return false;
|
||||
return this->bluetoothManager->device()->connected();
|
||||
}
|
||||
|
||||
bool homeform::getZwift()
|
||||
{
|
||||
if(!this->bluetoothManager->device())
|
||||
return false;
|
||||
if(!this->bluetoothManager->device()->VirtualDevice())
|
||||
return false;
|
||||
if(this->bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL &&
|
||||
((virtualtreadmill*)((treadmill*)bluetoothManager->device())->VirtualDevice())->connected())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE &&
|
||||
((virtualbike*)((bike*)bluetoothManager->device())->VirtualDevice())->connected())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
94
src/homeform.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifndef HOMEFORM_H
|
||||
#define HOMEFORM_H
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include "bluetooth.h"
|
||||
#include "sessionline.h"
|
||||
|
||||
class DataObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
|
||||
Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)
|
||||
Q_PROPERTY(bool writable READ writable NOTIFY writableChanged)
|
||||
Q_PROPERTY(QString plusName READ plusName NOTIFY plusNameChanged)
|
||||
Q_PROPERTY(QString minusName READ minusName NOTIFY minusNameChanged)
|
||||
|
||||
public:
|
||||
DataObject(QString name, QString icon, QString value, bool writable, QString id);
|
||||
void setValue(QString value);
|
||||
QString name() {return m_name;}
|
||||
QString icon() {return m_icon;}
|
||||
QString value() {return m_value;}
|
||||
bool writable() {return m_writable;}
|
||||
QString plusName() {return m_id + "_plus";}
|
||||
QString minusName() {return m_id + "_minus";}
|
||||
|
||||
QString m_id;
|
||||
QString m_name;
|
||||
QString m_icon;
|
||||
QString m_value;
|
||||
bool m_writable;
|
||||
|
||||
signals:
|
||||
void valueChanged(QString value);
|
||||
void nameChanged(QString value);
|
||||
void iconChanged(QString value);
|
||||
void writableChanged(bool value);
|
||||
void plusNameChanged(QString value);
|
||||
void minusNameChanged(QString value);
|
||||
};
|
||||
|
||||
class homeform: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( bool device READ getDevice NOTIFY changeOfdevice)
|
||||
Q_PROPERTY( bool zwift READ getZwift NOTIFY changeOfzwift)
|
||||
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
|
||||
|
||||
public:
|
||||
homeform(QQmlApplicationEngine* engine, bluetooth* bl);
|
||||
QString info() {return m_info;}
|
||||
|
||||
private:
|
||||
QList<QObject *> dataList;
|
||||
QList<SessionLine> Session;
|
||||
bluetooth* bluetoothManager;
|
||||
|
||||
QString m_info = "Connecting...";
|
||||
|
||||
DataObject* speed = new DataObject("Speed (km/h)", "icons/icons/speed.png", "0.0", true, "speed");
|
||||
DataObject* inclination = new DataObject("Inclination (%)", "icons/icons/inclination.png", "0.0", true, "inclination");
|
||||
DataObject* cadence = new DataObject("Cadence (bpm)", "icons/icons/cadence.png", "0", false, "cadence");
|
||||
DataObject* elevation = new DataObject("Elev. Gain (m)", "icons/icons/elevationgain.png", "0", false, "elevation");
|
||||
DataObject* calories = new DataObject("Calories (KCal)", "icons/icons/kcal.png", "0", false, "calories");
|
||||
DataObject* odometer = new DataObject("Odometer (km)", "icons/icons/odometer.png", "0.0", false, "odometer");
|
||||
DataObject* pace = new DataObject("Pace (m/km)", "icons/icons/pace.png", "0:00", false, "pace");
|
||||
DataObject* resistance = new DataObject("Resistance (%)", "icons/icons/resistance.png", "0", true, "resistance");
|
||||
DataObject* watt = new DataObject("Watt", "icons/icons/watt.png", "0", false, "watt");
|
||||
DataObject* heart = new DataObject("Heart (bpm)", "icons/icons/heart_red.png", "0", false, "heart");
|
||||
DataObject* fan = new DataObject("Fan Speed", "icons/icons/fan.png", "0", true, "fan");
|
||||
|
||||
QTimer* timer;
|
||||
|
||||
void update();
|
||||
bool getDevice();
|
||||
bool getZwift();
|
||||
|
||||
private slots:
|
||||
void Start();
|
||||
void Stop();
|
||||
void Minus(QString);
|
||||
void Plus(QString);
|
||||
void deviceFound(QString name);
|
||||
|
||||
signals:
|
||||
void changeOfdevice();
|
||||
void changeOfzwift();
|
||||
void infoChanged(QString value);
|
||||
};
|
||||
|
||||
#endif // HOMEFORM_H
|
||||
22
src/icons.qrc
Normal file
@@ -0,0 +1,22 @@
|
||||
<RCC>
|
||||
<qresource prefix="/icons">
|
||||
<file>icons/bluetooth-icon.png</file>
|
||||
<file>icons/zwift-on.png</file>
|
||||
<file>icons/fan.png</file>
|
||||
<file>icons/speed.png</file>
|
||||
<file>icons/inclination.png</file>
|
||||
<file>icons/heart_red.png</file>
|
||||
<file>icons/watt.png</file>
|
||||
<file>icons/odometer.png</file>
|
||||
<file>icons/elevationgain.png</file>
|
||||
<file>icons/kcal.png</file>
|
||||
<file>icons/weight.png</file>
|
||||
<file>icons/start.png</file>
|
||||
<file>icons/stop.png</file>
|
||||
<file>icons/cadence.png</file>
|
||||
<file>icons/resistance.png</file>
|
||||
<file>icons/pace.png</file>
|
||||
<file>icons/chart.png</file>
|
||||
<file>icons/icon.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
BIN
src/icons/bluetooth-icon.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
src/icons/cadence.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/icons/chart.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/icons/elevationgain.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
src/icons/fan.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/icons/heart_red.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/icons/icon.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/icons/icon.xcf
Normal file
BIN
src/icons/inclination.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/icons/kcal.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/icons/odometer.png
Normal file
|
After Width: | Height: | Size: 1011 B |
BIN
src/icons/pace.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/icons/resistance.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
src/icons/speed.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/icons/start.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/icons/stop.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/icons/watt.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
src/icons/weight.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/icons/zwift-on.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
190
src/main.cpp
@@ -1,21 +1,191 @@
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QApplication>
|
||||
#include <QStyleFactory>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <QStandardPaths>
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include "virtualtreadmill.h"
|
||||
#include "domyostreadmill.h"
|
||||
#include "bluetooth.h"
|
||||
#include "mainwindow.h"
|
||||
#include "homeform.h"
|
||||
|
||||
bool nologs = false;
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = true;
|
||||
bool noConsole = false;
|
||||
bool onlyVirtualBike = false;
|
||||
bool onlyVirtualTreadmill = false;
|
||||
bool testResistance = false;
|
||||
QString trainProgram;
|
||||
QString deviceName = "";
|
||||
uint32_t pollDeviceTime = 200;
|
||||
static QString logfilename = "debug-" + QDateTime::currentDateTime().toString().replace(":", "_") + ".log";
|
||||
|
||||
QCoreApplication* createApplication(int &argc, char *argv[])
|
||||
{
|
||||
bool nogui = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!qstrcmp(argv[i], "-no-gui"))
|
||||
nogui = true;
|
||||
if (!qstrcmp(argv[i], "-no-console"))
|
||||
noConsole = true;
|
||||
if (!qstrcmp(argv[i], "-test-resistance"))
|
||||
testResistance = true;
|
||||
if (!qstrcmp(argv[i], "-no-log"))
|
||||
nologs = true;
|
||||
if (!qstrcmp(argv[i], "-no-write-resistance"))
|
||||
noWriteResistance = true;
|
||||
if (!qstrcmp(argv[i], "-no-heart-service"))
|
||||
noHeartService = true;
|
||||
if (!qstrcmp(argv[i], "-heart-service"))
|
||||
noHeartService = false;
|
||||
if (!qstrcmp(argv[i], "-only-virtualbike"))
|
||||
onlyVirtualBike = true;
|
||||
if (!qstrcmp(argv[i], "-only-virtualtreadmill"))
|
||||
onlyVirtualTreadmill = true;
|
||||
if (!qstrcmp(argv[i], "-train"))
|
||||
{
|
||||
trainProgram = argv[++i];
|
||||
}
|
||||
if (!qstrcmp(argv[i], "-name"))
|
||||
{
|
||||
deviceName = argv[++i];
|
||||
}
|
||||
if (!qstrcmp(argv[i], "-poll-device-time"))
|
||||
{
|
||||
pollDeviceTime = atol(argv[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(nogui)
|
||||
return new QCoreApplication(argc, argv);
|
||||
else
|
||||
{
|
||||
QApplication* a = new QApplication(argc, argv);
|
||||
|
||||
a->setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
/*QFont defaultFont = QApplication::font();
|
||||
defaultFont.setPointSize(defaultFont.pointSize()+2);
|
||||
qApp->setFont(defaultFont);*/
|
||||
|
||||
// modify palette to dark
|
||||
QPalette darkPalette;
|
||||
darkPalette.setColor(QPalette::Window,QColor(53,53,53));
|
||||
darkPalette.setColor(QPalette::WindowText,Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled,QPalette::WindowText,QColor(127,127,127));
|
||||
darkPalette.setColor(QPalette::Base,QColor(42,42,42));
|
||||
darkPalette.setColor(QPalette::AlternateBase,QColor(66,66,66));
|
||||
darkPalette.setColor(QPalette::ToolTipBase,Qt::white);
|
||||
darkPalette.setColor(QPalette::ToolTipText,Qt::white);
|
||||
darkPalette.setColor(QPalette::Text,Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled,QPalette::Text,QColor(127,127,127));
|
||||
darkPalette.setColor(QPalette::Dark,QColor(35,35,35));
|
||||
darkPalette.setColor(QPalette::Shadow,QColor(20,20,20));
|
||||
darkPalette.setColor(QPalette::Button,QColor(53,53,53));
|
||||
darkPalette.setColor(QPalette::ButtonText,Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled,QPalette::ButtonText,QColor(127,127,127));
|
||||
darkPalette.setColor(QPalette::BrightText,Qt::red);
|
||||
darkPalette.setColor(QPalette::Link,QColor(42,130,218));
|
||||
darkPalette.setColor(QPalette::Highlight,QColor(42,130,218));
|
||||
darkPalette.setColor(QPalette::Disabled,QPalette::Highlight,QColor(80,80,80));
|
||||
darkPalette.setColor(QPalette::HighlightedText,Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled,QPalette::HighlightedText,QColor(127,127,127));
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
QByteArray localMsg = msg.toLocal8Bit();
|
||||
const char *file = context.file ? context.file : "";
|
||||
const char *function = context.function ? context.function : "";
|
||||
QString txt;
|
||||
switch (type) {
|
||||
case QtInfoMsg:
|
||||
txt = QString("Info: %1 %2 %3\n").arg(file).arg(function).arg(msg);
|
||||
break;
|
||||
case QtDebugMsg:
|
||||
txt = QString("Debug: %1 %2 %3\n").arg(file).arg(function).arg(msg);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
txt = QString("Warning: %1 %2 %3\n").arg(file).arg(function).arg(msg);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
txt = QString("Critical: %1 %2 %3\n").arg(file).arg(function).arg(msg);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
txt = QString("Fatal: %1 %2 %3\n").arg(file).arg(function).arg(msg);
|
||||
abort();
|
||||
}
|
||||
|
||||
if(nologs == false)
|
||||
{
|
||||
QString path = "";
|
||||
#ifdef Q_OS_ANDROID
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
|
||||
#endif
|
||||
|
||||
QFile outFile(path + logfilename);
|
||||
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||
QTextStream ts(&outFile);
|
||||
ts << txt;
|
||||
|
||||
fprintf(stderr, txt.toLocal8Bit());
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
//QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
|
||||
#ifndef Q_OS_ANDROID
|
||||
QCoreApplication app(argc, argv);
|
||||
#else
|
||||
QGuiApplication app(argc, argv);
|
||||
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
|
||||
qInstallMessageHandler(myMessageOutput);
|
||||
|
||||
if(onlyVirtualBike)
|
||||
{
|
||||
virtualbike* V = new virtualbike(new bike(), noWriteResistance, noHeartService);
|
||||
return app->exec();
|
||||
}
|
||||
else if(onlyVirtualTreadmill)
|
||||
{
|
||||
virtualtreadmill* V = new virtualtreadmill(new treadmill(), noHeartService);
|
||||
return app->exec();
|
||||
}
|
||||
#endif
|
||||
bluetooth* bl = new bluetooth(!nologs, deviceName, noWriteResistance, noHeartService, pollDeviceTime, testResistance);
|
||||
|
||||
//virtualtreadmill* V = new virtualtreadmill();
|
||||
domyostreadmill* D = new domyostreadmill();
|
||||
|
||||
//Q_UNUSED(V);
|
||||
Q_UNUSED(D);
|
||||
#ifdef Q_OS_ANDROID
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication app(argc, argv);
|
||||
QQmlApplicationEngine engine;
|
||||
const QUrl url(QStringLiteral("qrc:/main.qml"));
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||
&app, [url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
}, Qt::QueuedConnection);
|
||||
engine.load(url);
|
||||
new homeform(&engine, bl);
|
||||
|
||||
return app.exec();
|
||||
#else
|
||||
if (qobject_cast<QApplication *>(app.data())) {
|
||||
// start GUI version...
|
||||
MainWindow* W = 0;
|
||||
if(trainProgram.isEmpty())
|
||||
W = new MainWindow(bl);
|
||||
else
|
||||
W = new MainWindow(bl, trainProgram);
|
||||
W->show();
|
||||
} else {
|
||||
// start non-GUI version...
|
||||
}
|
||||
return app->exec();
|
||||
#endif
|
||||
}
|
||||
|
||||
67
src/main.qml
Normal file
@@ -0,0 +1,67 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
ApplicationWindow {
|
||||
id: window
|
||||
width: 640
|
||||
height: 480
|
||||
visible: true
|
||||
title: qsTr("Stack")
|
||||
|
||||
header: ToolBar {
|
||||
contentHeight: toolButton.implicitHeight
|
||||
|
||||
ToolButton {
|
||||
id: toolButton
|
||||
icon.source: "icons/icons/icon.png"
|
||||
text: stackView.depth > 1 ? "\u25C0" : "\u2630"
|
||||
font.pixelSize: Qt.application.font.pixelSize * 1.6
|
||||
onClicked: {
|
||||
if (stackView.depth > 1) {
|
||||
stackView.pop()
|
||||
} else {
|
||||
drawer.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: stackView.currentItem.title
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Drawer {
|
||||
id: drawer
|
||||
width: window.width * 0.66
|
||||
height: window.height
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
|
||||
ItemDelegate {
|
||||
text: qsTr("by Roberto Viola")
|
||||
width: parent.width
|
||||
/* onClicked: {
|
||||
stackView.push("Page1Form.ui.qml")
|
||||
drawer.close()
|
||||
}*/
|
||||
}
|
||||
/* ItemDelegate {
|
||||
text: qsTr("Page 2")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("Page2Form.ui.qml")
|
||||
drawer.close()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: stackView
|
||||
initialItem: "Home.qml"
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
540
src/mainwindow.cpp
Normal file
@@ -0,0 +1,540 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include <QFileDialog>
|
||||
#include "gpx.h"
|
||||
#include "charts.h"
|
||||
|
||||
charts* Charts = 0;
|
||||
|
||||
void MainWindow::load(bluetooth* b)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
this->bluetoothManager = b;
|
||||
connect(this->bluetoothManager, SIGNAL(deviceConnected()), this, SLOT(trainProgramSignals()));
|
||||
timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &MainWindow::update);
|
||||
timer->start(1000);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
ui->groupTrain->setVisible(false);
|
||||
ui->fanBar->setVisible(false);
|
||||
ui->fanSpeedMinus->setVisible(false);
|
||||
ui->fanSpeedPlus->setVisible(false);
|
||||
ui->weight->setVisible(false);
|
||||
ui->weightDescription->setVisible(false);
|
||||
ui->weightIcon->setVisible(false);
|
||||
ui->load->setVisible(false);
|
||||
ui->reset->setVisible(false);
|
||||
ui->save->setVisible(false);
|
||||
#endif
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(bluetooth* b) :
|
||||
QDialog(nullptr),
|
||||
ui(new Ui::MainWindow)
|
||||
{
|
||||
load(b);
|
||||
this->trainProgram = new trainprogram(QList<trainrow>(), b);
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(bluetooth* b, QString trainProgram) :
|
||||
QDialog(nullptr),
|
||||
ui(new Ui::MainWindow)
|
||||
{
|
||||
load(b);
|
||||
loadTrainProgram(trainProgram);
|
||||
}
|
||||
|
||||
void MainWindow::update()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
double inclination = 0;
|
||||
double resistance = 0;
|
||||
double watts = 0;
|
||||
double pace = 0;
|
||||
|
||||
ui->speed->setText(QString::number(bluetoothManager->device()->currentSpeed(), 'f', 2));
|
||||
ui->heartrate->setText(QString::number(bluetoothManager->device()->currentHeart()));
|
||||
ui->odometer->setText(QString::number(bluetoothManager->device()->odometer(), 'f', 2));
|
||||
ui->calories->setText(QString::number(bluetoothManager->device()->calories(), 'f', 0));
|
||||
ui->fanBar->setValue(bluetoothManager->device()->fanSpeed());
|
||||
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
|
||||
if(pace < 0) pace = 0;
|
||||
watts = ((treadmill*)bluetoothManager->device())->watts(ui->weight->text().toFloat());
|
||||
inclination = ((treadmill*)bluetoothManager->device())->currentInclination();
|
||||
ui->pace->setText(((treadmill*)bluetoothManager->device())->currentPace().toString("m:ss"));
|
||||
ui->watt->setText(QString::number(watts, 'f', 0));
|
||||
ui->inclination->setText(QString::number(inclination, 'f', 1));
|
||||
ui->elevationGain->setText(QString::number(((treadmill*)bluetoothManager->device())->elevationGain(), 'f', 1));
|
||||
}
|
||||
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
resistance = ((bike*)bluetoothManager->device())->currentResistance();
|
||||
watts = ((bike*)bluetoothManager->device())->watts();
|
||||
ui->watt->setText(QString::number(watts));
|
||||
ui->resistance->setText(QString::number(resistance));
|
||||
}
|
||||
|
||||
if(trainProgram)
|
||||
{
|
||||
ui->trainProgramElapsedTime->setText(trainProgram->totalElapsedTime().toString("hh:mm:ss"));
|
||||
ui->trainProgramCurrentRowElapsedTime->setText(trainProgram->currentRowElapsedTime().toString("hh:mm:ss"));
|
||||
ui->trainProgramDuration->setText(trainProgram->duration().toString("hh:mm:ss"));
|
||||
|
||||
double distance = trainProgram->totalDistance();
|
||||
if(distance > 0)
|
||||
{
|
||||
ui->trainProgramTotalDistance->setText(QString::number(distance));
|
||||
}
|
||||
else
|
||||
ui->trainProgramTotalDistance->setText("N/A");
|
||||
}
|
||||
|
||||
if(bluetoothManager->device()->connected())
|
||||
{
|
||||
ui->connectionToTreadmill->setEnabled(true);
|
||||
if(bluetoothManager->device()->VirtualDevice())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL &&
|
||||
((virtualtreadmill*)((treadmill*)bluetoothManager->device())->VirtualDevice())->connected())
|
||||
{
|
||||
ui->connectionToZwift->setEnabled(true);
|
||||
}
|
||||
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE &&
|
||||
((virtualbike*)((bike*)bluetoothManager->device())->VirtualDevice())->connected())
|
||||
{
|
||||
ui->connectionToZwift->setEnabled(true);
|
||||
}
|
||||
else
|
||||
ui->connectionToZwift->setEnabled(false);
|
||||
}
|
||||
else
|
||||
ui->connectionToZwift->setEnabled(false);
|
||||
}
|
||||
else
|
||||
ui->connectionToTreadmill->setEnabled(false);
|
||||
|
||||
SessionLine s(
|
||||
bluetoothManager->device()->currentSpeed(),
|
||||
inclination,
|
||||
bluetoothManager->device()->odometer(),
|
||||
watts,
|
||||
resistance,
|
||||
bluetoothManager->device()->currentHeart(),
|
||||
pace);
|
||||
|
||||
Session.append(s);
|
||||
|
||||
if(ui->chart->isChecked())
|
||||
{
|
||||
if(!Charts)
|
||||
{
|
||||
Charts = new charts(this);
|
||||
Charts->show();
|
||||
}
|
||||
Charts->update();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->connectionToTreadmill->setEnabled(false);
|
||||
ui->connectionToZwift->setEnabled(false);
|
||||
|
||||
/*
|
||||
* DEBUG CHARTS
|
||||
*
|
||||
|
||||
if(!Charts)
|
||||
{
|
||||
Charts = new charts(this);
|
||||
Charts->show();
|
||||
}
|
||||
|
||||
SessionLine s(
|
||||
(double)QRandomGenerator::global()->bounded(22),
|
||||
QRandomGenerator::global()->bounded(15),
|
||||
(double)QRandomGenerator::global()->bounded(15),
|
||||
QRandomGenerator::global()->bounded(150),
|
||||
0,
|
||||
QRandomGenerator::global()->bounded(180));
|
||||
|
||||
Session.append(s);
|
||||
Charts->update();*/
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::addEmptyRow()
|
||||
{
|
||||
int row = ui->tableWidget->rowCount();
|
||||
editing = true;
|
||||
ui->tableWidget->insertRow(row);
|
||||
ui->tableWidget->setItem(row, 0, new QTableWidgetItem("00:00:00"));
|
||||
ui->tableWidget->setItem(row, 1, new QTableWidgetItem("10"));
|
||||
ui->tableWidget->setItem(row, 2, new QTableWidgetItem("0"));
|
||||
ui->tableWidget->setItem(row, 3, new QTableWidgetItem(""));
|
||||
ui->tableWidget->item(row, 0)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidget->item(row, 1)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidget->item(row, 2)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidget->item(row, 3)->setCheckState(Qt::CheckState::Checked);
|
||||
editing = false;
|
||||
}
|
||||
|
||||
void MainWindow::on_tableWidget_cellChanged(int row, int column)
|
||||
{
|
||||
if(editing) return;
|
||||
if(column == 0)
|
||||
{
|
||||
switch(ui->tableWidget->currentItem()->text().length())
|
||||
{
|
||||
case 4:
|
||||
ui->tableWidget->currentItem()->setText("00:0" + ui->tableWidget->currentItem()->text());
|
||||
break;
|
||||
case 5:
|
||||
ui->tableWidget->currentItem()->setText("00:" + ui->tableWidget->currentItem()->text());
|
||||
break;
|
||||
case 7:
|
||||
ui->tableWidget->currentItem()->setText("0" + ui->tableWidget->currentItem()->text());
|
||||
break;
|
||||
}
|
||||
QString fmt = "hh:mm:ss";
|
||||
QTime dt = QTime::fromString(ui->tableWidget->currentItem()->text());
|
||||
QString timeStr = dt.toString("hh:mm:ss");
|
||||
ui->tableWidget->currentItem()->setText(timeStr);
|
||||
}
|
||||
|
||||
if(row + 1 == ui->tableWidget->rowCount() && ui->tableWidget->currentItem()->text().length() )
|
||||
addEmptyRow();
|
||||
|
||||
QList<trainrow> rows;
|
||||
for(int i = 0; i < ui->tableWidget->rowCount(); i++)
|
||||
{
|
||||
if(!ui->tableWidget->item(i, 0)->text().contains("00:00:00"))
|
||||
{
|
||||
trainrow t;
|
||||
t.duration = QTime::fromString(ui->tableWidget->item(i, 0)->text(), "hh:mm:ss");
|
||||
t.speed = ui->tableWidget->item(i, 1)->text().toFloat();
|
||||
t.inclination = ui->tableWidget->item(i, 2)->text().toFloat();
|
||||
t.forcespeed = ui->tableWidget->item(i, 3)->checkState() == Qt::CheckState::Checked;
|
||||
rows.append(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
createTrainProgram(rows);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::trainProgramSignals()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
disconnect(trainProgram, SIGNAL(start()), bluetoothManager->device(), SLOT(start()));
|
||||
disconnect(trainProgram, SIGNAL(stop()), bluetoothManager->device(), SLOT(stop()));
|
||||
disconnect(trainProgram, SIGNAL(changeSpeed(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeed(double)));
|
||||
disconnect(trainProgram, SIGNAL(changeInclination(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeInclination(double)));
|
||||
disconnect(trainProgram, SIGNAL(changeSpeedAndInclination(double, double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeedAndInclination(double, double)));
|
||||
disconnect(trainProgram, SIGNAL(changeResistance(double)), ((bike*)bluetoothManager->device()), SLOT(changeResistance(double)));
|
||||
disconnect(((treadmill*)bluetoothManager->device()), SIGNAL(tapeStarted()), trainProgram, SLOT(onTapeStarted()));
|
||||
disconnect(((bike*)bluetoothManager->device()), SIGNAL(bikeStarted()), trainProgram, SLOT(onTapeStarted()));
|
||||
|
||||
connect(trainProgram, SIGNAL(start()), bluetoothManager->device(), SLOT(start()));
|
||||
connect(trainProgram, SIGNAL(stop()), bluetoothManager->device(), SLOT(stop()));
|
||||
connect(trainProgram, SIGNAL(changeSpeed(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeed(double)));
|
||||
connect(trainProgram, SIGNAL(changeInclination(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeInclination(double)));
|
||||
connect(trainProgram, SIGNAL(changeSpeedAndInclination(double, double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeedAndInclination(double, double)));
|
||||
connect(trainProgram, SIGNAL(changeResistance(double)), ((bike*)bluetoothManager->device()), SLOT(changeResistance(double)));
|
||||
connect(((treadmill*)bluetoothManager->device()), SIGNAL(tapeStarted()), trainProgram, SLOT(onTapeStarted()));
|
||||
connect(((bike*)bluetoothManager->device()), SIGNAL(bikeStarted()), trainProgram, SLOT(onTapeStarted()));
|
||||
|
||||
qDebug() << "trainProgram associated to a device";
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "trainProgram NOT associated to a device";
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::createTrainProgram(QList<trainrow> rows)
|
||||
{
|
||||
if(trainProgram) delete trainProgram;
|
||||
trainProgram = new trainprogram(rows, bluetoothManager);
|
||||
if(rows.length() == 0)
|
||||
addEmptyRow();
|
||||
trainProgramSignals();
|
||||
}
|
||||
|
||||
void MainWindow::on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
|
||||
{
|
||||
Q_UNUSED(current);
|
||||
Q_UNUSED(previous);
|
||||
}
|
||||
|
||||
void MainWindow::on_save_clicked()
|
||||
{
|
||||
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
|
||||
"train.xml",
|
||||
tr("Train Program (*.xml)"));
|
||||
if(!fileName.isEmpty() && trainProgram)
|
||||
trainProgram->save(fileName);
|
||||
}
|
||||
|
||||
void MainWindow::loadTrainProgram(QString fileName)
|
||||
{
|
||||
if(!fileName.isEmpty())
|
||||
{
|
||||
ui->difficulty->setValue(50);
|
||||
int rows = ui->tableWidget->rowCount();
|
||||
for(int i = 0; i<rows; i++)
|
||||
ui->tableWidget->removeRow(ui->tableWidget->rowCount() - 1);
|
||||
|
||||
if(fileName.endsWith("xml"))
|
||||
{
|
||||
if(trainProgram)
|
||||
delete trainProgram;
|
||||
trainProgram = trainprogram::load(fileName, bluetoothManager);
|
||||
}
|
||||
else if(fileName.endsWith("gpx"))
|
||||
{
|
||||
if(trainProgram)
|
||||
delete trainProgram;
|
||||
gpx g;
|
||||
QList<trainrow> list;
|
||||
foreach(gpx_altitude_point_for_treadmill p, g.open(fileName))
|
||||
{
|
||||
trainrow r;
|
||||
r.speed = p.speed;
|
||||
r.duration = QTime(0,0,0,0);
|
||||
r.duration = r.duration.addSecs(p.seconds);
|
||||
r.inclination = p.inclination;
|
||||
r.forcespeed = true;
|
||||
list.append(r);
|
||||
}
|
||||
trainProgram = new trainprogram(list, bluetoothManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
int countRow = 0;
|
||||
foreach(trainrow row, trainProgram->rows)
|
||||
{
|
||||
if(ui->tableWidget->rowCount() <= countRow)
|
||||
addEmptyRow();
|
||||
|
||||
QTableWidgetItem* i;
|
||||
editing = true;
|
||||
i = ui->tableWidget->takeItem(countRow, 0);
|
||||
i->setText(row.duration.toString("hh:mm:ss"));
|
||||
ui->tableWidget->setItem(countRow, 0, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 1);
|
||||
i->setText(QString::number(row.speed));
|
||||
ui->tableWidget->setItem(countRow, 1, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 2);
|
||||
i->setText(QString::number(row.inclination));
|
||||
ui->tableWidget->setItem(countRow, 2, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 3);
|
||||
i->setCheckState(row.forcespeed?Qt::CheckState::Checked:Qt::CheckState::Unchecked);
|
||||
ui->tableWidget->setItem(countRow, 3, i);
|
||||
|
||||
editing = false;
|
||||
|
||||
countRow++;
|
||||
}
|
||||
|
||||
trainProgramSignals();
|
||||
ui->groupTrain->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_load_clicked()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
|
||||
"train.xml",
|
||||
tr("Train Program (*.xml *.gpx)"));
|
||||
loadTrainProgram(fileName);
|
||||
}
|
||||
|
||||
void MainWindow::on_reset_clicked()
|
||||
{
|
||||
if(bluetoothManager->device() && bluetoothManager->device()->currentSpeed() > 0) return;
|
||||
|
||||
int countRow = 0;
|
||||
foreach(trainrow row, trainProgram->rows)
|
||||
{
|
||||
QTableWidgetItem* i;
|
||||
editing = true;
|
||||
i = ui->tableWidget->takeItem(countRow, 0);
|
||||
i->setText("00:00:00");
|
||||
ui->tableWidget->setItem(countRow, 0, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 1);
|
||||
i->setText("0");
|
||||
ui->tableWidget->setItem(countRow, 1, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 2);
|
||||
i->setText("0");
|
||||
ui->tableWidget->setItem(countRow, 2, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 3);
|
||||
i->setCheckState(row.forcespeed?Qt::CheckState::Checked:Qt::CheckState::Unchecked);
|
||||
ui->tableWidget->setItem(countRow, 3, i);
|
||||
|
||||
editing = false;
|
||||
|
||||
countRow++;
|
||||
}
|
||||
|
||||
createTrainProgram(QList<trainrow>());
|
||||
}
|
||||
|
||||
void MainWindow::on_stop_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->stop();
|
||||
}
|
||||
|
||||
void MainWindow::on_start_clicked()
|
||||
{
|
||||
trainProgram->restart();
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->start();
|
||||
}
|
||||
|
||||
void MainWindow::on_groupTrain_clicked()
|
||||
{
|
||||
if(!trainProgram)
|
||||
createTrainProgram(QList<trainrow>());
|
||||
trainProgram->enabled = ui->groupTrain->isChecked();
|
||||
}
|
||||
|
||||
void MainWindow::on_fanSpeedMinus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() - 1);
|
||||
}
|
||||
|
||||
void MainWindow::on_fanSpeedPlus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() + 1);
|
||||
}
|
||||
|
||||
void MainWindow::on_difficulty_valueChanged(int value)
|
||||
{
|
||||
if(editing) return;
|
||||
|
||||
for(int i=0;i<trainProgram->rows.count(); i++)
|
||||
{
|
||||
trainProgram->rows[i].speed = trainProgram->loadedRows[i].speed +
|
||||
(trainProgram->loadedRows[i].speed * (0.02 * (value - 50)));
|
||||
trainProgram->rows[i].inclination = trainProgram->loadedRows[i].inclination +
|
||||
(trainProgram->loadedRows[i].inclination * (0.02 * (value - 50)));
|
||||
}
|
||||
|
||||
int countRow = 0;
|
||||
foreach(trainrow row, trainProgram->rows)
|
||||
{
|
||||
QTableWidgetItem* i;
|
||||
editing = true;
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 1);
|
||||
i->setText(QString::number(row.speed));
|
||||
ui->tableWidget->setItem(countRow, 1, i);
|
||||
|
||||
i = ui->tableWidget->takeItem(countRow, 2);
|
||||
i->setText(QString::number(row.inclination));
|
||||
ui->tableWidget->setItem(countRow, 2, i);
|
||||
|
||||
editing = false;
|
||||
|
||||
countRow++;
|
||||
}
|
||||
ui->difficulty->setToolTip(QString::number(value) + "%");
|
||||
}
|
||||
|
||||
void MainWindow::on_speedMinus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() - 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_speedPlus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_inclinationMinus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() - 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_inclinationPlus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_resistanceMinus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_resistancePlus_clicked()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_chart_clicked()
|
||||
{
|
||||
|
||||
}
|
||||
63
src/mainwindow.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
#include <QTableWidgetItem>
|
||||
#include "trainprogram.h"
|
||||
#include "domyostreadmill.h"
|
||||
#include "sessionline.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QList<SessionLine> Session;
|
||||
explicit MainWindow(bluetooth* t);
|
||||
explicit MainWindow(bluetooth* t, QString trainProgram);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
void addEmptyRow();
|
||||
void load(bluetooth* device);
|
||||
void loadTrainProgram(QString fileName);
|
||||
void createTrainProgram(QList<trainrow> rows);
|
||||
bool editing = false;
|
||||
trainprogram* trainProgram = 0;
|
||||
|
||||
Ui::MainWindow *ui;
|
||||
QTimer *timer;
|
||||
|
||||
bluetooth* bluetoothManager;
|
||||
|
||||
private slots:
|
||||
void update();
|
||||
void on_tableWidget_cellChanged(int row, int column);
|
||||
void on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
|
||||
void on_save_clicked();
|
||||
void on_load_clicked();
|
||||
void on_reset_clicked();
|
||||
void on_stop_clicked();
|
||||
void on_start_clicked();
|
||||
void on_groupTrain_clicked();
|
||||
void on_fanSpeedMinus_clicked();
|
||||
void on_fanSpeedPlus_clicked();
|
||||
void on_difficulty_valueChanged(int value);
|
||||
void trainProgramSignals();
|
||||
void on_speedMinus_clicked();
|
||||
void on_speedPlus_clicked();
|
||||
void on_inclinationMinus_clicked();
|
||||
void on_inclinationPlus_clicked();
|
||||
void on_resistanceMinus_clicked();
|
||||
void on_resistancePlus_clicked();
|
||||
void on_chart_clicked();
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
1182
src/mainwindow.ui
Normal file
@@ -1,8 +1,7 @@
|
||||
QT -= gui
|
||||
QT += bluetooth
|
||||
QT += bluetooth widgets xml positioning charts quick
|
||||
unix:android: QT += androidextras
|
||||
|
||||
CONFIG += c++11 console
|
||||
CONFIG -= app_bundle
|
||||
CONFIG += c++11 console debug app_bundle
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any Qt feature that has been marked deprecated (the exact warnings
|
||||
@@ -16,9 +15,23 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += \
|
||||
bike.cpp \
|
||||
bluetooth.cpp \
|
||||
bluetoothdevice.cpp \
|
||||
charts.cpp \
|
||||
domyostreadmill.cpp \
|
||||
gpx.cpp \
|
||||
homeform.cpp \
|
||||
main.cpp \
|
||||
virtualtreadmill.cpp
|
||||
sessionline.cpp \
|
||||
toorxtreadmill.cpp \
|
||||
treadmill.cpp \
|
||||
mainwindow.cpp \
|
||||
trainprogram.cpp \
|
||||
trxappgateusbtreadmill.cpp \
|
||||
virtualbike.cpp \
|
||||
virtualtreadmill.cpp \
|
||||
domyosbike.cpp
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
@@ -26,5 +39,41 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
HEADERS += \
|
||||
bike.h \
|
||||
bluetooth.h \
|
||||
bluetoothdevice.h \
|
||||
charts.h \
|
||||
domyostreadmill.h \
|
||||
virtualtreadmill.h
|
||||
homeform.h \
|
||||
sessionline.h \
|
||||
toorxtreadmill.h \
|
||||
gpx.h \
|
||||
treadmill.h \
|
||||
mainwindow.h \
|
||||
trainprogram.h \
|
||||
trxappgateusbtreadmill.h \
|
||||
virtualbike.h \
|
||||
virtualtreadmill.h \
|
||||
domyosbike.h
|
||||
|
||||
FORMS += \
|
||||
charts.ui \
|
||||
mainwindow.ui
|
||||
|
||||
RESOURCES += \
|
||||
icons.qrc \
|
||||
qml.qrc
|
||||
|
||||
DISTFILES += \
|
||||
android/AndroidManifest.xml \
|
||||
android/build.gradle \
|
||||
android/gradle/wrapper/gradle-wrapper.jar \
|
||||
android/gradle/wrapper/gradle-wrapper.properties \
|
||||
android/gradlew \
|
||||
android/gradlew.bat \
|
||||
android/res/values/libs.xml \
|
||||
android/src/MyActivity.java
|
||||
|
||||
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
|
||||
|
||||
ANDROID_ABIS = armeabi-v7a
|
||||
|
||||
25
src/qdomyos-zwift.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.271
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qdomyos-zwift", "qdomyos-zwift.vcxproj", "{9D0092A7-A461-39F4-9B1F-4C5838A323A6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Debug|x64.Build.0 = Debug|x64
|
||||
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Release|x64.ActiveCfg = Release|x64
|
||||
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7EDA6C26-B7B8-4AFB-AD08-977E1DEF7E75}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
298
src/qdomyos-zwift.vcxproj
Normal file
@@ -0,0 +1,298 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{9D0092A7-A461-39F4-9B1F-4C5838A323A6}</ProjectGuid>
|
||||
<RootNamespace>qdomyos-zwift</RootNamespace>
|
||||
<Keyword>QtVS_v303</Keyword>
|
||||
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
|
||||
<QtMsBuild Condition="'$(QtMsBuild)'=='' or !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<OutputDirectory>debug\</OutputDirectory>
|
||||
<ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<IntermediateDirectory>debug\</IntermediateDirectory>
|
||||
<PrimaryOutput>qdomyos-zwift</PrimaryOutput>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<OutputDirectory>release\</OutputDirectory>
|
||||
<ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<IntermediateDirectory>release\</IntermediateDirectory>
|
||||
<PrimaryOutput>qdomyos-zwift</PrimaryOutput>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
|
||||
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
|
||||
</Target>
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
|
||||
<Import Project="$(QtMsBuild)\qt_defaults.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<OutDir>release\</OutDir>
|
||||
<IntDir>release\</IntDir>
|
||||
<TargetName>qdomyos-zwift</TargetName>
|
||||
<IgnoreImportLibrary>true</IgnoreImportLibrary>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<OutDir>debug\</OutDir>
|
||||
<IntDir>debug\</IntDir>
|
||||
<TargetName>qdomyos-zwift</TargetName>
|
||||
<IgnoreImportLibrary>true</IgnoreImportLibrary>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<QtInstall>msvc2015_64</QtInstall>
|
||||
<QtModules>core;xml;gui;widgets;bluetooth;positioning;charts</QtModules>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<QtInstall>msvc2015_64</QtInstall>
|
||||
<QtModules>core;xml;gui;widgets;bluetooth;positioning;charts</QtModules>
|
||||
</PropertyGroup>
|
||||
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.props')">
|
||||
<Import Project="$(QtMsBuild)\qt.props" />
|
||||
</ImportGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;debug;/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AssemblerListingLocation>debug\</AssemblerListingLocation>
|
||||
<BrowseInformation>false</BrowseInformation>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<ExceptionHandling>Sync</ExceptionHandling>
|
||||
<ObjectFileName>debug\</ObjectFileName>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessToFile>false</PreprocessToFile>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
|
||||
<DataExecutionPrevention>true</DataExecutionPrevention>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<IgnoreImportLibrary>true</IgnoreImportLibrary>
|
||||
<OutputFile>$(OutDir)\qdomyos-zwift.exe</OutputFile>
|
||||
<RandomizedBaseAddress>true</RandomizedBaseAddress>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Link>
|
||||
<Midl>
|
||||
<DefaultCharType>Unsigned</DefaultCharType>
|
||||
<EnableErrorChecks>None</EnableErrorChecks>
|
||||
<WarningLevel>0</WarningLevel>
|
||||
</Midl>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;QT_CHARTS_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_BLUETOOTH_LIB;QT_XML_LIB;QT_POSITIONING_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
<QtMoc>
|
||||
<CompilerFlavor>msvc</CompilerFlavor>
|
||||
<Include>./$(Configuration)/moc_predefs.h</Include>
|
||||
<ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
|
||||
<DynamicSource>output</DynamicSource>
|
||||
<QtMocDir>$(Configuration)</QtMocDir>
|
||||
<QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
|
||||
</QtMoc>
|
||||
<QtRcc>
|
||||
<InitFuncName>icons</InitFuncName>
|
||||
<Compression>default</Compression>
|
||||
<ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
|
||||
<QtRccDir>$(Configuration)</QtRccDir>
|
||||
<QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
|
||||
</QtRcc>
|
||||
<QtUic>
|
||||
<ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
|
||||
<QtUicDir>$(ProjectDir)</QtUicDir>
|
||||
<QtUicFileName>ui_%(Filename).h</QtUicFileName>
|
||||
</QtUic>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;release;/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AssemblerListingLocation>release\</AssemblerListingLocation>
|
||||
<BrowseInformation>false</BrowseInformation>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<ExceptionHandling>Sync</ExceptionHandling>
|
||||
<ObjectFileName>release\</ObjectFileName>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessToFile>false</PreprocessToFile>
|
||||
<ProgramDataBaseFileName>
|
||||
</ProgramDataBaseFileName>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
|
||||
<DataExecutionPrevention>true</DataExecutionPrevention>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<IgnoreImportLibrary>true</IgnoreImportLibrary>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<OutputFile>$(OutDir)\qdomyos-zwift.exe</OutputFile>
|
||||
<RandomizedBaseAddress>true</RandomizedBaseAddress>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Link>
|
||||
<Midl>
|
||||
<DefaultCharType>Unsigned</DefaultCharType>
|
||||
<EnableErrorChecks>None</EnableErrorChecks>
|
||||
<WarningLevel>0</WarningLevel>
|
||||
</Midl>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;NDEBUG;QT_NO_DEBUG;QT_CHARTS_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_BLUETOOTH_LIB;QT_XML_LIB;QT_POSITIONING_LIB;QT_CORE_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
<QtMoc>
|
||||
<CompilerFlavor>msvc</CompilerFlavor>
|
||||
<Include>./$(Configuration)/moc_predefs.h</Include>
|
||||
<ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
|
||||
<DynamicSource>output</DynamicSource>
|
||||
<QtMocDir>$(Configuration)</QtMocDir>
|
||||
<QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
|
||||
</QtMoc>
|
||||
<QtRcc>
|
||||
<InitFuncName>icons</InitFuncName>
|
||||
<Compression>default</Compression>
|
||||
<ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
|
||||
<QtRccDir>$(Configuration)</QtRccDir>
|
||||
<QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
|
||||
</QtRcc>
|
||||
<QtUic>
|
||||
<ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
|
||||
<QtUicDir>$(ProjectDir)</QtUicDir>
|
||||
<QtUicFileName>ui_%(Filename).h</QtUicFileName>
|
||||
</QtUic>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="bike.cpp" />
|
||||
<ClCompile Include="bluetooth.cpp" />
|
||||
<ClCompile Include="bluetoothdevice.cpp" />
|
||||
<ClCompile Include="charts.cpp" />
|
||||
<ClCompile Include="domyosbike.cpp" />
|
||||
<ClCompile Include="domyostreadmill.cpp" />
|
||||
<ClCompile Include="gpx.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="mainwindow.cpp" />
|
||||
<ClCompile Include="toorxtreadmill.cpp" />
|
||||
<ClCompile Include="trainprogram.cpp" />
|
||||
<ClCompile Include="treadmill.cpp" />
|
||||
<ClCompile Include="trxappgateusbtreadmill.cpp" />
|
||||
<ClCompile Include="virtualbike.cpp" />
|
||||
<ClCompile Include="virtualtreadmill.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtMoc Include="bike.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="bluetooth.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="bluetoothdevice.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="charts.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="domyosbike.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="domyostreadmill.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="gpx.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="mainwindow.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="toorxtreadmill.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="trainprogram.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="treadmill.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="trxappgateusbtreadmill.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="virtualbike.h">
|
||||
</QtMoc>
|
||||
<QtMoc Include="virtualtreadmill.h">
|
||||
</QtMoc>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="debug\moc_predefs.h.cbt">
|
||||
<FileType>Document</FileType>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2>NUL >debug\moc_predefs.h</Command>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Generate moc_predefs.h</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">debug\moc_predefs.h;%(Outputs)</Outputs>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="release\moc_predefs.h.cbt">
|
||||
<FileType>Document</FileType>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2>NUL >release\moc_predefs.h</Command>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Generate moc_predefs.h</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">release\moc_predefs.h;%(Outputs)</Outputs>
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtUic Include="charts.ui">
|
||||
</QtUic>
|
||||
<QtUic Include="mainwindow.ui">
|
||||
</QtUic>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="icons\bluetooth-icon.png" />
|
||||
<None Include="icons\cadence.png" />
|
||||
<None Include="icons\elevationgain.png" />
|
||||
<None Include="icons\fan.png" />
|
||||
<None Include="icons\heart_red.png" />
|
||||
<QtRcc Include="icons.qrc">
|
||||
</QtRcc>
|
||||
<None Include="icons\inclination.png" />
|
||||
<None Include="icons\kcal.png" />
|
||||
<None Include="icons\odometer.png" />
|
||||
<None Include="icons\resistance.png" />
|
||||
<None Include="icons\speed.png" />
|
||||
<None Include="icons\start.png" />
|
||||
<None Include="icons\stop.png" />
|
||||
<None Include="icons\watt.png" />
|
||||
<None Include="icons\weight.png" />
|
||||
<None Include="icons\zwift-on.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
|
||||
<Import Project="$(QtMsBuild)\qt.targets" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
||||
10
src/qml.qrc
Normal file
@@ -0,0 +1,10 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>HomeForm.ui.qml</file>
|
||||
<file>main.qml</file>
|
||||
<file>Page1Form.ui.qml</file>
|
||||
<file>Page2Form.ui.qml</file>
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file>Home.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
14
src/qtquickcontrols2.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
; This file can be edited to change the style of the application
|
||||
; Read "Qt Quick Controls 2 Configuration File" for details:
|
||||
; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html
|
||||
|
||||
[Controls]
|
||||
Style=Material
|
||||
|
||||
[Universal]
|
||||
Theme=System
|
||||
Accent=Red
|
||||
|
||||
[Material]
|
||||
Theme=Dark
|
||||
Primary=BlueGrey
|
||||
15
src/sessionline.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "sessionline.h"
|
||||
|
||||
SessionLine::SessionLine(double speed, int8_t inclination, double distance, uint8_t watt, int8_t resistance, uint8_t heart, double pace, QTime time)
|
||||
{
|
||||
this->speed = speed;
|
||||
this->inclination = inclination;
|
||||
this->distance = distance;
|
||||
this->watt = watt;
|
||||
this->resistance = resistance;
|
||||
this->heart = heart;
|
||||
this->pace = pace;
|
||||
this->time = time;
|
||||
}
|
||||
|
||||
SessionLine::SessionLine() {}
|
||||
22
src/sessionline.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SESSIONLINE_H
|
||||
#define SESSIONLINE_H
|
||||
#include <QTimer>
|
||||
#include <QTime>
|
||||
|
||||
class SessionLine
|
||||
{
|
||||
public:
|
||||
double speed;
|
||||
int8_t inclination;
|
||||
double distance;
|
||||
uint8_t watt;
|
||||
int8_t resistance;
|
||||
uint8_t heart;
|
||||
double pace;
|
||||
QTime time;
|
||||
|
||||
SessionLine();
|
||||
SessionLine(double speed, int8_t inclination, double distance, uint8_t watt, int8_t resistance, uint8_t heart, double pace, QTime time = QTime::currentTime());
|
||||
};
|
||||
|
||||
#endif // SESSIONLINE_H
|
||||
73
src/test/test-bike/.gitignore
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
8
src/test/test-bike/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
23
src/test/test-bike/test-bike.pro
Normal file
@@ -0,0 +1,23 @@
|
||||
QT -= gui
|
||||
|
||||
CONFIG += c++11 console
|
||||
CONFIG -= app_bundle
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any Qt feature that has been marked deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
# You can also make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += \
|
||||
main.cpp
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
137
src/toorxtreadmill.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "toorxtreadmill.h"
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
toorxtreadmill::toorxtreadmill()
|
||||
{
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
refresh->start(200);
|
||||
}
|
||||
|
||||
void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
{
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("TRX ROUTE KEY"))
|
||||
{
|
||||
bttreadmill = device;
|
||||
|
||||
// Create a discovery agent and connect to its signals
|
||||
discoveryAgent = new QBluetoothServiceDiscoveryAgent(this);
|
||||
connect(discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)),
|
||||
this, SLOT(serviceDiscovered(QBluetoothServiceInfo)));
|
||||
|
||||
// Start a discovery
|
||||
discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// In your local slot, read information about the found devices
|
||||
void toorxtreadmill::serviceDiscovered(const QBluetoothServiceInfo &service)
|
||||
{
|
||||
if(service.device().address() == bttreadmill.address())
|
||||
{
|
||||
debug("Found new service: " + service.serviceName()
|
||||
+ '(' + service.serviceUuid().toString() + ')');
|
||||
|
||||
if(service.serviceName().contains("SerialPort"))
|
||||
{
|
||||
debug("Serial port service found");
|
||||
discoveryAgent->stop();
|
||||
|
||||
serialPortService = service;
|
||||
socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
|
||||
|
||||
connect(socket, &QBluetoothSocket::readyRead, this, &toorxtreadmill::readSocket);
|
||||
connect(socket, &QBluetoothSocket::connected, this, QOverload<>::of(&toorxtreadmill::rfCommConnected));
|
||||
connect(socket, &QBluetoothSocket::disconnected, this, &toorxtreadmill::disconnected);
|
||||
connect(socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error),
|
||||
this, &toorxtreadmill::onSocketErrorOccurred);
|
||||
|
||||
debug("Create socket");
|
||||
socket->connectToService(serialPortService);
|
||||
debug("ConnectToService done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void toorxtreadmill::update()
|
||||
{
|
||||
if(initDone)
|
||||
{
|
||||
const char poll[] = {0x55, 0x17, 0x01, 0x01, 0x53};
|
||||
socket->write(poll, sizeof(poll));
|
||||
debug("write poll");
|
||||
}
|
||||
}
|
||||
|
||||
void toorxtreadmill::rfCommConnected()
|
||||
{
|
||||
debug("connected " + socket->peerName());
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void toorxtreadmill::readSocket()
|
||||
{
|
||||
if (!socket)
|
||||
return;
|
||||
|
||||
while (socket->canReadLine()) {
|
||||
QByteArray line = socket->readLine();
|
||||
debug(socket->peerName() +
|
||||
QString::fromUtf8(line.constData(), line.length()));
|
||||
|
||||
if(line.length() == 17)
|
||||
{
|
||||
elapsed = GetElapsedTimeFromPacket(line);
|
||||
Distance = GetDistanceFromPacket(line);
|
||||
KCal = GetCaloriesFromPacket(line);
|
||||
Speed = GetSpeedFromPacket(line);
|
||||
Inclination = GetInclinationFromPacket(line);
|
||||
Heart = GetHeartRateFromPacket(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t toorxtreadmill::GetHeartRateFromPacket(QByteArray packet)
|
||||
{
|
||||
return packet.at(16);
|
||||
}
|
||||
|
||||
uint8_t toorxtreadmill::GetInclinationFromPacket(QByteArray packet)
|
||||
{
|
||||
return packet.at(15);
|
||||
}
|
||||
|
||||
double toorxtreadmill::GetSpeedFromPacket(QByteArray packet)
|
||||
{
|
||||
double convertedData = (double)(packet.at(13) << 8) + ((double)packet.at(14) / 100.0);
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
uint16_t toorxtreadmill::GetCaloriesFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(11) << 8) | packet.at(12);
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
|
||||
uint16_t toorxtreadmill::GetDistanceFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(9) << 8) | packet.at(10);
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
|
||||
uint16_t toorxtreadmill::GetElapsedTimeFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
void toorxtreadmill::onSocketErrorOccurred(QBluetoothSocket::SocketError error)
|
||||
{
|
||||
debug("onSocketErrorOccurred " + QString::number(error));
|
||||
}
|
||||
70
src/toorxtreadmill.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef TOORX_H
|
||||
#define TOORX_H
|
||||
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QBluetoothServiceDiscoveryAgent>
|
||||
#include <QBluetoothSocket>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "virtualtreadmill.h"
|
||||
#include "treadmill.h"
|
||||
|
||||
class toorxtreadmill : public treadmill
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit toorxtreadmill();
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
void serviceDiscovered(const QBluetoothServiceInfo &service);
|
||||
void readSocket();
|
||||
void rfCommConnected();
|
||||
void onSocketErrorOccurred(QBluetoothSocket::SocketError);
|
||||
void update();
|
||||
|
||||
private:
|
||||
QBluetoothDeviceInfo bttreadmill;
|
||||
QBluetoothServiceDiscoveryAgent *discoveryAgent;
|
||||
QBluetoothServiceInfo serialPortService;
|
||||
QBluetoothSocket *socket = nullptr;
|
||||
|
||||
QTimer* refresh;
|
||||
bool initDone = false;
|
||||
|
||||
uint16_t GetElapsedTimeFromPacket(QByteArray packet);
|
||||
uint16_t GetDistanceFromPacket(QByteArray packet);
|
||||
uint16_t GetCaloriesFromPacket(QByteArray packet);
|
||||
double GetSpeedFromPacket(QByteArray packet);
|
||||
uint8_t GetInclinationFromPacket(QByteArray packet);
|
||||
uint8_t GetHeartRateFromPacket(QByteArray packet);
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
};
|
||||
|
||||
#endif // TOORX_H
|
||||
177
src/trainprogram.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "trainprogram.h"
|
||||
#include <QFile>
|
||||
#include <QtXml/QtXml>
|
||||
|
||||
trainprogram::trainprogram(QList<trainrow> rows, bluetooth* b)
|
||||
{
|
||||
this->bluetoothManager = b;
|
||||
this->rows = rows;
|
||||
this->loadedRows = rows;
|
||||
connect(&timer, SIGNAL(timeout()), this, SLOT(scheduler()));
|
||||
timer.setInterval(1000);
|
||||
timer.start();
|
||||
}
|
||||
|
||||
void trainprogram::scheduler()
|
||||
{
|
||||
if(
|
||||
rows.count() == 0 ||
|
||||
started == false ||
|
||||
enabled == false ||
|
||||
bluetoothManager->device() == nullptr ||
|
||||
bluetoothManager->device()->currentSpeed() <= 0
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ticks++;
|
||||
elapsed = ticks;
|
||||
ticksCurrentRow++;
|
||||
elapsedCurrentRow = ticksCurrentRow;
|
||||
|
||||
// entry point
|
||||
if(ticks == 1 && currentStep == 0)
|
||||
{
|
||||
if(rows[0].forcespeed && rows[0].speed)
|
||||
{
|
||||
qDebug() << "trainprogram change speed" + QString::number(rows[0].speed);
|
||||
emit changeSpeedAndInclination(rows[0].speed, rows[0].inclination);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "trainprogram change inclination" + QString::number(rows[0].inclination);
|
||||
emit changeInclination(rows[0].inclination);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t currentRowLen = rows[currentStep].duration.second() +
|
||||
(rows[currentStep].duration.minute() * 60) +
|
||||
(rows[currentStep].duration.hour() * 3600);
|
||||
|
||||
uint32_t nextRowLen = 0;
|
||||
|
||||
if(rows.count() > currentStep + 1)
|
||||
nextRowLen = rows[currentStep + 1].duration.second() +
|
||||
(rows[currentStep + 1].duration.minute() * 60) +
|
||||
(rows[currentStep + 1].duration.hour() * 3600);
|
||||
|
||||
qDebug() << "trainprogram elapsed current row" + QString::number(elapsedCurrentRow) + "current row len" + QString::number(currentRowLen);
|
||||
|
||||
if(elapsedCurrentRow >= currentRowLen && currentRowLen)
|
||||
{
|
||||
if(nextRowLen)
|
||||
{
|
||||
currentStep++;
|
||||
ticksCurrentRow = 0;
|
||||
elapsedCurrentRow = 0;
|
||||
if(rows[currentStep].forcespeed && rows[currentStep].speed)
|
||||
{
|
||||
qDebug() << "trainprogram change speed" + QString::number(rows[currentStep].speed);
|
||||
emit changeSpeedAndInclination(rows[currentStep].speed, rows[currentStep].inclination);
|
||||
}
|
||||
qDebug() << "trainprogram change inclination" + QString::number(rows[currentStep].inclination);
|
||||
emit changeInclination(rows[currentStep].inclination);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "trainprogram ends!";
|
||||
started = false;
|
||||
emit stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void trainprogram::onTapeStarted()
|
||||
{
|
||||
started = true;
|
||||
}
|
||||
|
||||
void trainprogram::restart()
|
||||
{
|
||||
ticks = 0;
|
||||
ticksCurrentRow = 0;
|
||||
elapsed = 0;
|
||||
elapsedCurrentRow = 0;
|
||||
currentStep = 0;
|
||||
started = true;
|
||||
}
|
||||
|
||||
void trainprogram::save(QString filename)
|
||||
{
|
||||
QFile output(filename);
|
||||
output.open(QIODevice::WriteOnly);
|
||||
QXmlStreamWriter stream(&output);
|
||||
stream.setAutoFormatting(true);
|
||||
stream.writeStartDocument();
|
||||
stream.writeStartElement("rows");
|
||||
foreach (trainrow row, rows) {
|
||||
stream.writeStartElement("row");
|
||||
stream.writeAttribute("duration", row.duration.toString());
|
||||
stream.writeAttribute("speed", QString::number(row.speed));
|
||||
stream.writeAttribute("inclination", QString::number(row.inclination));
|
||||
stream.writeAttribute("forcespeed", row.forcespeed?"1":"0");
|
||||
stream.writeEndElement();
|
||||
}
|
||||
stream.writeEndElement();
|
||||
stream.writeEndDocument();
|
||||
}
|
||||
|
||||
trainprogram* trainprogram::load(QString filename, bluetooth* b)
|
||||
{
|
||||
QList<trainrow> list;
|
||||
QFile input(filename);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
QXmlStreamReader stream(&input);
|
||||
while(!stream.atEnd())
|
||||
{
|
||||
stream.readNext();
|
||||
trainrow row;
|
||||
QXmlStreamAttributes atts = stream.attributes();
|
||||
if(atts.length())
|
||||
{
|
||||
row.duration = QTime::fromString(atts.value("duration").toString(), "hh:mm:ss");
|
||||
row.speed = atts.value("speed").toDouble();
|
||||
row.inclination = atts.value("inclination").toDouble();
|
||||
row.forcespeed = atts.value("forcespeed").toInt()?true:false ;
|
||||
list.append(row);
|
||||
}
|
||||
}
|
||||
trainprogram *tr = new trainprogram(list, b);
|
||||
return tr;
|
||||
}
|
||||
|
||||
QTime trainprogram::totalElapsedTime()
|
||||
{
|
||||
return QTime(0,0,elapsed);
|
||||
}
|
||||
|
||||
QTime trainprogram::currentRowElapsedTime()
|
||||
{
|
||||
return QTime(0,0,elapsedCurrentRow);
|
||||
}
|
||||
|
||||
QTime trainprogram::duration()
|
||||
{
|
||||
QTime total(0,0,0,0);
|
||||
foreach (trainrow row, rows) {
|
||||
total = total.addSecs((row.duration.hour() * 3600) + (row.duration.minute() * 60) + row.duration.second());
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
double trainprogram::totalDistance()
|
||||
{
|
||||
double distance = 0;
|
||||
foreach (trainrow row, rows) {
|
||||
if(row.duration.hour() || row.duration.minute() || row.duration.second())
|
||||
{
|
||||
if(!row.forcespeed)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
distance += ((row.duration.hour() * 3600) + (row.duration.minute() * 60) + row.duration.second()) * (row.speed / 3600);
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
59
src/trainprogram.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef TRAINPROGRAM_H
|
||||
#define TRAINPROGRAM_H
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include "bluetooth.h"
|
||||
|
||||
class trainrow
|
||||
{
|
||||
public:
|
||||
QTime duration;
|
||||
double speed;
|
||||
double inclination;
|
||||
bool forcespeed;
|
||||
};
|
||||
|
||||
class trainprogram: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
trainprogram(QList<trainrow>, bluetooth* b);
|
||||
void save(QString filename);
|
||||
static trainprogram* load(QString filename, bluetooth* b);
|
||||
QTime totalElapsedTime();
|
||||
QTime currentRowElapsedTime();
|
||||
QTime duration();
|
||||
double totalDistance();
|
||||
|
||||
QList<trainrow> rows;
|
||||
QList<trainrow> loadedRows; // rows as loaded
|
||||
uint32_t elapsed = 0;
|
||||
bool enabled = true;
|
||||
|
||||
void restart();
|
||||
void scheduler(int tick);
|
||||
|
||||
public slots:
|
||||
void onTapeStarted();
|
||||
void scheduler();
|
||||
|
||||
signals:
|
||||
void start();
|
||||
void stop();
|
||||
void changeSpeed(double speed);
|
||||
void changeInclination(double inclination);
|
||||
void changeSpeedAndInclination(double speed, double inclination);
|
||||
|
||||
private:
|
||||
bluetooth* bluetoothManager;
|
||||
bool started = false;
|
||||
uint32_t ticks = 0;
|
||||
uint16_t currentStep = 0;
|
||||
uint32_t ticksCurrentRow = 0;
|
||||
uint32_t elapsedCurrentRow = 0;
|
||||
QTimer timer;
|
||||
};
|
||||
|
||||
#endif // TRAINPROGRAM_H
|
||||
32
src/treadmill.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "treadmill.h"
|
||||
|
||||
treadmill::treadmill()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void treadmill::changeSpeed(double speed){ requestSpeed = speed;}
|
||||
void treadmill::changeInclination(double inclination){ requestInclination = inclination; }
|
||||
void treadmill::changeSpeedAndInclination(double speed, double inclination){ requestSpeed = speed; requestInclination = inclination;}
|
||||
double treadmill::currentInclination(){ return Inclination; }
|
||||
double treadmill::elevationGain(){ return elevationAcc; }
|
||||
uint8_t treadmill::fanSpeed() { return FanSpeed; };
|
||||
bool treadmill::connected() { return false; }
|
||||
bluetoothdevice::BLUETOOTH_TYPE treadmill::deviceType() { return bluetoothdevice::TREADMILL; }
|
||||
|
||||
uint16_t treadmill::watts(double weight)
|
||||
{
|
||||
// calc Watts ref. https://alancouzens.com/blog/Run_Power.html
|
||||
|
||||
uint16_t watts=0;
|
||||
if(currentSpeed() > 0)
|
||||
{
|
||||
double pace=60/currentSpeed();
|
||||
double VO2R=210.0/pace;
|
||||
double VO2A=(VO2R*weight)/1000.0;
|
||||
double hwatts=75*VO2A;
|
||||
double vwatts=((9.8*weight) * (currentInclination()/100));
|
||||
watts=hwatts+vwatts;
|
||||
}
|
||||
return watts;
|
||||
}
|
||||
34
src/treadmill.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef TREADMILL_H
|
||||
#define TREADMILL_H
|
||||
#include <QObject>
|
||||
#include "bluetoothdevice.h"
|
||||
|
||||
class treadmill:public bluetoothdevice
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
treadmill();
|
||||
virtual double currentInclination();
|
||||
virtual double elevationGain();
|
||||
virtual uint8_t fanSpeed();
|
||||
virtual bool connected();
|
||||
uint16_t watts(double weight=75.0);
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType();
|
||||
|
||||
public slots:
|
||||
virtual void changeSpeed(double speed);
|
||||
virtual void changeInclination(double inclination);
|
||||
virtual void changeSpeedAndInclination(double speed, double inclination);
|
||||
|
||||
signals:
|
||||
void tapeStarted();
|
||||
|
||||
protected:
|
||||
double elevationAcc = 0;
|
||||
double Inclination = 0;
|
||||
double requestSpeed = -1;
|
||||
double requestInclination = -1;
|
||||
};
|
||||
|
||||
#endif // TREADMILL_H
|
||||
398
src/trxappgateusbtreadmill.cpp
Normal file
@@ -0,0 +1,398 @@
|
||||
#include "trxappgateusbtreadmill.h"
|
||||
#include "virtualtreadmill.h"
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
trxappgateusbtreadmill::trxappgateusbtreadmill()
|
||||
{
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
refresh->start(200);
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log)
|
||||
{
|
||||
QEventLoop loop;
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
|
||||
|
||||
if(!disable_log)
|
||||
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline)
|
||||
{
|
||||
Q_UNUSED(requestSpeed);
|
||||
Q_UNUSED(requestIncline);
|
||||
}
|
||||
|
||||
bool trxappgateusbtreadmill::changeFanSpeed(uint8_t speed)
|
||||
{
|
||||
Q_UNUSED(speed);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void trxappgateusbtreadmill::update()
|
||||
{
|
||||
static uint8_t sec1 = 0;
|
||||
static QTime lastTime;
|
||||
static bool first = true;
|
||||
//qDebug() << treadmill.isValid() << m_control->state() << gattCommunicationChannelService << gattWriteCharacteristic.isValid() << gattNotifyCharacteristic.isValid() << initDone;
|
||||
|
||||
if(m_control->state() == QLowEnergyController::UnconnectedState)
|
||||
{
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if(initRequest)
|
||||
{
|
||||
initRequest = false;
|
||||
btinit(false);
|
||||
}
|
||||
else if(bttreadmill.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
gattNotifyCharacteristic.isValid() &&
|
||||
initDone)
|
||||
{
|
||||
if(currentSpeed() > 0.0 && !first)
|
||||
elapsed += ((double)lastTime.msecsTo(QTime::currentTime()) / 1000.0);
|
||||
|
||||
// updating the treadmill console every second
|
||||
if(sec1++ == (1000 / refresh->interval()))
|
||||
{
|
||||
sec1 = 0;
|
||||
//updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
const uint8_t noOpData[] = { 0xf0, 0xa2, 0x01, 0xd3, 0x66 };
|
||||
writeCharacteristic((uint8_t*)noOpData, sizeof(noOpData), "noOp", true);
|
||||
|
||||
if(requestSpeed != -1)
|
||||
{
|
||||
if(requestSpeed != currentSpeed())
|
||||
{
|
||||
debug("writing speed " + QString::number(requestSpeed));
|
||||
double inc = Inclination;
|
||||
if(requestInclination != -1)
|
||||
{
|
||||
inc = requestInclination;
|
||||
requestInclination = -1;
|
||||
}
|
||||
forceSpeedOrIncline(requestSpeed, inc);
|
||||
}
|
||||
requestSpeed = -1;
|
||||
}
|
||||
if(requestInclination != -1)
|
||||
{
|
||||
if(requestInclination != currentInclination())
|
||||
{
|
||||
debug("writing incline " + QString::number(requestInclination));
|
||||
double speed = currentSpeed();
|
||||
if(requestSpeed != -1)
|
||||
{
|
||||
speed = requestSpeed;
|
||||
requestSpeed = -1;
|
||||
}
|
||||
forceSpeedOrIncline(speed, requestInclination);
|
||||
}
|
||||
requestInclination = -1;
|
||||
}
|
||||
if(requestStart != -1)
|
||||
{
|
||||
debug("starting...");
|
||||
btinit(true);
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if(requestStop != -1)
|
||||
{
|
||||
debug("stopping...");
|
||||
//writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
if(requestIncreaseFan != -1)
|
||||
{
|
||||
debug("increasing fan speed...");
|
||||
changeFanSpeed(FanSpeed + 1);
|
||||
requestIncreaseFan = -1;
|
||||
}
|
||||
else if(requestDecreaseFan != -1)
|
||||
{
|
||||
debug("decreasing fan speed...");
|
||||
changeFanSpeed(FanSpeed - 1);
|
||||
requestDecreaseFan = -1;
|
||||
}
|
||||
|
||||
elevationAcc += (currentSpeed() / 3600.0) * 1000 * (currentInclination() / 100) * (refresh->interval() / 1000);
|
||||
}
|
||||
|
||||
lastTime = QTime::currentTime();
|
||||
first = false;
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::serviceDiscovered(const QBluetoothUuid &gatt)
|
||||
{
|
||||
debug("serviceDiscovered " + gatt.toString());
|
||||
}
|
||||
|
||||
static QByteArray lastPacket;
|
||||
void trxappgateusbtreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
static QTime lastTime;
|
||||
static bool first = true;
|
||||
|
||||
debug(" << " + newValue.toHex(' '));
|
||||
|
||||
if (lastPacket.length() && lastPacket == newValue)
|
||||
return;
|
||||
|
||||
lastPacket = newValue;
|
||||
if (newValue.length() != 19)
|
||||
return;
|
||||
|
||||
double speed = GetSpeedFromPacket(newValue);
|
||||
double incline = GetInclinationFromPacket(newValue);
|
||||
double kcal = GetKcalFromPacket(newValue);
|
||||
double distance = GetDistanceFromPacket(newValue);
|
||||
|
||||
Heart = 0;
|
||||
FanSpeed = 0;
|
||||
|
||||
if(!first)
|
||||
DistanceCalculated += ((speed / 3600.0) / ( 1000.0 / (lastTime.msecsTo(QTime::currentTime()))));
|
||||
|
||||
debug("Current speed: " + QString::number(speed));
|
||||
debug("Current incline: " + QString::number(incline));
|
||||
debug("Current heart: " + QString::number(Heart));
|
||||
debug("Current KCal: " + QString::number(kcal));
|
||||
debug("Current Distance: " + QString::number(distance));
|
||||
debug("Current Elapsed from the treadmill (not used): " + QString::number(GetElapsedFromPacket(newValue)));
|
||||
debug("Current Distance Calculated: " + QString::number(DistanceCalculated));
|
||||
|
||||
if(m_control->error() != QLowEnergyController::NoError)
|
||||
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
|
||||
|
||||
Speed = speed;
|
||||
Inclination = incline;
|
||||
KCal = kcal;
|
||||
Distance = distance;
|
||||
|
||||
lastTime = QTime::currentTime();
|
||||
first = false;
|
||||
}
|
||||
|
||||
uint16_t trxappgateusbtreadmill::GetElapsedFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(4) - 1);
|
||||
convertedData += ((packet.at(5) - 1) * 60);
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
double trxappgateusbtreadmill::GetSpeedFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(13) - 1) + ((packet.at(12) - 1) * 100);
|
||||
double data = (double)(convertedData) / 10.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
double trxappgateusbtreadmill::GetKcalFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = ((packet.at(8) - 1) << 8) | (packet.at(9) - 1);
|
||||
return (double)(convertedData);
|
||||
}
|
||||
|
||||
double trxappgateusbtreadmill::GetDistanceFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = ((packet.at(6) - 1) << 8) | (packet.at(7) - 1);
|
||||
double data = ((double)(convertedData)) / 100.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
double trxappgateusbtreadmill::GetInclinationFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = packet.at(14);
|
||||
double data = (convertedData - 1);
|
||||
if (data < 0) return 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::btinit(bool startTape)
|
||||
{
|
||||
Q_UNUSED(startTape);
|
||||
|
||||
const uint8_t initData1[] = { 0xf0, 0xa0, 0x01, 0x01, 0x92 };
|
||||
const uint8_t initData2[] = { 0xf0, 0xa5, 0x01, 0xd3, 0x04, 0x6d };
|
||||
const uint8_t initData3[] = { 0xf0, 0xa0, 0x01, 0xd3, 0x64 };
|
||||
const uint8_t initData4[] = { 0xf0, 0xa1, 0x01, 0xd3, 0x65 };
|
||||
const uint8_t initData5[] = { 0xf0, 0xa3, 0x01, 0xd3, 0x01, 0x15, 0x01, 0x02, 0x51, 0x01, 0x51, 0x23 };
|
||||
const uint8_t initData6[] = { 0xf0, 0xa4, 0x01, 0xd3, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x73 };
|
||||
const uint8_t initData7[] = { 0xf0, 0xaf, 0x01, 0xd3, 0x02, 0x75 };
|
||||
|
||||
writeCharacteristic((uint8_t*)initData1, sizeof(initData1), "init");
|
||||
writeCharacteristic((uint8_t*)initData2, sizeof(initData2), "init");
|
||||
writeCharacteristic((uint8_t*)initData3, sizeof(initData3), "init");
|
||||
writeCharacteristic((uint8_t*)initData4, sizeof(initData4), "init");
|
||||
writeCharacteristic((uint8_t*)initData3, sizeof(initData3), "init");
|
||||
writeCharacteristic((uint8_t*)initData5, sizeof(initData5), "init");
|
||||
writeCharacteristic((uint8_t*)initData6, sizeof(initData6), "init");
|
||||
writeCharacteristic((uint8_t*)initData7, sizeof(initData7), "init");
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::stateChanged(QLowEnergyService::ServiceState state)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
if(state == QLowEnergyService::ServiceDiscovered)
|
||||
{
|
||||
foreach(QLowEnergyCharacteristic c,gattCommunicationChannelService->characteristics())
|
||||
{
|
||||
debug("characteristic " + c.uuid().toString());
|
||||
}
|
||||
|
||||
QBluetoothUuid _gattWriteCharacteristicId((QString)"0000fff2-0000-1000-8000-00805f9b34fb");
|
||||
QBluetoothUuid _gattNotifyCharacteristicId((QString)"0000fff1-0000-1000-8000-00805f9b34fb");
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotifyCharacteristic.isValid());
|
||||
|
||||
// establish hook into notifications
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
|
||||
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
|
||||
this, SLOT(errorService(QLowEnergyService::ServiceError)));
|
||||
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
|
||||
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
|
||||
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
static uint8_t first = 0;
|
||||
if(!first)
|
||||
{
|
||||
debug("creating virtual treadmill interface...");
|
||||
virtualTreadMill = new virtualtreadmill(this, false);
|
||||
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&trxappgateusbtreadmill::debug);
|
||||
}
|
||||
first = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
|
||||
{
|
||||
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
Q_UNUSED(characteristic);
|
||||
debug("characteristicWritten " + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::serviceScanDone(void)
|
||||
{
|
||||
debug("serviceScanDone");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((QString)"0000fff0-0000-1000-8000-00805f9b34fb");
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::errorService(QLowEnergyService::ServiceError err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
debug("trxappgateusbtreadmill::errorService" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::error(QLowEnergyController::Error err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
debug("trxappgateusbtreadmill::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
m_control->disconnect();
|
||||
}
|
||||
|
||||
void trxappgateusbtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
{
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("TOORX"))
|
||||
{
|
||||
bttreadmill = device;
|
||||
m_control = QLowEnergyController::createCentral(bttreadmill, this);
|
||||
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
|
||||
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
|
||||
connect(m_control, SIGNAL(discoveryFinished()),
|
||||
this, SLOT(serviceScanDone()));
|
||||
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
|
||||
this, SLOT(error(QLowEnergyController::Error)));
|
||||
|
||||
connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
debug("Cannot connect to remote device.");
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
debug("Controller connected. Search services...");
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
debug("LowEnergy controller disconnected");
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool trxappgateusbtreadmill::connected()
|
||||
{
|
||||
if(!m_control)
|
||||
return false;
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void* trxappgateusbtreadmill::VirtualTreadMill()
|
||||
{
|
||||
return virtualTreadMill;
|
||||
}
|
||||
|
||||
void* trxappgateusbtreadmill::VirtualDevice()
|
||||
{
|
||||
return VirtualTreadMill();
|
||||
}
|
||||
|
||||
double trxappgateusbtreadmill::odometer()
|
||||
{
|
||||
return DistanceCalculated;
|
||||
}
|
||||
89
src/trxappgateusbtreadmill.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef TRXAPPGATEUSBTREADMILL_H
|
||||
#define TRXAPPGATEUSBTREADMILL_H
|
||||
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "virtualtreadmill.h"
|
||||
#include "treadmill.h"
|
||||
|
||||
class trxappgateusbtreadmill : public treadmill
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
trxappgateusbtreadmill();
|
||||
bool connected();
|
||||
bool changeFanSpeed(uint8_t speed);
|
||||
double odometer();
|
||||
|
||||
void* VirtualTreadMill();
|
||||
void* VirtualDevice();
|
||||
|
||||
private:
|
||||
double GetSpeedFromPacket(QByteArray packet);
|
||||
double GetInclinationFromPacket(QByteArray packet);
|
||||
double GetKcalFromPacket(QByteArray packet);
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
uint16_t GetElapsedFromPacket(QByteArray packet);
|
||||
void forceSpeedOrIncline(double requestSpeed, double requestIncline);
|
||||
void updateDisplay(uint16_t elapsed);
|
||||
void btinit(bool startTape);
|
||||
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log=false);
|
||||
void startDiscover();
|
||||
double DistanceCalculated = 0;
|
||||
|
||||
QTimer* refresh;
|
||||
virtualtreadmill* virtualTreadMill = 0;
|
||||
|
||||
QBluetoothDeviceInfo bttreadmill;
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotifyCharacteristic;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void stateChanged(QLowEnergyService::ServiceState state);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
};
|
||||
|
||||
#endif // TRXAPPGATEUSBTREADMILL_H
|
||||
311
src/virtualbike.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "virtualbike.h"
|
||||
#include <QtMath>
|
||||
#include <QMetaEnum>
|
||||
#include <QDataStream>
|
||||
|
||||
enum FtmsControlPointCommand {
|
||||
FTMS_REQUEST_CONTROL = 0x00,
|
||||
FTMS_RESET,
|
||||
FTMS_SET_TARGET_SPEED,
|
||||
FTMS_SET_TARGET_INCLINATION,
|
||||
FTMS_SET_TARGET_RESISTANCE_LEVEL,
|
||||
FTMS_SET_TARGET_POWER,
|
||||
FTMS_SET_TARGET_HEARTRATE,
|
||||
FTMS_START_RESUME,
|
||||
FTMS_STOP_PAUSE,
|
||||
FTMS_SET_TARGETED_EXP_ENERGY,
|
||||
FTMS_SET_TARGETED_STEPS,
|
||||
FTMS_SET_TARGETED_STRIDES,
|
||||
FTMS_SET_TARGETED_DISTANCE,
|
||||
FTMS_SET_TARGETED_TIME,
|
||||
FTMS_SET_TARGETED_TIME_TWO_HR_ZONES,
|
||||
FTMS_SET_TARGETED_TIME_THREE_HR_ZONES,
|
||||
FTMS_SET_TARGETED_TIME_FIVE_HR_ZONES,
|
||||
FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS,
|
||||
FTMS_SET_WHEEL_CIRCUMFERENCE,
|
||||
FTMS_SPIN_DOWN_CONTROL,
|
||||
FTMS_SET_TARGETED_CADENCE,
|
||||
FTMS_RESPONSE_CODE = 0x80
|
||||
};
|
||||
|
||||
enum FtmsResultCode {
|
||||
FTMS_SUCCESS = 0x01,
|
||||
FTMS_NOT_SUPPORTED,
|
||||
FTMS_INVALID_PARAMETER,
|
||||
FTMS_OPERATION_FAILED,
|
||||
FTMS_CONTROL_NOT_PERMITTED
|
||||
};
|
||||
|
||||
virtualbike::virtualbike(bike* t, bool noWriteResistance, bool noHeartService)
|
||||
{
|
||||
Bike = t;
|
||||
this->noHeartService = noHeartService;
|
||||
Q_UNUSED(noWriteResistance)
|
||||
|
||||
//! [Advertising Data]
|
||||
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
|
||||
advertisingData.setIncludePowerLevel(true);
|
||||
advertisingData.setLocalName("DomyosBridge");
|
||||
QList<QBluetoothUuid> services;
|
||||
services << ((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
|
||||
if(!this->noHeartService)
|
||||
services << QBluetoothUuid::HeartRate;
|
||||
advertisingData.setServices(services);
|
||||
//! [Advertising Data]
|
||||
|
||||
serviceDataFIT.setType(QLowEnergyServiceData::ServiceTypePrimary);
|
||||
QLowEnergyCharacteristicData charDataFIT;
|
||||
charDataFIT.setUuid((QBluetoothUuid::CharacteristicType)0x2ACC); //FitnessMachineFeatureCharacteristicUuid
|
||||
QByteArray valueFIT;
|
||||
valueFIT.append((char)0x80); // resistance level supported
|
||||
valueFIT.append((char)0x14); // heart rate and elapsed time
|
||||
valueFIT.append((char)0x00);
|
||||
valueFIT.append((char)0x00);
|
||||
valueFIT.append((char)0x00);
|
||||
valueFIT.append((char)0x00);
|
||||
valueFIT.append((char)0x00);
|
||||
valueFIT.append((char)0x00);
|
||||
charDataFIT.setValue(valueFIT);
|
||||
charDataFIT.setProperties(QLowEnergyCharacteristic::Read);
|
||||
|
||||
QLowEnergyCharacteristicData charDataFIT2;
|
||||
charDataFIT2.setUuid((QBluetoothUuid::CharacteristicType)0x2AD6); //supported_resistance_level_rangeCharacteristicUuid
|
||||
charDataFIT2.setProperties(QLowEnergyCharacteristic::Read);
|
||||
QByteArray valueFIT2;
|
||||
valueFIT2.append((char)0x0A); // min resistance value
|
||||
valueFIT2.append((char)0x00); // min resistance value
|
||||
valueFIT2.append((char)0x96); // max resistance value
|
||||
valueFIT2.append((char)0x00); // max resistance value
|
||||
valueFIT2.append((char)0x0A); // step resistance
|
||||
valueFIT2.append((char)0x00); // step resistance
|
||||
charDataFIT2.setValue(valueFIT2);
|
||||
|
||||
QLowEnergyCharacteristicData charDataFIT3;
|
||||
charDataFIT3.setUuid((QBluetoothUuid::CharacteristicType)0x2AD9); //Fitness Machine Control Point
|
||||
charDataFIT3.setProperties(QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::Indicate);
|
||||
const QLowEnergyDescriptorData cpClientConfig(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
QByteArray(2, 0));
|
||||
charDataFIT3.addDescriptor(cpClientConfig);
|
||||
|
||||
QLowEnergyCharacteristicData charDataFIT4;
|
||||
charDataFIT4.setUuid((QBluetoothUuid::CharacteristicType)0x2AD2); //indoor bike
|
||||
charDataFIT4.setProperties(QLowEnergyCharacteristic::Notify | QLowEnergyCharacteristic::Read);
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
const QLowEnergyDescriptorData clientConfig4(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
descriptor);
|
||||
charDataFIT4.addDescriptor(clientConfig4);
|
||||
|
||||
serviceDataFIT.setUuid((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
|
||||
serviceDataFIT.addCharacteristic(charDataFIT);
|
||||
serviceDataFIT.addCharacteristic(charDataFIT2);
|
||||
serviceDataFIT.addCharacteristic(charDataFIT3);
|
||||
serviceDataFIT.addCharacteristic(charDataFIT4);
|
||||
|
||||
if(!this->noHeartService)
|
||||
{
|
||||
QLowEnergyCharacteristicData charDataHR;
|
||||
charDataHR.setUuid(QBluetoothUuid::HeartRateMeasurement);
|
||||
charDataHR.setValue(QByteArray(2, 0));
|
||||
charDataHR.setProperties(QLowEnergyCharacteristic::Notify);
|
||||
const QLowEnergyDescriptorData clientConfigHR(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
QByteArray(2, 0));
|
||||
charDataHR.addDescriptor(clientConfigHR);
|
||||
|
||||
serviceDataHR.setType(QLowEnergyServiceData::ServiceTypePrimary);
|
||||
serviceDataHR.setUuid(QBluetoothUuid::HeartRate);
|
||||
serviceDataHR.addCharacteristic(charDataHR);
|
||||
}
|
||||
|
||||
//! [Start Advertising]
|
||||
leController = QLowEnergyController::createPeripheral();
|
||||
Q_ASSERT(leController);
|
||||
if(!this->noHeartService)
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
serviceFIT = leController->addService(serviceDataFIT);
|
||||
|
||||
QObject::connect(serviceFIT, SIGNAL(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)), this, SLOT(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
|
||||
QLowEnergyAdvertisingParameters pars;
|
||||
pars.setInterval(100, 100);
|
||||
leController->startAdvertising(pars, advertisingData,
|
||||
advertisingData);
|
||||
//! [Start Advertising]
|
||||
|
||||
//! [Provide Heartbeat]
|
||||
QObject::connect(&bikeTimer, SIGNAL(timeout()), this, SLOT(bikeProvider()));
|
||||
bikeTimer.start(1000);
|
||||
//! [Provide Heartbeat]
|
||||
QObject::connect(leController, SIGNAL(disconnected()), this, SLOT(reconnect()));
|
||||
QObject::connect(leController, SIGNAL(error(QLowEnergyController::Error)), this, SLOT(error(QLowEnergyController::Error)));
|
||||
}
|
||||
|
||||
void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
QByteArray reply;
|
||||
emit debug("characteristicChanged " + QString::number(characteristic.uuid().toUInt16()) + " " + newValue.toHex(' '));
|
||||
|
||||
switch(characteristic.uuid().toUInt16())
|
||||
{
|
||||
case 0x2AD9: // Fitness Machine Control Point
|
||||
if((char)newValue.at(0) == FTMS_SET_TARGET_RESISTANCE_LEVEL)
|
||||
{
|
||||
// Set Target Resistance
|
||||
uint8_t uresistance = newValue.at(1);
|
||||
uresistance = uresistance / 10;
|
||||
Bike->changeResistance(uresistance);
|
||||
emit debug("new requested resistance " + QString::number(uresistance));
|
||||
reply.append((quint8)FTMS_RESPONSE_CODE);
|
||||
reply.append((quint8)FTMS_SET_TARGET_RESISTANCE_LEVEL);
|
||||
reply.append((quint8)FTMS_SUCCESS);
|
||||
}
|
||||
else if((char)newValue.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS) // simulation parameter
|
||||
{
|
||||
emit debug("indoor bike simulation parameters");
|
||||
reply.append((quint8)FTMS_RESPONSE_CODE);
|
||||
reply.append((quint8)FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS);
|
||||
reply.append((quint8)FTMS_SUCCESS);
|
||||
|
||||
int16_t iresistance = (newValue.at(3) + (newValue.at(4) << 8));
|
||||
double resistance = ((double)iresistance) / 100.0;
|
||||
Bike->changeResistance((uint8_t)resistance + 1); // resistance start from 1
|
||||
}
|
||||
else if((char)newValue.at(0) == FTMS_START_RESUME)
|
||||
{
|
||||
emit debug("start simulation!");
|
||||
reply.append((quint8)FTMS_RESPONSE_CODE);
|
||||
reply.append((quint8)FTMS_START_RESUME);
|
||||
reply.append((quint8)FTMS_SUCCESS);
|
||||
}
|
||||
else if((char)newValue.at(0) == FTMS_REQUEST_CONTROL)
|
||||
{
|
||||
emit debug("control requested");
|
||||
reply.append((quint8)FTMS_RESPONSE_CODE);
|
||||
reply.append((quint8)FTMS_REQUEST_CONTROL);
|
||||
reply.append((quint8)FTMS_SUCCESS);
|
||||
}
|
||||
else
|
||||
{
|
||||
emit debug("not supported");
|
||||
reply.append((quint8)FTMS_RESPONSE_CODE);
|
||||
reply.append((quint8)newValue.at(0));
|
||||
reply.append((quint8)FTMS_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
QLowEnergyCharacteristic characteristic
|
||||
= serviceFIT->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9);
|
||||
Q_ASSERT(characteristic.isValid());
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
return;
|
||||
}
|
||||
writeCharacteristic(serviceFIT, characteristic, reply);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void virtualbike::writeCharacteristic(QLowEnergyService* service, QLowEnergyCharacteristic characteristic, QByteArray value)
|
||||
{
|
||||
try {
|
||||
emit debug("virtualbike::writeCharacteristic " + service->serviceName() + " " + characteristic.name() + " " + value.toHex(' '));
|
||||
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
|
||||
} catch (...) {
|
||||
emit debug("virtual bike error!");
|
||||
}
|
||||
}
|
||||
|
||||
void virtualbike::reconnect()
|
||||
{
|
||||
emit debug("virtualbike::reconnect");
|
||||
leController->disconnectFromDevice();
|
||||
|
||||
if(!this->noHeartService)
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
serviceFIT = leController->addService(serviceDataFIT);
|
||||
|
||||
if (serviceFIT)
|
||||
leController->startAdvertising(QLowEnergyAdvertisingParameters(),
|
||||
advertisingData, advertisingData);
|
||||
}
|
||||
|
||||
void virtualbike::bikeProvider()
|
||||
{
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
emit debug("virtual bike connected");
|
||||
}
|
||||
|
||||
QByteArray value;
|
||||
|
||||
value.append((char)0x64); // speed, inst. cadence, resistance lvl, instant power
|
||||
value.append((char)0x02); // heart rate
|
||||
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed() * 100);
|
||||
value.append((char)(normalizeSpeed & 0xFF)); // speed
|
||||
value.append((char)(normalizeSpeed >> 8) & 0xFF); // speed
|
||||
|
||||
value.append((char)(Bike->currentCadence() * 2)); // cadence
|
||||
value.append((char)(0)); // cadence
|
||||
|
||||
value.append((char)Bike->currentResistance()); // resistance
|
||||
value.append((char)(0)); // resistance
|
||||
|
||||
value.append((char)(Bike->watts() & 0xFF)); // watts
|
||||
value.append((char)(Bike->watts() >> 8) & 0xFF); // watts
|
||||
|
||||
value.append(char(Bike->currentHeart())); // Actual value.
|
||||
|
||||
QLowEnergyCharacteristic characteristic
|
||||
= serviceFIT->characteristic((QBluetoothUuid::CharacteristicType)0x2AD2);
|
||||
Q_ASSERT(characteristic.isValid());
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
return;
|
||||
}
|
||||
writeCharacteristic(serviceFIT, characteristic, value);
|
||||
|
||||
//characteristic
|
||||
// = service->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9); // Fitness Machine Control Point
|
||||
//Q_ASSERT(characteristic.isValid());
|
||||
//service->readCharacteristic(characteristic);
|
||||
|
||||
if(!this->noHeartService)
|
||||
{
|
||||
QByteArray valueHR;
|
||||
valueHR.append(char(0)); // Flags that specify the format of the value.
|
||||
valueHR.append(char(Bike->currentHeart())); // Actual value.
|
||||
QLowEnergyCharacteristic characteristicHR
|
||||
= serviceHR->characteristic(QBluetoothUuid::HeartRateMeasurement);
|
||||
Q_ASSERT(characteristicHR.isValid());
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
return;
|
||||
}
|
||||
writeCharacteristic(serviceHR, characteristicHR, valueHR);
|
||||
}
|
||||
}
|
||||
|
||||
bool virtualbike::connected()
|
||||
{
|
||||
if(!leController)
|
||||
return false;
|
||||
return leController->state() == QLowEnergyController::ConnectedState;
|
||||
}
|
||||
|
||||
void virtualbike::error(QLowEnergyController::Error newError)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
debug("virtualbike::controller:ERROR " + QString::fromLocal8Bit(metaEnum.valueToKey(newError)));
|
||||
|
||||
if(newError != QLowEnergyController::RemoteHostClosedError)
|
||||
reconnect();
|
||||
}
|
||||
58
src/virtualbike.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef VIRTUALBIKE_H
|
||||
#define VIRTUALBIKE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.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/qloggingcategory.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include "bike.h"
|
||||
|
||||
class virtualbike: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtualbike(bike* t, bool noWriteResistance = false, bool noHeartService = false);
|
||||
bool connected();
|
||||
|
||||
private:
|
||||
QLowEnergyController* leController;
|
||||
QLowEnergyService* serviceHR;
|
||||
QLowEnergyService* serviceFIT;
|
||||
QLowEnergyAdvertisingData advertisingData;
|
||||
QLowEnergyServiceData serviceDataHR;
|
||||
QLowEnergyServiceData serviceDataFIT;
|
||||
QTimer bikeTimer;
|
||||
bike* Bike;
|
||||
|
||||
bool noHeartService = false;
|
||||
|
||||
void writeCharacteristic(QLowEnergyService* service, QLowEnergyCharacteristic characteristic, QByteArray value);
|
||||
|
||||
signals:
|
||||
void debug(QString string);
|
||||
|
||||
private slots:
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void bikeProvider();
|
||||
void reconnect();
|
||||
void error(QLowEnergyController::Error newError);
|
||||
};
|
||||
|
||||
#endif // VIRTUALBIKE_H
|
||||
@@ -1,16 +1,11 @@
|
||||
#include "virtualtreadmill.h"
|
||||
#include <QtMath>
|
||||
|
||||
volatile double currentSpeed = 0;
|
||||
volatile double currentIncline = 0;
|
||||
volatile uint8_t currentHeart = 0;
|
||||
volatile double requestSpeed = -1;
|
||||
volatile double requestIncline = -1;
|
||||
volatile int8_t requestStart = -1;
|
||||
volatile int8_t requestStop = -1;
|
||||
|
||||
virtualtreadmill::virtualtreadmill()
|
||||
virtualtreadmill::virtualtreadmill(treadmill* t, bool noHeartService)
|
||||
{
|
||||
treadMill = t;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
//! [Advertising Data]
|
||||
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
|
||||
advertisingData.setIncludePowerLevel(true);
|
||||
@@ -60,27 +55,33 @@ virtualtreadmill::virtualtreadmill()
|
||||
serviceData.addCharacteristic(charData3);
|
||||
//! [Service Data]
|
||||
|
||||
QLowEnergyCharacteristicData charDataHR;
|
||||
charDataHR.setUuid(QBluetoothUuid::HeartRateMeasurement);
|
||||
charDataHR.setValue(QByteArray(2, 0));
|
||||
charDataHR.setProperties(QLowEnergyCharacteristic::Notify);
|
||||
const QLowEnergyDescriptorData clientConfigHR(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
QByteArray(2, 0));
|
||||
charDataHR.addDescriptor(clientConfigHR);
|
||||
if(noHeartService == false)
|
||||
{
|
||||
QLowEnergyCharacteristicData charDataHR;
|
||||
charDataHR.setUuid(QBluetoothUuid::HeartRateMeasurement);
|
||||
charDataHR.setValue(QByteArray(2, 0));
|
||||
charDataHR.setProperties(QLowEnergyCharacteristic::Notify);
|
||||
const QLowEnergyDescriptorData clientConfigHR(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
QByteArray(2, 0));
|
||||
charDataHR.addDescriptor(clientConfigHR);
|
||||
|
||||
QLowEnergyServiceData serviceDataHR;
|
||||
serviceDataHR.setType(QLowEnergyServiceData::ServiceTypePrimary);
|
||||
serviceDataHR.setUuid(QBluetoothUuid::HeartRate);
|
||||
serviceDataHR.addCharacteristic(charDataHR);
|
||||
serviceDataHR.setType(QLowEnergyServiceData::ServiceTypePrimary);
|
||||
serviceDataHR.setUuid(QBluetoothUuid::HeartRate);
|
||||
serviceDataHR.addCharacteristic(charDataHR);
|
||||
}
|
||||
|
||||
//! [Start Advertising]
|
||||
leController = QLowEnergyController::createPeripheral();
|
||||
Q_ASSERT(leController);
|
||||
service = leController->addService(serviceData);
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
if(noHeartService == false)
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
|
||||
QObject::connect(service, SIGNAL(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)), this, SLOT(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
|
||||
leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData,
|
||||
QLowEnergyAdvertisingParameters pars;
|
||||
pars.setInterval(100, 100);
|
||||
leController->startAdvertising(pars, advertisingData,
|
||||
advertisingData);
|
||||
//! [Start Advertising]
|
||||
|
||||
@@ -93,7 +94,7 @@ virtualtreadmill::virtualtreadmill()
|
||||
|
||||
void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
qDebug() << "characteristicChanged" << characteristic.uuid().toUInt16() << newValue;
|
||||
emit debug("characteristicChanged " + QString::number(characteristic.uuid().toUInt16()) + " " + newValue);
|
||||
|
||||
char a;
|
||||
char b;
|
||||
@@ -108,8 +109,9 @@ void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
b = newValue.at(2);
|
||||
|
||||
uint16_t uspeed = a + (((uint16_t)b) << 8);
|
||||
requestSpeed = (double)uspeed / 100.0;
|
||||
qDebug() << "new requested speed" << requestSpeed;
|
||||
double requestSpeed = (double)uspeed / 100.0;
|
||||
treadMill->changeSpeed(requestSpeed);
|
||||
emit debug("new requested speed " + QString::number(requestSpeed));
|
||||
}
|
||||
else if ((char)newValue.at(0)== 0x03) // Set Target Inclination
|
||||
{
|
||||
@@ -117,20 +119,21 @@ void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
b = newValue.at(2);
|
||||
|
||||
int16_t sincline = a + (((int16_t)b) << 8);
|
||||
requestIncline = (double)sincline / 10.0;
|
||||
double requestIncline = (double)sincline / 10.0;
|
||||
if(requestIncline < 0)
|
||||
requestIncline = 0;
|
||||
qDebug() << "new requested incline" << requestIncline;
|
||||
treadMill->changeInclination(requestIncline);
|
||||
emit debug("new requested incline " +QString::number(requestIncline));
|
||||
}
|
||||
else if ((char)newValue.at(0)== 0x07) // Start request
|
||||
{
|
||||
requestStart = 1;
|
||||
qDebug() << "request to start";
|
||||
treadMill->start();
|
||||
emit debug("request to start");
|
||||
}
|
||||
else if ((char)newValue.at(0)== 0x08) // Stop request
|
||||
{
|
||||
requestStop = 1;
|
||||
qDebug() << "request to stop";
|
||||
treadMill->stop();
|
||||
emit debug("request to stop");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -138,7 +141,12 @@ void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
|
||||
void virtualtreadmill::reconnect()
|
||||
{
|
||||
emit debug("virtualtreadmill reconnect");
|
||||
service = leController->addService(serviceData);
|
||||
|
||||
if(noHeartService == false)
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
|
||||
if (service)
|
||||
leController->startAdvertising(QLowEnergyAdvertisingParameters(),
|
||||
advertisingData, advertisingData);
|
||||
@@ -146,23 +154,29 @@ void virtualtreadmill::reconnect()
|
||||
|
||||
void virtualtreadmill::treadmillProvider()
|
||||
{
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtualtreadmill connection error");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray value;
|
||||
value.append(0x08); // Inclination avaiable
|
||||
value.append((char)0x00);
|
||||
value.append((char)0x01); // heart rate avaiable
|
||||
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(currentSpeed * 100);
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(treadMill->currentSpeed() * 100);
|
||||
char a = (normalizeSpeed >> 8) & 0XFF;
|
||||
char b = normalizeSpeed & 0XFF;
|
||||
QByteArray speedBytes;
|
||||
speedBytes.append(b);
|
||||
speedBytes.append(a);
|
||||
uint16_t normalizeIncline = (uint32_t)qRound(currentIncline * 10);
|
||||
uint16_t normalizeIncline = (uint32_t)qRound(treadMill->currentInclination() * 10);
|
||||
a = (normalizeIncline >> 8) & 0XFF;
|
||||
b = normalizeIncline & 0XFF;
|
||||
QByteArray inclineBytes;
|
||||
inclineBytes.append(b);
|
||||
inclineBytes.append(a);
|
||||
double ramp = qRadiansToDegrees(qAtan(currentIncline/100));
|
||||
double ramp = qRadiansToDegrees(qAtan(treadMill->currentInclination()/100));
|
||||
int16_t normalizeRamp = (int32_t)qRound(ramp * 10);
|
||||
a = (normalizeRamp >> 8) & 0XFF;
|
||||
b = normalizeRamp & 0XFF;
|
||||
@@ -176,39 +190,51 @@ void virtualtreadmill::treadmillProvider()
|
||||
|
||||
value.append(rampBytes); //ramp angle
|
||||
|
||||
value.append(treadMill->currentHeart()); // current heart rate
|
||||
|
||||
QLowEnergyCharacteristic characteristic
|
||||
= service->characteristic((QBluetoothUuid::CharacteristicType)0x2ACD); //TreadmillDataCharacteristicUuid
|
||||
Q_ASSERT(characteristic.isValid());
|
||||
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtualtreadmill connection error");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
|
||||
} catch (...) {
|
||||
emit debug("virtualtreadmill error!");
|
||||
}
|
||||
|
||||
//characteristic
|
||||
// = service->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9); // Fitness Machine Control Point
|
||||
//Q_ASSERT(characteristic.isValid());
|
||||
//service->readCharacteristic(characteristic);
|
||||
|
||||
QByteArray valueHR;
|
||||
valueHR.append(char(0)); // Flags that specify the format of the value.
|
||||
valueHR.append(char(currentHeart)); // Actual value.
|
||||
QLowEnergyCharacteristic characteristicHR
|
||||
= serviceHR->characteristic(QBluetoothUuid::HeartRateMeasurement);
|
||||
Q_ASSERT(characteristicHR.isValid());
|
||||
serviceHR->writeCharacteristic(characteristicHR, valueHR); // Potentially causes notification.
|
||||
}
|
||||
|
||||
uint16_t virtualtreadmill::watts()
|
||||
{
|
||||
// calc Watts ref. https://alancouzens.com/blog/Run_Power.html
|
||||
|
||||
uint16_t watts=0;
|
||||
if(currentSpeed > 0)
|
||||
if(noHeartService == false)
|
||||
{
|
||||
double weight=75.0; // TODO: config need
|
||||
double pace=60/currentSpeed;
|
||||
double VO2R=210.0/pace;
|
||||
double VO2A=(VO2R*weight)/1000.0;
|
||||
double hwatts=75*VO2A;
|
||||
double vwatts=((9.8*weight) * (currentIncline/100));
|
||||
watts=hwatts+vwatts;
|
||||
QByteArray valueHR;
|
||||
valueHR.append(char(0)); // Flags that specify the format of the value.
|
||||
valueHR.append(char(treadMill->currentHeart())); // Actual value.
|
||||
QLowEnergyCharacteristic characteristicHR
|
||||
= serviceHR->characteristic(QBluetoothUuid::HeartRateMeasurement);
|
||||
Q_ASSERT(characteristicHR.isValid());
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtualtreadmill connection error");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
serviceHR->writeCharacteristic(characteristicHR, valueHR); // Potentially causes notification.
|
||||
} catch (...) {
|
||||
emit debug("virtualtreadmill error!");
|
||||
}
|
||||
}
|
||||
return watts;
|
||||
}
|
||||
|
||||
bool virtualtreadmill::connected()
|
||||
{
|
||||
if(!leController)
|
||||
return false;
|
||||
return leController->state() == QLowEnergyController::ConnectedState;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
class virtualtreadmill: QObject
|
||||
#include "treadmill.h"
|
||||
|
||||
class virtualtreadmill: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtualtreadmill();
|
||||
virtualtreadmill(treadmill* t, bool noHeartService);
|
||||
bool connected();
|
||||
|
||||
private:
|
||||
QLowEnergyController* leController;
|
||||
@@ -34,8 +37,14 @@ private:
|
||||
QLowEnergyService* serviceHR;
|
||||
QLowEnergyAdvertisingData advertisingData;
|
||||
QLowEnergyServiceData serviceData;
|
||||
QTimer treadmillTimer;
|
||||
uint16_t watts();
|
||||
QLowEnergyServiceData serviceDataHR;
|
||||
QTimer treadmillTimer;
|
||||
treadmill* treadMill;
|
||||
|
||||
bool noHeartService = false;
|
||||
|
||||
signals:
|
||||
void debug(QString string);
|
||||
|
||||
private slots:
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
|
||||
23
train-programs-examples/calorie-barbara.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rows>
|
||||
<row duration="00:02:00" speed="5" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:03:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:05:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7.7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7.7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7.7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
|
||||
<row duration="00:06:00" speed="6" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="5" inclination="0" forcespeed="1"/>
|
||||
</rows>
|
||||
43062
train-programs-examples/fogliano.gpx
Normal file
74
train-programs-examples/fogliano.xml
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rows>
|
||||
<row duration="00:01:00" speed="9.85447" inclination="-1.82658" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.2993" inclination="-2.91281" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.2958" inclination="-2.15623" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.2988" inclination="-0.174779" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.3375" inclination="-2.49576" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.0169" inclination="-1.41601" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.8215" inclination="-2.0302" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.0147" inclination="-1.19839" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="3.66404" inclination="3.11131" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.80634" inclination="5.75139" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="8.71754" inclination="9.91104" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="8.99724" inclination="9.40288" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.11" inclination="4.51039" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.1726" inclination="4.01078" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.96776" inclination="6.01941" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.21979" inclination="9.24099" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.00303" inclination="9.39684" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="7.59236" inclination="7.6656" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.24165" inclination="5.12895" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6.95364" inclination="9.23258" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.4115" inclination="6.45437" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.95344" inclination="5.9678" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.3448" inclination="2.72599" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.485" inclination="0.286123" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.42228" inclination="-5.4127" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.5566" inclination="-6.5417" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.3381" inclination="-3.48228" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="12.2822" inclination="-2.34486" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.4139" inclination="4.72445" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="8.69461" inclination="-5.38266" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.1889" inclination="-3.06215" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.98059" inclination="2.46477" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.9802" inclination="3.87972" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.05151" inclination="-0.0662914" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="3.99165" inclination="-19.9917" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="8.7631" inclination="-12.0505" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6.64707" inclination="-16.2478" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="6.51384" inclination="-14.8299" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="8.57592" inclination="-10.8443" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="8.401" inclination="-8.92751" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.2526" inclination="-2.45276" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.6882" inclination="-1.12934" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="13.9318" inclination="-0.387599" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.6752" inclination="-0.112417" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.2653" inclination="-2.33797" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.7095" inclination="-2.18497" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.3889" inclination="2.10888" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.1298" inclination="-3.01893" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.9722" inclination="-1.90441" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="12.0696" inclination="-1.73991" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="12.9631" inclination="-1.80512" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="12.336" inclination="-1.89688" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.5617" inclination="-0.467066" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.3423" inclination="-1.63987" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.5684" inclination="0" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.971" inclination="0.656273" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.2002" inclination="-0.642841" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.1976" inclination="-0.910918" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.4373" inclination="-0.977264" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.2949" inclination="-0.582811" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.9175" inclination="0.0549612" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="9.11082" inclination="2.30495" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.4257" inclination="1.20855" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.9692" inclination="1.42217" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.5789" inclination="1.24777" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.5046" inclination="0.971002" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.5973" inclination="0.339712" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.4905" inclination="1.14389" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="11.7968" inclination="1.5767" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="10.9239" inclination="1.70268" forcespeed="1"/>
|
||||
<row duration="00:01:00" speed="12.6751" inclination="2.46152" forcespeed="1"/>
|
||||
</rows>
|
||||