mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
481e256621 | ||
|
|
2e94a16889 | ||
|
|
b4c4b194b2 | ||
|
|
5da8440086 | ||
|
|
d5424a38fa | ||
|
|
1947473b2c | ||
|
|
1b38c1f400 | ||
|
|
51f7580d06 | ||
|
|
e6b2a17bee | ||
|
|
95338bb35e | ||
|
|
f4138820cf | ||
|
|
f446dac1db | ||
|
|
2967fc1ab4 | ||
|
|
ed3ab8b0f1 | ||
|
|
884b9a9a8c | ||
|
|
aac169b834 | ||
|
|
601d9cb20e | ||
|
|
90034c3746 | ||
|
|
97a2e7d8c9 | ||
|
|
bccd5522d1 | ||
|
|
07c5dc247a | ||
|
|
005834588a | ||
|
|
a2fa9a4843 | ||
|
|
1d8f473e4c | ||
|
|
4dc48fca2b | ||
|
|
7eb0176e0c | ||
|
|
974396672a | ||
|
|
1758255399 | ||
|
|
1506839f54 | ||
|
|
636c4c8185 | ||
|
|
9d0fd94a23 | ||
|
|
251c00cefb | ||
|
|
4949e8d816 | ||
|
|
e57b0834c6 | ||
|
|
bb3f9d0bb4 | ||
|
|
2a8b8d6584 | ||
|
|
ea58b92fed | ||
|
|
d6f5ce405e | ||
|
|
34ffafb55f | ||
|
|
e6d9f5d847 | ||
|
|
c3bfaffcf1 | ||
|
|
f5eac6d6a1 | ||
|
|
19c3a90bf4 | ||
|
|
705baaa37c | ||
|
|
e302e90066 | ||
|
|
9d9800d4e6 | ||
|
|
5c00a959f4 | ||
|
|
54501760d3 | ||
|
|
2f4b76014f | ||
|
|
7de4bac932 | ||
|
|
d992959792 | ||
|
|
1ce77629ff | ||
|
|
908c1536f6 | ||
|
|
b8948c6d8f | ||
|
|
1ab448f7cc | ||
|
|
38ea3f5c80 | ||
|
|
a87e818d9a | ||
|
|
d852bd44fe | ||
|
|
5f7d7e01b8 | ||
|
|
548fa9d8d6 | ||
|
|
3a725d71b5 | ||
|
|
a304963dc5 | ||
|
|
bf9fb4537b | ||
|
|
838fe8c96e | ||
|
|
77b204d9fd | ||
|
|
8a6e8e9c9d | ||
|
|
123df9db6b |
74
.github/workflows/main.yml
vendored
74
.github/workflows/main.yml
vendored
@@ -19,19 +19,19 @@ jobs:
|
||||
|
||||
# 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 Desktop
|
||||
# id: cache-qt-linux-desktop
|
||||
# uses: actions/cache@v1
|
||||
# with:
|
||||
# path: '${{ github.workspace }}/output/linux-desktop/'
|
||||
# key: ${{ runner.os }}-QtCache-Linux-Desktop
|
||||
|
||||
- name: Cache Qt Linux Android
|
||||
id: cache-qt-android
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: '${{ github.workspace }}/output/android/'
|
||||
key: ${{ runner.os }}-QtCache-Android
|
||||
# - name: 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
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
target: 'desktop'
|
||||
modules: 'qtcharts debug_info'
|
||||
dir: '${{ github.workspace }}/output/linux-desktop/'
|
||||
cached: ${{ steps.cache-qt-linux-desktop.outputs.cache-hit }}
|
||||
# cached: ${{ steps.cache-qt-linux-desktop.outputs.cache-hit }}
|
||||
|
||||
- name: Compile Linux Desktop
|
||||
run: cd src; qmake; make -j4
|
||||
@@ -56,39 +56,39 @@ jobs:
|
||||
name: linux-desktop-binary
|
||||
path: src/qdomyos-zwift
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: nttld/setup-ndk
|
||||
path: setup-ndk
|
||||
# - 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
|
||||
# - 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
|
||||
# - name: Setup Android NDK r21d
|
||||
# uses: ./setup-ndk
|
||||
#- uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r21d
|
||||
# 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: 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: Compile Android
|
||||
# run: cd src; qmake; make -j4
|
||||
|
||||
# - name: Install Qt MacOS
|
||||
# uses: jurplel/install-qt-action@v2
|
||||
|
||||
17
README.md
17
README.md
@@ -5,6 +5,8 @@ Zwift bridge for Treadmills and Bike!
|
||||
|
||||

|
||||
|
||||
[](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -17,12 +19,13 @@ 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
|
||||
2. Toorx TRX Route Key compatible
|
||||
3. Echelon Connect Sport compatible
|
||||
4. Zwift compatible
|
||||
5. Create, load and save train programs
|
||||
6. Measure distance, elevation gain and watts
|
||||
7. Gpx import (with difficulty slider)
|
||||
8. Realtime Charts
|
||||
|
||||

|
||||
|
||||
@@ -59,6 +62,8 @@ Download and install http://download.qt.io/official_releases/qt/5.12/5.12.9/qt-o
|
||||
|
||||
- Raspberry 3b+ and Toorx TRX Route Key
|
||||
|
||||
- Android Pixel 2 and Echelon Connect Sport
|
||||
|
||||
|
||||
### Your machine is not compatible?
|
||||
|
||||
|
||||
69
docs/org.bluetooth.characteristic.csc_measurement.xml
Normal file
69
docs/org.bluetooth.characteristic.csc_measurement.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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="CSC Measurement" type="org.bluetooth.characteristic.csc_measurement" uuid="2A5B" last-modified="2012-04-12" approved="Yes">
|
||||
<InformativeText>
|
||||
<Summary>
|
||||
The CSC Measurement characteristic (CSC refers to Cycling Speed and Cadence) is a variable length structure containing a Flags field and, based on the contents of the Flags field, may contain one or more additional fields as shown in the tables below.
|
||||
</Summary>
|
||||
</InformativeText>
|
||||
<Value>
|
||||
<Field name="Flags">
|
||||
<InformativeText>These flags define which data fields are present in the Characteristic value.</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Format>8bit</Format>
|
||||
<BitField>
|
||||
<Bit index="0" size="1" name="Wheel Revolution Data Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C1" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="1" size="1" name="Crank Revolution Data Present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C2" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<ReservedForFutureUse index="2" size="6" />
|
||||
</BitField>
|
||||
</Field>
|
||||
<Field name="Cumulative Wheel Revolutions">
|
||||
<InformativeText>
|
||||
C1: Field exists if the key of bit 0 of the Flags field is set to 1.
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint32</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
</Field>
|
||||
<Field name="Last Wheel Event Time">
|
||||
<InformativeText>
|
||||
Unit has a resolution of 1/1024s.
|
||||
<br>C1: Field exists if the key of bit 0 of the Flags field is set to 1.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
<BinaryExponent>-10</BinaryExponent>
|
||||
</Field>
|
||||
<Field name="Cumulative Crank Revolutions">
|
||||
<InformativeText>
|
||||
C2: Field exists if the key of bit 1 of the Flags field is set to 1.
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.unitless</Unit>
|
||||
</Field>
|
||||
<Field name="Last Crank Event Time">
|
||||
<InformativeText>C2: Field exists if the key of bit 1 of the Flags field is set to 1.
|
||||
<br>Unit has a resolution of 1/1024s.</br>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Format>uint16</Format>
|
||||
<Unit>org.bluetooth.unit.time.second</Unit>
|
||||
<BinaryExponent>-10</BinaryExponent>
|
||||
</Field>
|
||||
</Value>
|
||||
<Note>
|
||||
The fields in the above table are in the order of LSO to MSO. Where LSO = Least Significant Octet and MSO = Most Significant Octet.
|
||||
</Note>
|
||||
</Characteristic>
|
||||
@@ -1,14 +1,8 @@
|
||||
<?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">
|
||||
<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>
|
||||
<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">
|
||||
@@ -17,203 +11,200 @@ last-modified="2017-02-14" approved="Yes">
|
||||
<BitField>
|
||||
<Bit index="0" size="1" name="More Data">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" requires="C1" />
|
||||
<Enumeration key="0" value="False" requires="C1"/>
|
||||
<Enumeration key="1" value="True" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="1" size="1"
|
||||
name="Instantaneous Cadence present">
|
||||
<Bit index="1" size="1" name="Average Speed present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C2" />
|
||||
<Enumeration key="1" value="True" requires="C2" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="2" size="1" name="Average Speed present">
|
||||
<Bit index="2" size="1" name="Instantaneous Cadence present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C3" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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" />
|
||||
<Enumeration key="1" value="True" requires="C10" />
|
||||
</Enumerations>
|
||||
</Bit>
|
||||
<Bit index="10" size="1"
|
||||
name="Metabolic Equivalent present">
|
||||
<Bit index="10" size="1" name="Metabolic Equivalent present">
|
||||
<Enumerations>
|
||||
<Enumeration key="0" value="False" />
|
||||
<Enumeration key="1" value="True" requires="C11" />
|
||||
<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" />
|
||||
<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" />
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<Unit>org.bluetooth.unit.angular_velocity.revolution_per_minute</Unit>
|
||||
</Field>
|
||||
|
||||
<Field name="Total Distance">
|
||||
<InformativeText>Meters with a resolution of
|
||||
1</InformativeText>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<InformativeText>Watts with a resolution of 1</InformativeText>
|
||||
<Requirement>C8</Requirement>
|
||||
<Format>sint16</Format>
|
||||
<Unit>org.bluetooth.unit.power.watt</Unit>
|
||||
</Field>
|
||||
</Field>
|
||||
|
||||
<Field name="Total Energy">
|
||||
<InformativeText>Kilo Calorie with a resolution of
|
||||
1</InformativeText>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
123
docs/org.bluetooth.service.cycling_speed_and_cadence.xml
Normal file
123
docs/org.bluetooth.service.cycling_speed_and_cadence.xml
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2011 Bluetooth SIG, Inc. All rights reserved. -->
|
||||
<Service xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/service.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Cycling Speed and Cadence" type="org.bluetooth.service.cycling_speed_and_cadence" uuid="1816" last-modified="2012-04-05" approved="Yes">
|
||||
<InformativeText>
|
||||
<Abstract>
|
||||
This service exposes speed-related and cadence-related data from a Cycling Speed and Cadence sensor intended for fitness applications.
|
||||
</Abstract>
|
||||
<Summary>
|
||||
The Cycling Speed and Cadence (CSC) Service exposes speed-related data and/or cadence-related data while using the Cycling Speed and Cadence sensor (Server).
|
||||
</Summary>
|
||||
</InformativeText>
|
||||
<Dependencies>
|
||||
<Dependency>This service is not dependent upon any other services.</Dependency>
|
||||
</Dependencies>
|
||||
<GATTRequirements>
|
||||
<Requirement subProcedure="Write Characteristic Values">C1</Requirement>
|
||||
<Requirement subProcedure="Notifications">Mandatory</Requirement>
|
||||
<Requirement subProcedure="Indications">C1</Requirement>
|
||||
<Requirement subProcedure="Read Characteristic Descriptors">Mandatory</Requirement>
|
||||
<Requirement subProcedure="Write Characteristic Descriptors">Mandatory</Requirement>
|
||||
</GATTRequirements>
|
||||
<Note>C1: Mandatory if the SC Control Point characteristic is supported, otherwise excluded for this service.</Note>
|
||||
<Transports>
|
||||
<Classic>true</Classic>
|
||||
<LowEnergy>true</LowEnergy>
|
||||
</Transports>
|
||||
<ErrorCodes>
|
||||
<ErrorCode name="Procedure Already in Progress" code="0x80">A SC Control Point request cannot be serviced because a previously triggered SC Control Point operation is still in progress.</ErrorCode>
|
||||
<ErrorCode name="Client Characteristic Configuration descriptor improperly configured" code="0x81">The Client Characteristic Configuration descriptor is not configured according to the requirements of the service.</ErrorCode>
|
||||
</ErrorCodes>
|
||||
<Characteristics>
|
||||
<Characteristic name="CSC Measurement" type="org.bluetooth.characteristic.csc_measurement">
|
||||
<InformativeText>
|
||||
The CSC Measurement characteristic is used to send speed-related data and/or cadence-related data.
|
||||
</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Properties>
|
||||
<Read>Excluded</Read>
|
||||
<Write>Excluded</Write>
|
||||
<WriteWithoutResponse>Excluded</WriteWithoutResponse>
|
||||
<SignedWrite>Excluded</SignedWrite>
|
||||
<ReliableWrite>Excluded</ReliableWrite>
|
||||
<Notify>Mandatory</Notify>
|
||||
<Indicate>Excluded</Indicate>
|
||||
<WritableAuxiliaries>Excluded</WritableAuxiliaries>
|
||||
<Broadcast>Excluded</Broadcast>
|
||||
</Properties>
|
||||
<Descriptors>
|
||||
<Descriptor name="Client Characteristic Configuration" type="org.bluetooth.descriptor.gatt.client_characteristic_configuration">
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Properties>
|
||||
<Read>Mandatory</Read>
|
||||
<Write>Mandatory</Write>
|
||||
</Properties>
|
||||
</Descriptor>
|
||||
</Descriptors>
|
||||
</Characteristic>
|
||||
<Characteristic name="CSC Feature" type="org.bluetooth.characteristic.csc_feature">
|
||||
<InformativeText>
|
||||
The CSC Feature characteristic is used to describe the supported features of the Server. Reserved for Future Use (RFU) bits in the CSC Feature characteristic value are set to 0.
|
||||
</InformativeText>
|
||||
<Requirement>Mandatory</Requirement>
|
||||
<Properties>
|
||||
<Read>Mandatory</Read>
|
||||
<Write>Excluded</Write>
|
||||
<WriteWithoutResponse>Excluded</WriteWithoutResponse>
|
||||
<SignedWrite>Excluded</SignedWrite>
|
||||
<ReliableWrite>Excluded</ReliableWrite>
|
||||
<Notify>Excluded</Notify>
|
||||
<Indicate>Excluded</Indicate>
|
||||
<WritableAuxiliaries>Excluded</WritableAuxiliaries>
|
||||
<Broadcast>Excluded</Broadcast>
|
||||
</Properties>
|
||||
</Characteristic>
|
||||
<Characteristic name="Sensor Location" type="org.bluetooth.characteristic.sensor_location">
|
||||
<InformativeText>
|
||||
<p>The Sensor Location characteristic of the device is used to describe the physical location of the Server when correctly fitted.</p>
|
||||
<p><b>C1:</b> Mandatory if the Multiple Sensor Location feature is supported, otherwise optional.</p>
|
||||
</InformativeText>
|
||||
<Requirement>C1</Requirement>
|
||||
<Properties>
|
||||
<Read>Mandatory</Read>
|
||||
<Write>Excluded</Write>
|
||||
<WriteWithoutResponse>Excluded</WriteWithoutResponse>
|
||||
<SignedWrite>Excluded</SignedWrite>
|
||||
<ReliableWrite>Excluded</ReliableWrite>
|
||||
<Notify>Excluded</Notify>
|
||||
<Indicate>Excluded</Indicate>
|
||||
<WritableAuxiliaries>Excluded</WritableAuxiliaries>
|
||||
<Broadcast>Excluded</Broadcast>
|
||||
</Properties>
|
||||
</Characteristic>
|
||||
<Characteristic name="SC Control Point" type="org.bluetooth.characteristic.sc_control_point">
|
||||
<InformativeText>
|
||||
<p>If the SC Control Point is supported, profiles utilizing this service are required to ensure that the Client configures the SC Control Point characteristic for indications (i.e. via the Client Characteristic Configuration descriptor) at the first connection.</p>
|
||||
<p>Support for this characteristic is mandatory if the Server supports Wheel Revolution Data or Multiple Sensor Locations features, otherwise it is excluded.</p>
|
||||
<p><b>C2:</b> Mandatory if at least one SC Control Point procedure is supported, otherwise excluded.</p>
|
||||
</InformativeText>
|
||||
<Requirement>C2</Requirement>
|
||||
<Properties>
|
||||
<Read>Excluded</Read>
|
||||
<Write>Mandatory</Write>
|
||||
<WriteWithoutResponse>Excluded</WriteWithoutResponse>
|
||||
<SignedWrite>Excluded</SignedWrite>
|
||||
<ReliableWrite>Excluded</ReliableWrite>
|
||||
<Notify>Excluded</Notify>
|
||||
<Indicate>Mandatory</Indicate>
|
||||
<WritableAuxiliaries>Excluded</WritableAuxiliaries>
|
||||
<Broadcast>Excluded</Broadcast>
|
||||
</Properties>
|
||||
|
||||
<Descriptors>
|
||||
<Descriptor name="Client Characteristic Configuration" type="org.bluetooth.descriptor.gatt.client_characteristic_configuration">
|
||||
<Requirement>if_characteristic_supported</Requirement>
|
||||
<Properties>
|
||||
<Read>Mandatory</Read>
|
||||
<Write>Mandatory</Write>
|
||||
</Properties>
|
||||
</Descriptor>
|
||||
</Descriptors>
|
||||
</Characteristic>
|
||||
</Characteristics>
|
||||
</Service>
|
||||
24
src/Home.qml
24
src/Home.qml
@@ -13,13 +13,13 @@ HomeForm{
|
||||
start.onClicked: { start_clicked(); }
|
||||
stop.onClicked: { stop_clicked(); }
|
||||
|
||||
Component.onCompleted: { console.log("completed"); }
|
||||
Component.onCompleted: { console.log("completed"); }
|
||||
|
||||
GridView {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175
|
||||
cellHeight: 125
|
||||
cellHeight: 130
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { (parent.width % 175) / 2; }
|
||||
@@ -34,17 +34,31 @@ HomeForm{
|
||||
// }
|
||||
delegate: Item {
|
||||
id: id1
|
||||
width: 175
|
||||
width: 170
|
||||
height: 125
|
||||
|
||||
visible: visibleItem
|
||||
Component.onCompleted: console.log("completed " + objectName)
|
||||
|
||||
Rectangle {
|
||||
width: 173
|
||||
width: 168
|
||||
height: 123
|
||||
radius: 3
|
||||
border.width: 1
|
||||
border.color: "purple"
|
||||
color: Material.backgroundColor
|
||||
id: rect
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
anchors.fill: rect
|
||||
cached: true
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
color: Material.color(Material.Purple)
|
||||
source: rect
|
||||
}
|
||||
|
||||
Image {
|
||||
@@ -60,7 +74,7 @@ HomeForm{
|
||||
Text {
|
||||
objectName: "value"
|
||||
id: myValue
|
||||
color: Material.textSelectionColor
|
||||
color: "white"
|
||||
y: 0
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -28,19 +28,42 @@ Page {
|
||||
width: 50
|
||||
height: 100
|
||||
color: Material.backgroundColor
|
||||
Image {
|
||||
Column {
|
||||
id: column
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
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"
|
||||
width: parent.width
|
||||
height: 100
|
||||
spacing: 0
|
||||
padding: 0
|
||||
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"
|
||||
}
|
||||
}
|
||||
Image {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
id: treadmill_signal
|
||||
width: 24
|
||||
height: 24
|
||||
source: rootItem.signal
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.3.1" android:versionCode="12" 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 -->
|
||||
@@ -9,7 +9,7 @@
|
||||
<!-- %%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">
|
||||
<application android:hardwareAccelerated="true" android:debuggable="false" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon">
|
||||
<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"/>
|
||||
@@ -73,7 +73,9 @@
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
|
||||
</manifest>
|
||||
|
||||
@@ -36,7 +36,7 @@ android {
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
buildToolsVersion '28.0.3'
|
||||
buildToolsVersion '29.0.2'
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -53,6 +53,7 @@ android {
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
// Do not compress Qt binary resources file
|
||||
@@ -62,7 +63,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
resConfig "en"
|
||||
minSdkVersion = qtMinSdkVersion
|
||||
targetSdkVersion = qtTargetSdkVersion
|
||||
minSdkVersion = 29
|
||||
targetSdkVersion = 29
|
||||
}
|
||||
}
|
||||
|
||||
106
src/bike.cpp
106
src/bike.cpp
@@ -13,110 +13,6 @@ int8_t bike::currentResistance() { return Resistance;}
|
||||
uint8_t bike::currentCadence() { return Cadence;}
|
||||
uint8_t bike::fanSpeed() { return FanSpeed; }
|
||||
bool bike::connected() { return false; }
|
||||
uint16_t bike::watts() { return 0; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
virtual double currentCrankRevolutions();
|
||||
virtual uint16_t lastCrankEventTime();
|
||||
virtual bool connected();
|
||||
uint16_t watts();
|
||||
virtual uint16_t watts();
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType();
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
#include <QDateTime>
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QtXml>
|
||||
|
||||
bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool noHeartService, uint32_t pollDeviceTime, bool noConsole, bool testResistance) : QObject(nullptr)
|
||||
bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool noHeartService, uint32_t pollDeviceTime, bool noConsole, bool testResistance, uint8_t bikeResistanceOffset, uint8_t bikeResistanceGain)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
|
||||
filterDevice = deviceName;
|
||||
@@ -14,6 +15,8 @@ bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool
|
||||
this->pollDeviceTime = pollDeviceTime;
|
||||
this->noConsole = noConsole;
|
||||
this->logs = logs;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
|
||||
#ifndef WIN32
|
||||
if(!QBluetoothLocalDevice::allDevices().count())
|
||||
@@ -27,12 +30,20 @@ bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool
|
||||
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
|
||||
connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
|
||||
this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
|
||||
connect(discoveryAgent, SIGNAL(canceled()),
|
||||
this, SLOT(canceled()));
|
||||
|
||||
// Start a discovery
|
||||
discoveryAgent->start();
|
||||
}
|
||||
}
|
||||
|
||||
void bluetooth::canceled()
|
||||
{
|
||||
debug("BTLE scanning stops");
|
||||
emit searchingStop();
|
||||
}
|
||||
|
||||
void bluetooth::debug(QString text)
|
||||
{
|
||||
QString debug = QDateTime::currentDateTime().toString() + " " + QString::number(QDateTime::currentMSecsSinceEpoch()) + " " + text;
|
||||
@@ -59,20 +70,41 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
if(device.name().startsWith("Domyos-Bike") && !device.name().startsWith("DomyosBridge") && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
domyosBike = new domyosbike(noWriteResistance, noHeartService, testResistance);
|
||||
domyosBike = new domyosbike(noWriteResistance, noHeartService, testResistance, bikeResistanceOffset, bikeResistanceGain);
|
||||
emit(deviceConnected());
|
||||
connect(domyosBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(domyosBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(domyosBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
domyosBike->deviceDiscovered(device);
|
||||
connect(this, SIGNAL(searchingStop()), domyosBike, SLOT(searchingStop()));
|
||||
if(!discoveryAgent->isActive())
|
||||
emit searchingStop();
|
||||
}
|
||||
else if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge") && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
domyos = new domyostreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
stateFileRead();
|
||||
emit(deviceConnected());
|
||||
connect(domyos, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(domyos, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(domyos, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
connect(domyos, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
|
||||
connect(domyos, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
domyos->deviceDiscovered(device);
|
||||
connect(this, SIGNAL(searchingStop()), domyos, SLOT(searchingStop()));
|
||||
if(!discoveryAgent->isActive())
|
||||
emit searchingStop();
|
||||
}
|
||||
else if(device.name().startsWith("ECH-SPORT") && filter)
|
||||
{
|
||||
discoveryAgent->stop();
|
||||
echelonConnectSport = new echelonconnectsport(noWriteResistance, noHeartService);
|
||||
//stateFileRead();
|
||||
emit(deviceConnected());
|
||||
connect(echelonConnectSport, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(echelonConnectSport, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
//connect(echelonConnectSport, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
|
||||
//connect(echelonConnectSport, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
|
||||
echelonConnectSport->deviceDiscovered(device);
|
||||
}
|
||||
else if((device.name().startsWith("TRX ROUTE KEY")) && filter)
|
||||
{
|
||||
@@ -116,6 +148,11 @@ void bluetooth::restart()
|
||||
delete trxappgateusb;
|
||||
trxappgateusb = 0;
|
||||
}
|
||||
if(echelonConnectSport)
|
||||
{
|
||||
delete echelonConnectSport;
|
||||
echelonConnectSport = 0;
|
||||
}
|
||||
discoveryAgent->start();
|
||||
}
|
||||
|
||||
@@ -129,5 +166,104 @@ bluetoothdevice* bluetooth::device()
|
||||
return toorx;
|
||||
else if(trxappgateusb)
|
||||
return trxappgateusb;
|
||||
else if(echelonConnectSport)
|
||||
return echelonConnectSport;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool bluetooth::handleSignal(int signal)
|
||||
{
|
||||
if(signal == SIGNALS::SIG_INT)
|
||||
{
|
||||
qDebug() << "SIGINT";
|
||||
QFile::remove("status.xml");
|
||||
exit(0);
|
||||
}
|
||||
// Let the signal propagate as though we had not been there
|
||||
return false;
|
||||
}
|
||||
|
||||
void bluetooth::stateFileRead()
|
||||
{
|
||||
if(!device()) return;
|
||||
|
||||
QFile* log;
|
||||
QDomDocument xmlBOM;
|
||||
log = new QFile("status.xml");
|
||||
if(!log->open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
qDebug() << "Open status.xml for writing failed";
|
||||
return;
|
||||
}
|
||||
xmlBOM.setContent(log);
|
||||
QDomElement root=xmlBOM.documentElement();
|
||||
|
||||
// Get root names and attributes
|
||||
QString Type=root.tagName();
|
||||
QString lastUpdated = root.attribute("Updated", QDateTime::currentDateTime().toString());
|
||||
|
||||
QDomElement machine=root.firstChild().toElement();
|
||||
// Loop while there is a child
|
||||
while(!machine.isNull())
|
||||
{
|
||||
// Check if the child tag name is COMPONENT
|
||||
if (machine.tagName()=="Treadmill")
|
||||
{
|
||||
// Read and display the component ID
|
||||
double speed = machine.attribute("Speed", "0.0").toDouble();
|
||||
double inclination = machine.attribute("Incline", "0.0").toDouble();
|
||||
|
||||
((domyostreadmill*)device())->setLastSpeed(speed);
|
||||
((domyostreadmill*)device())->setLastInclination(inclination);
|
||||
}
|
||||
|
||||
// Next component
|
||||
machine = machine.nextSibling().toElement();
|
||||
}
|
||||
|
||||
log->close();
|
||||
}
|
||||
|
||||
void bluetooth::stateFileUpdate()
|
||||
{
|
||||
if(!device()) return;
|
||||
if(device()->deviceType() != bluetoothdevice::TREADMILL) return;
|
||||
|
||||
QFile* log;
|
||||
QDomDocument docStatus;
|
||||
QDomElement docRoot;
|
||||
QDomElement docTreadmill;
|
||||
QDomElement docHeart;
|
||||
log = new QFile("status.xml");
|
||||
if(!log->open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
qDebug() << "Open status.xml for writing failed";
|
||||
return;
|
||||
}
|
||||
docRoot = docStatus.createElement("Gym");
|
||||
docStatus.appendChild(docRoot);
|
||||
docTreadmill = docStatus.createElement("Treadmill");
|
||||
docTreadmill.setAttribute("Speed", QString::number(device()->currentSpeed(), 'f', 1));
|
||||
docTreadmill.setAttribute("Incline", QString::number(((treadmill*)device())->currentInclination(), 'f', 1));
|
||||
docRoot.appendChild(docTreadmill);
|
||||
//docHeart = docStatus.createElement("Heart");
|
||||
//docHeart.setAttribute("Rate", QString::number(currentHeart));
|
||||
//docRoot.appendChild(docHeart);
|
||||
docRoot.setAttribute("Updated", QDateTime::currentDateTime().toString());
|
||||
QTextStream stream(log);
|
||||
stream << docStatus.toString();
|
||||
log->flush();
|
||||
log->close();
|
||||
}
|
||||
|
||||
void bluetooth::speedChanged(double speed)
|
||||
{
|
||||
Q_UNUSED(speed);
|
||||
stateFileUpdate();
|
||||
}
|
||||
|
||||
void bluetooth::inclinationChanged(double inclination)
|
||||
{
|
||||
Q_UNUSED(inclination);
|
||||
stateFileUpdate();
|
||||
}
|
||||
|
||||
@@ -20,13 +20,15 @@
|
||||
#include "domyosbike.h"
|
||||
#include "trxappgateusbtreadmill.h"
|
||||
#include "toorxtreadmill.h"
|
||||
#include "echelonconnectsport.h"
|
||||
#include "bluetoothdevice.h"
|
||||
#include "signalhandler.h"
|
||||
|
||||
class bluetooth : public QObject
|
||||
class bluetooth : public QObject, public SignalHandler
|
||||
{
|
||||
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);
|
||||
explicit bluetooth(bool logs, QString deviceName = "", bool noWriteResistance = false, bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false, bool testResistance = false, uint8_t bikeResistanceOffset = 4, uint8_t bikeResistanceGain = 1);
|
||||
bluetoothdevice* device();
|
||||
|
||||
private:
|
||||
@@ -36,6 +38,7 @@ private:
|
||||
domyosbike* domyosBike = 0;
|
||||
toorxtreadmill* toorx = 0;
|
||||
trxappgateusbtreadmill* trxappgateusb = 0;
|
||||
echelonconnectsport* echelonConnectSport = 0;
|
||||
QString filterDevice = "";
|
||||
bool testResistance = false;
|
||||
bool noWriteResistance = false;
|
||||
@@ -43,10 +46,17 @@ private:
|
||||
bool noConsole = false;
|
||||
bool logs = true;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
uint8_t bikeResistanceGain = 1;
|
||||
|
||||
bool handleSignal(int signal);
|
||||
void stateFileUpdate();
|
||||
void stateFileRead();
|
||||
|
||||
signals:
|
||||
void deviceConnected();
|
||||
void deviceFound(QString name);
|
||||
void searchingStop();
|
||||
|
||||
public slots:
|
||||
void restart();
|
||||
@@ -54,6 +64,9 @@ public slots:
|
||||
|
||||
private slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
void canceled();
|
||||
void speedChanged(double);
|
||||
void inclinationChanged(double);
|
||||
|
||||
signals:
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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 ); }
|
||||
QTime bluetoothdevice::currentPace(){ if(Speed == 0) return QTime(0,0,0,0); else 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; };
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define BLUETOOTHDEVICE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QBluetoothDeviceInfo>
|
||||
|
||||
class bluetoothdevice : public QObject
|
||||
{
|
||||
@@ -18,6 +19,7 @@ public:
|
||||
virtual void* VirtualDevice();
|
||||
uint16_t watts(double weight=75.0);
|
||||
virtual bool changeFanSpeed(uint8_t speed);
|
||||
QBluetoothDeviceInfo bluetoothDevice;
|
||||
|
||||
enum BLUETOOTH_TYPE {
|
||||
UNKNOWN = 0,
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance)
|
||||
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, uint8_t bikeResistanceOffset, uint8_t bikeResistanceGain)
|
||||
{
|
||||
refresh = new QTimer(this);
|
||||
|
||||
this->testResistance = testResistance;
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
|
||||
initDone = false;
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
refresh->start(200);
|
||||
@@ -19,10 +23,13 @@ domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testRes
|
||||
void domyosbike::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
|
||||
{
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
if(wait_for_response)
|
||||
{
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
timeout.singleShot(300, &loop, SLOT(quit()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -36,6 +43,9 @@ void domyosbike::writeCharacteristic(uint8_t* data, uint8_t data_len, QString in
|
||||
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
|
||||
|
||||
loop.exec();
|
||||
|
||||
if(timeout.isActive() == false)
|
||||
debug(" exit for timeout");
|
||||
}
|
||||
|
||||
void domyosbike::updateDisplay(uint16_t elapsed)
|
||||
@@ -65,8 +75,8 @@ void domyosbike::updateDisplay(uint16_t elapsed)
|
||||
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) );
|
||||
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed), false, false );
|
||||
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed), false, true );
|
||||
|
||||
//if(bike_type == CHANG_YOW)
|
||||
{
|
||||
@@ -82,8 +92,8 @@ void domyosbike::updateDisplay(uint16_t elapsed)
|
||||
display2[26] += display2[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(display2, 20, "updateDisplay2");
|
||||
writeCharacteristic(&display2[20], sizeof (display2) - 20, "updateDisplay2");
|
||||
writeCharacteristic(display2, 20, "updateDisplay2", false, false);
|
||||
writeCharacteristic(&display2[20], sizeof (display2) - 20, "updateDisplay2", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +139,7 @@ void domyosbike::update()
|
||||
//else
|
||||
// btinit_telink(false);
|
||||
}
|
||||
else if(btbike.isValid() &&
|
||||
else if(bluetoothDevice.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
@@ -141,6 +151,18 @@ void domyosbike::update()
|
||||
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
|
||||
lastTime = current;
|
||||
|
||||
// ******************************************* virtual bike init *************************************
|
||||
static uint8_t firstVirtual = 0;
|
||||
if(!firstVirtual && searchStopped)
|
||||
{
|
||||
debug("creating virtual bike interface...");
|
||||
virtualBike = new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
connect(virtualBike,&virtualbike::debug ,this,&domyosbike::debug);
|
||||
firstVirtual = 1;
|
||||
}
|
||||
// ********************************************************************************************************
|
||||
|
||||
|
||||
// updating the treadmill console every second
|
||||
if(sec1++ == (500 / refresh->interval()))
|
||||
{
|
||||
@@ -394,17 +416,6 @@ void domyosbike::stateChanged(QLowEnergyService::ServiceState state)
|
||||
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);
|
||||
@@ -412,6 +423,11 @@ void domyosbike::stateChanged(QLowEnergyService::ServiceState state)
|
||||
}
|
||||
}
|
||||
|
||||
void domyosbike::searchingStop()
|
||||
{
|
||||
searchStopped = true;
|
||||
}
|
||||
|
||||
void domyosbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
|
||||
{
|
||||
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
|
||||
@@ -455,7 +471,7 @@ 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;
|
||||
bluetoothDevice = device;
|
||||
|
||||
if(device.address().toString().startsWith("57"))
|
||||
{
|
||||
@@ -468,7 +484,7 @@ void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
bike_type = CHANG_YOW;
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(btbike, this);
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
|
||||
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
|
||||
connect(m_control, SIGNAL(discoveryFinished()),
|
||||
@@ -481,6 +497,7 @@ void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
debug("Cannot connect to remote device.");
|
||||
searchStopped = false;
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
@@ -491,6 +508,7 @@ void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
debug("LowEnergy controller disconnected");
|
||||
searchStopped = false;
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
@@ -516,3 +534,108 @@ void* domyosbike::VirtualDevice()
|
||||
{
|
||||
return VirtualBike();
|
||||
}
|
||||
|
||||
uint16_t domyosbike::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;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class domyosbike : public bike
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyosbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false);
|
||||
domyosbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false, uint8_t bikeResistanceOffset = 4, uint8_t bikeResistanceGain = 1);
|
||||
bool connected();
|
||||
|
||||
void* VirtualBike();
|
||||
@@ -49,11 +49,11 @@ private:
|
||||
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();
|
||||
uint16_t watts();
|
||||
|
||||
QTimer* refresh;
|
||||
virtualbike* virtualBike = 0;
|
||||
|
||||
QBluetoothDeviceInfo btbike;
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
@@ -64,6 +64,9 @@ private:
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool testResistance = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
uint8_t bikeResistanceGain = 1;
|
||||
bool searchStopped = false;
|
||||
|
||||
enum _BIKE_TYPE {
|
||||
CHANG_YOW,
|
||||
@@ -77,6 +80,7 @@ signals:
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
void searchingStop();
|
||||
|
||||
private slots:
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
|
||||
static double lastSpeed = 0.0;
|
||||
static double lastInclination = 0;
|
||||
|
||||
// set speed and incline to 0
|
||||
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
|
||||
uint8_t initData2[] = { 0xf0, 0xc9, 0xb9 };
|
||||
@@ -49,7 +52,6 @@ QBluetoothUuid _gattCommunicationChannelServiceId((QString)"49535343-fe7d-4ae5-8
|
||||
QBluetoothUuid _gattWriteCharacteristicId((QString)"49535343-8841-43f4-a8d4-ecbe34729bb3");
|
||||
QBluetoothUuid _gattNotifyCharacteristicId((QString)"49535343-1e4d-4bd9-ba61-23c647249616");
|
||||
|
||||
QBluetoothDeviceInfo bttreadmill;
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
@@ -58,10 +60,17 @@ QLowEnergyCharacteristic gattNotifyCharacteristic;
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService)
|
||||
domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed, double forceInitInclination)
|
||||
{
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
if(forceInitSpeed > 0)
|
||||
lastSpeed = forceInitSpeed;
|
||||
|
||||
if(forceInitInclination > 0)
|
||||
lastInclination = forceInitInclination;
|
||||
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
@@ -71,10 +80,13 @@ domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool n
|
||||
void domyostreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
|
||||
{
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
if(wait_for_response)
|
||||
{
|
||||
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
|
||||
&loop, SLOT(quit()));
|
||||
timeout.singleShot(300, &loop, SLOT(quit()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -88,6 +100,9 @@ void domyostreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, QStri
|
||||
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
|
||||
|
||||
loop.exec();
|
||||
|
||||
if(timeout.isActive() == false)
|
||||
debug(" exit for timeout");
|
||||
}
|
||||
|
||||
void domyostreadmill::updateDisplay(uint16_t elapsed)
|
||||
@@ -136,8 +151,8 @@ void domyostreadmill::updateDisplay(uint16_t elapsed)
|
||||
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) );
|
||||
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed), false, false );
|
||||
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed), false, true );
|
||||
}
|
||||
|
||||
void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline)
|
||||
@@ -161,8 +176,8 @@ void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestInc
|
||||
//qDebug() << "writeIncline crc" << QString::number(writeIncline[26], 16);
|
||||
|
||||
|
||||
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));
|
||||
writeCharacteristic(writeIncline, 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline), false, false);
|
||||
writeCharacteristic(&writeIncline[20], sizeof (writeIncline) - 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline), false, true);
|
||||
}
|
||||
|
||||
bool domyostreadmill::changeFanSpeed(uint8_t speed)
|
||||
@@ -178,7 +193,7 @@ bool domyostreadmill::changeFanSpeed(uint8_t speed)
|
||||
fanSpeed[3] += fanSpeed[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(fanSpeed, 4, "changeFanSpeed speed=" + QString::number(speed));
|
||||
writeCharacteristic(fanSpeed, 4, "changeFanSpeed speed=" + QString::number(speed), false, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -199,18 +214,32 @@ void domyostreadmill::update()
|
||||
if(initRequest)
|
||||
{
|
||||
initRequest = false;
|
||||
btinit(false);
|
||||
btinit((lastSpeed > 0 ? true : false));
|
||||
}
|
||||
else if(bttreadmill.isValid() &&
|
||||
else if(bluetoothDevice.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
gattNotifyCharacteristic.isValid() &&
|
||||
initDone)
|
||||
{
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
static uint8_t firstInit = 0;
|
||||
if(!firstInit && searchStopped)
|
||||
{
|
||||
debug("creating virtual treadmill interface...");
|
||||
virtualTreadMill = new virtualtreadmill(this, noHeartService);
|
||||
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&domyostreadmill::debug);
|
||||
firstInit = 1;
|
||||
}
|
||||
// ********************************************************************************************************
|
||||
|
||||
debug("Domyos Treadmill RSSI " + QString::number(bluetoothDevice.rssi()));
|
||||
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
double deltaTime = (((double)lastTime.msecsTo(current)) / ((double)1000.0));
|
||||
if(currentSpeed() > 0.0 && !first)
|
||||
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
|
||||
elapsed += deltaTime;
|
||||
lastTime = current;
|
||||
|
||||
// updating the treadmill console every second
|
||||
@@ -263,6 +292,8 @@ void domyostreadmill::update()
|
||||
if(requestStart != -1)
|
||||
{
|
||||
debug("starting...");
|
||||
if(lastSpeed == 0.0)
|
||||
lastSpeed = 0.5;
|
||||
btinit(true);
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
@@ -287,7 +318,7 @@ void domyostreadmill::update()
|
||||
}
|
||||
}
|
||||
|
||||
elevationAcc += (currentSpeed() / 3600.0) * 1000 * (currentInclination() / 100) * (refresh->interval() / 1000);
|
||||
elevationAcc += (currentSpeed() / 3600.0) * 1000.0 * (currentInclination() / 100.0) * deltaTime;
|
||||
}
|
||||
|
||||
first = false;
|
||||
@@ -390,11 +421,26 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
|
||||
if(m_control->error() != QLowEnergyController::NoError)
|
||||
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
|
||||
|
||||
Speed = speed;
|
||||
Inclination = incline;
|
||||
if(Speed != speed)
|
||||
{
|
||||
Speed = speed;
|
||||
emit speedChanged(speed);
|
||||
}
|
||||
if(Inclination != incline)
|
||||
{
|
||||
Inclination = incline;
|
||||
emit inclinationChanged(incline);
|
||||
}
|
||||
|
||||
KCal = kcal;
|
||||
Distance = distance;
|
||||
|
||||
if(speed > 0)
|
||||
{
|
||||
lastSpeed = speed;
|
||||
lastInclination = incline;
|
||||
}
|
||||
|
||||
lastTime = QDateTime::currentDateTime();
|
||||
first = false;
|
||||
}
|
||||
@@ -422,7 +468,12 @@ double domyostreadmill::GetDistanceFromPacket(QByteArray packet)
|
||||
double domyostreadmill::GetInclinationFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(2) << 8) | packet.at(3);
|
||||
double data = ((double)convertedData - 1000.0f) / 10.0f;
|
||||
double data;
|
||||
|
||||
if(convertedData > 10000)
|
||||
data = ((double)convertedData - 65512.0f) / 10.0f;
|
||||
else
|
||||
data = ((double)convertedData - 1000.0f) / 10.0f;
|
||||
if (data < 0) return 0;
|
||||
return data;
|
||||
}
|
||||
@@ -436,16 +487,20 @@ void domyostreadmill::btinit(bool startTape)
|
||||
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(initDataStart6, sizeof(initDataStart6), "init", false, false);
|
||||
//writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
|
||||
forceSpeedOrIncline(lastSpeed, lastInclination);
|
||||
|
||||
writeCharacteristic(initDataStart8, sizeof(initDataStart8), "init", false, false);
|
||||
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
|
||||
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
|
||||
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
|
||||
if(startTape)
|
||||
{
|
||||
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
|
||||
writeCharacteristic(initDataStart11, sizeof(initDataStart11), "init", false, true);
|
||||
writeCharacteristic(initDataStart12, sizeof(initDataStart12), "init", false, false);
|
||||
writeCharacteristic(initDataStart13, sizeof(initDataStart13), "init", false, true);
|
||||
|
||||
forceSpeedOrIncline(lastSpeed, lastInclination);
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
@@ -474,17 +529,6 @@ void domyostreadmill::stateChanged(QLowEnergyService::ServiceState state)
|
||||
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;
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
@@ -532,8 +576,8 @@ void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge"))
|
||||
{
|
||||
bttreadmill = device;
|
||||
m_control = QLowEnergyController::createCentral(bttreadmill, this);
|
||||
bluetoothDevice = device;
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
|
||||
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
|
||||
connect(m_control, SIGNAL(discoveryFinished()),
|
||||
@@ -546,6 +590,7 @@ void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
debug("Cannot connect to remote device.");
|
||||
searchStopped = false;
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
@@ -556,6 +601,7 @@ void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
debug("LowEnergy controller disconnected");
|
||||
searchStopped = false;
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
@@ -586,3 +632,18 @@ double domyostreadmill::odometer()
|
||||
{
|
||||
return DistanceCalculated;
|
||||
}
|
||||
|
||||
void domyostreadmill::setLastSpeed(double speed)
|
||||
{
|
||||
lastSpeed = speed;
|
||||
}
|
||||
|
||||
void domyostreadmill::setLastInclination(double inclination)
|
||||
{
|
||||
lastInclination = inclination;
|
||||
}
|
||||
|
||||
void domyostreadmill::searchingStop()
|
||||
{
|
||||
searchStopped = true;
|
||||
}
|
||||
|
||||
@@ -31,11 +31,14 @@ class domyostreadmill : public treadmill
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyostreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false);
|
||||
domyostreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false, double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
|
||||
bool connected();
|
||||
bool changeFanSpeed(uint8_t speed);
|
||||
double odometer();
|
||||
|
||||
void setLastSpeed(double speed);
|
||||
void setLastInclination(double inclination);
|
||||
|
||||
void* VirtualTreadMill();
|
||||
void* VirtualDevice();
|
||||
|
||||
@@ -54,6 +57,7 @@ private:
|
||||
bool noConsole = false;
|
||||
bool noHeartService = false;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
bool searchStopped = false;
|
||||
|
||||
QTimer* refresh;
|
||||
virtualtreadmill* virtualTreadMill = 0;
|
||||
@@ -61,9 +65,12 @@ private:
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
void speedChanged(double speed);
|
||||
void inclinationChanged(double inclination);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
void searchingStop();
|
||||
|
||||
private slots:
|
||||
|
||||
|
||||
387
src/echelonconnectsport.cpp
Normal file
387
src/echelonconnectsport.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include "echelonconnectsport.h"
|
||||
#include "virtualbike.h"
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMetaEnum>
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <math.h>
|
||||
|
||||
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService)
|
||||
{
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
initDone = false;
|
||||
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
|
||||
refresh->start(200);
|
||||
}
|
||||
|
||||
void echelonconnectsport::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 echelonconnectsport::sendPoll()
|
||||
{
|
||||
static uint8_t counter = 1;
|
||||
uint8_t noOpData[] = { 0xf0, 0xa0, 0x01, 0x00, 0x00 };
|
||||
|
||||
noOpData[3] = counter;
|
||||
|
||||
for(uint8_t i=0; i<sizeof(noOpData)-1; i++)
|
||||
{
|
||||
noOpData[4] += noOpData[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(noOpData, sizeof(noOpData), "noOp", true);
|
||||
|
||||
counter++;
|
||||
if(!counter)
|
||||
counter = 1;
|
||||
}
|
||||
|
||||
void echelonconnectsport::update()
|
||||
{
|
||||
static QDateTime lastTime;
|
||||
static bool first = true;
|
||||
|
||||
static uint8_t sec1 = 0;
|
||||
|
||||
if(m_control->state() == QLowEnergyController::UnconnectedState)
|
||||
{
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if(initRequest)
|
||||
{
|
||||
initRequest = false;
|
||||
btinit();
|
||||
}
|
||||
else if(bluetoothDevice.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
gattNotify1Characteristic.isValid() &&
|
||||
gattNotify2Characteristic.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);
|
||||
}
|
||||
|
||||
sendPoll();
|
||||
|
||||
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...");
|
||||
|
||||
btinit();
|
||||
|
||||
requestStart = -1;
|
||||
emit bikeStarted();
|
||||
}
|
||||
if(requestStop != -1)
|
||||
{
|
||||
debug("stopping...");
|
||||
//writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
void echelonconnectsport::serviceDiscovered(const QBluetoothUuid &gatt)
|
||||
{
|
||||
debug("serviceDiscovered " + gatt.toString());
|
||||
}
|
||||
|
||||
static QByteArray lastPacket;
|
||||
void echelonconnectsport::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;
|
||||
|
||||
// resistance value is in another frame
|
||||
if(newValue.length() == 5 && newValue.at(0) == 0xf0 && newValue.at(1) == 0xd2)
|
||||
{
|
||||
Resistance = newValue.at(3);
|
||||
debug("Current resistance: " + QString::number(Resistance));
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue.length() != 13)
|
||||
return;
|
||||
|
||||
/*if ((uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd1)
|
||||
return;*/
|
||||
|
||||
double distance = GetDistanceFromPacket(newValue);
|
||||
|
||||
Cadence = newValue.at(10);
|
||||
Speed = 0.37497622 * ((double)Cadence);
|
||||
KCal = 0;
|
||||
Distance += ((Speed / 3600000.0) * ((double)lastRefresh.msecsTo(QDateTime::currentDateTime())) );
|
||||
|
||||
double cadenceAdd = ((double)(lastRefresh.msecsTo(QDateTime::currentDateTime())) * ((double)Cadence / 60000.0) );
|
||||
static double crankRevsLocal = 0;
|
||||
static uint16_t lastCrankEventTimeLocal = 0;
|
||||
|
||||
crankRevsLocal += cadenceAdd;
|
||||
lastCrankEventTimeLocal += (uint16_t)((lastRefresh.msecsTo(QDateTime::currentDateTime())) * 1.024);
|
||||
lastRefresh = QDateTime::currentDateTime();
|
||||
|
||||
// crank rotation done
|
||||
if(((uint16_t)(crankRevsLocal)) > ((uint16_t)(CrankRevs)))
|
||||
{
|
||||
CrankRevs = crankRevsLocal;
|
||||
LastCrankEventTime = lastCrankEventTimeLocal;
|
||||
}
|
||||
|
||||
debug("Current Local elapsed: " + GetElapsedFromPacket(newValue).toString());
|
||||
debug("Current Speed: " + QString::number(Speed));
|
||||
debug("Current Calculate Distance: " + QString::number(Distance));
|
||||
debug("Current Cadence: " + QString::number(Cadence));
|
||||
debug("Current Distance: " + QString::number(distance));
|
||||
debug("Local Current CrankRevs: " + QString::number(crankRevsLocal));
|
||||
debug("Local Last CrankEventTime: " + QString::number(lastCrankEventTimeLocal));
|
||||
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();
|
||||
}
|
||||
|
||||
QTime echelonconnectsport::GetElapsedFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(3) << 8) | packet.at(4);
|
||||
QTime t(0,convertedData / 60, convertedData % 60);
|
||||
return t;
|
||||
}
|
||||
|
||||
double echelonconnectsport::GetDistanceFromPacket(QByteArray packet)
|
||||
{
|
||||
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
|
||||
double data = ((double)convertedData) / 100.0f;
|
||||
return data;
|
||||
}
|
||||
|
||||
void echelonconnectsport::btinit()
|
||||
{
|
||||
uint8_t initData1[] = { 0xf0, 0xa1, 0x00, 0x91 };
|
||||
uint8_t initData2[] = { 0xf0, 0xa3, 0x00, 0x93 };
|
||||
uint8_t initData3[] = { 0xf0, 0xb0, 0x01, 0x01, 0xa2 };
|
||||
|
||||
// in the snoof log it repeats this frame 4 times, i will have to analyze the response to understand if 4 times are enough
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
|
||||
writeCharacteristic(initData2, sizeof(initData2), "init", false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
|
||||
writeCharacteristic(initData3, sizeof(initData3), "init", false, true);
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state)
|
||||
{
|
||||
QBluetoothUuid _gattWriteCharacteristicId((QString)"0bf669f2-45f2-11e7-9598-0800200c9a66");
|
||||
QBluetoothUuid _gattNotify1CharacteristicId((QString)"0bf669f3-45f2-11e7-9598-0800200c9a66");
|
||||
QBluetoothUuid _gattNotify2CharacteristicId((QString)"0bf669f4-45f2-11e7-9598-0800200c9a66");
|
||||
|
||||
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);
|
||||
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
|
||||
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
Q_ASSERT(gattNotify1Characteristic.isValid());
|
||||
Q_ASSERT(gattNotify2Characteristic.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,&echelonconnectsport::debug);
|
||||
}
|
||||
first = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattCommunicationChannelService->writeDescriptor(gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
gattCommunicationChannelService->writeDescriptor(gattNotify2Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
void echelonconnectsport::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
|
||||
{
|
||||
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
}
|
||||
|
||||
void echelonconnectsport::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
|
||||
{
|
||||
Q_UNUSED(characteristic);
|
||||
debug("characteristicWritten " + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void echelonconnectsport::serviceScanDone(void)
|
||||
{
|
||||
debug("serviceScanDone");
|
||||
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((QString)"0bf669f1-45f2-11e7-9598-0800200c9a66");
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
|
||||
void echelonconnectsport::errorService(QLowEnergyService::ServiceError err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
debug("echelonconnectsport::errorService" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
}
|
||||
|
||||
void echelonconnectsport::error(QLowEnergyController::Error err)
|
||||
{
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
debug("echelonconnectsport::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
|
||||
|
||||
m_control->disconnect();
|
||||
}
|
||||
|
||||
void echelonconnectsport::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
{
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("ECH-SPORT"))
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, 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 echelonconnectsport::connected()
|
||||
{
|
||||
if(!m_control)
|
||||
return false;
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void* echelonconnectsport::VirtualBike()
|
||||
{
|
||||
return virtualBike;
|
||||
}
|
||||
|
||||
void* echelonconnectsport::VirtualDevice()
|
||||
{
|
||||
return VirtualBike();
|
||||
}
|
||||
|
||||
uint16_t echelonconnectsport::watts()
|
||||
{
|
||||
if(currentCadence() == 0) return 0;
|
||||
|
||||
// https://github.com/cagnulein/qdomyos-zwift/issues/62#issuecomment-736913564
|
||||
if(currentCadence() < 90)
|
||||
return (uint16_t)((3.59 * exp(0.0217 * (double)(currentCadence()))) * exp(0.095 * (double)(currentResistance())) );
|
||||
else
|
||||
return (uint16_t)((3.59 * exp(0.0217 * (double)(currentCadence()))) * exp(0.088 * (double)(currentResistance())) );
|
||||
}
|
||||
87
src/echelonconnectsport.h
Normal file
87
src/echelonconnectsport.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef ECHELONCONNECTSPORT_H
|
||||
#define ECHELONCONNECTSPORT_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 echelonconnectsport : public bike
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
echelonconnectsport(bool noWriteResistance, bool noHeartService);
|
||||
bool connected();
|
||||
|
||||
void* VirtualBike();
|
||||
void* VirtualDevice();
|
||||
|
||||
private:
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
QTime GetElapsedFromPacket(QByteArray packet);
|
||||
void btinit();
|
||||
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log=false, bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
void sendPoll();
|
||||
uint16_t watts();
|
||||
|
||||
QTimer* refresh;
|
||||
virtualbike* virtualBike = 0;
|
||||
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyCharacteristic gattNotify2Characteristic;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = 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 // ECHELONCONNECTSPORT_H
|
||||
221
src/homeform.cpp
221
src/homeform.cpp
@@ -1,6 +1,9 @@
|
||||
#include "homeform.h"
|
||||
#include <QQmlContext>
|
||||
#include <QTime>
|
||||
#include <QSettings>
|
||||
#include <QQmlFile>
|
||||
#include "gpx.h"
|
||||
|
||||
DataObject::DataObject(QString name, QString icon, QString value, bool writable, QString id)
|
||||
{
|
||||
@@ -15,47 +18,125 @@ DataObject::DataObject(QString name, QString icon, QString value, bool writable,
|
||||
}
|
||||
|
||||
void DataObject::setValue(QString v) {m_value = v; emit valueChanged(m_value);}
|
||||
void DataObject::setVisible(bool visible) {m_visible = visible; emit visibleChanged(m_visible);}
|
||||
|
||||
homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
|
||||
{
|
||||
QSettings settings;
|
||||
bool miles = settings.value("miles_unit", false).toBool();
|
||||
QString unit = "km";
|
||||
if(miles)
|
||||
unit = "mi";
|
||||
|
||||
speed = new DataObject("Speed (" + unit + "/h)", "icons/icons/speed.png", "0.0", true, "speed");
|
||||
inclination = new DataObject("Inclination (%)", "icons/icons/inclination.png", "0.0", true, "inclination");
|
||||
cadence = new DataObject("Cadence (bpm)", "icons/icons/cadence.png", "0", false, "cadence");
|
||||
elevation = new DataObject("Elev. Gain (m)", "icons/icons/elevationgain.png", "0", false, "elevation");
|
||||
calories = new DataObject("Calories (KCal)", "icons/icons/kcal.png", "0", false, "calories");
|
||||
odometer = new DataObject("Odometer (" + unit + ")", "icons/icons/odometer.png", "0.0", false, "odometer");
|
||||
pace = new DataObject("Pace (m/km)", "icons/icons/pace.png", "0:00", false, "pace");
|
||||
resistance = new DataObject("Resistance (%)", "icons/icons/resistance.png", "0", true, "resistance");
|
||||
watt = new DataObject("Watt", "icons/icons/watt.png", "0", false, "watt");
|
||||
heart = new DataObject("Heart (bpm)", "icons/icons/heart_red.png", "0", false, "heart");
|
||||
fan = new DataObject("Fan Speed", "icons/icons/fan.png", "0", true, "fan");
|
||||
|
||||
this->bluetoothManager = bl;
|
||||
this->engine = engine;
|
||||
connect(bluetoothManager, SIGNAL(deviceFound(QString)), this, SLOT(deviceFound(QString)));
|
||||
connect(bluetoothManager, SIGNAL(deviceConnected()), this, SLOT(deviceConnected()));
|
||||
connect(bluetoothManager, SIGNAL(deviceConnected()), this, SLOT(trainProgramSignals()));
|
||||
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)));
|
||||
this->trainProgram = new trainprogram(QList<trainrow>(), bl);
|
||||
|
||||
timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &homeform::update);
|
||||
timer->start(1000);
|
||||
}
|
||||
|
||||
void homeform::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 homeform::deviceConnected()
|
||||
{
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
dataList = {
|
||||
speed,
|
||||
inclination,
|
||||
elevation,
|
||||
calories,
|
||||
odometer,
|
||||
pace,
|
||||
watt,
|
||||
heart,
|
||||
fan
|
||||
};
|
||||
|
||||
}
|
||||
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
{
|
||||
dataList = {
|
||||
speed,
|
||||
cadence,
|
||||
elevation,
|
||||
calories,
|
||||
odometer,
|
||||
resistance,
|
||||
watt,
|
||||
heart,
|
||||
fan
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
engine->rootContext()->setContextProperty("appModel", QVariant::fromValue(dataList));
|
||||
|
||||
QObject *rootObject = engine->rootObjects().first();
|
||||
QObject *home = rootObject->findChild<QObject*>("home");
|
||||
QObject *stack = rootObject;
|
||||
QObject::connect(home, SIGNAL(start_clicked()),
|
||||
this, SLOT(Start()));
|
||||
QObject::connect(home, SIGNAL(stop_clicked()),
|
||||
this, SLOT(Stop()));
|
||||
QObject::connect(stack, SIGNAL(trainprogram_open_clicked(QUrl)),
|
||||
this, SLOT(trainprogram_open_clicked(QUrl)));
|
||||
QObject::connect(home, SIGNAL(plus_clicked(QString)),
|
||||
this, SLOT(Plus(QString)));
|
||||
QObject::connect(home, SIGNAL(minus_clicked(QString)),
|
||||
this, SLOT(Minus(QString)));
|
||||
}
|
||||
|
||||
void homeform::deviceFound(QString name)
|
||||
{
|
||||
if(!name.trimmed().length()) return;
|
||||
m_info = name + " founded";
|
||||
emit infoChanged(m_info);
|
||||
}
|
||||
@@ -159,6 +240,23 @@ void homeform::Stop()
|
||||
bluetoothManager->device()->stop();
|
||||
}
|
||||
|
||||
QString homeform::signal()
|
||||
{
|
||||
if(!bluetoothManager)
|
||||
return "icons/icons/signal-1.png";
|
||||
|
||||
if(!bluetoothManager->device())
|
||||
return "icons/icons/signal-1.png";
|
||||
|
||||
int16_t rssi = bluetoothManager->device()->bluetoothDevice.rssi();
|
||||
if(rssi > -40)
|
||||
return "icons/icons/signal-3.png";
|
||||
else if(rssi > -60)
|
||||
return "icons/icons/signal-2.png";
|
||||
|
||||
return "icons/icons/signal-1.png";
|
||||
}
|
||||
|
||||
void homeform::update()
|
||||
{
|
||||
if(bluetoothManager->device())
|
||||
@@ -168,16 +266,31 @@ void homeform::update()
|
||||
double watts = 0;
|
||||
double pace = 0;
|
||||
|
||||
speed->setValue(QString::number(bluetoothManager->device()->currentSpeed(), 'f', 2));
|
||||
QSettings settings;
|
||||
bool miles = settings.value("miles_unit", false).toBool();
|
||||
double unit_conversion = 1.0;
|
||||
if(miles)
|
||||
unit_conversion = 0.621371;
|
||||
|
||||
emit signalChanged(signal());
|
||||
|
||||
speed->setValue(QString::number(bluetoothManager->device()->currentSpeed() * unit_conversion, 'f', 2));
|
||||
heart->setValue(QString::number(bluetoothManager->device()->currentHeart()));
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer(), 'f', 2));
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer() * unit_conversion, '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;
|
||||
if(bluetoothManager->device()->currentSpeed())
|
||||
{
|
||||
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
|
||||
if(pace < 0) pace = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
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"));
|
||||
@@ -191,6 +304,7 @@ void homeform::update()
|
||||
watts = ((bike*)bluetoothManager->device())->watts();
|
||||
watt->setValue(QString::number(watts));
|
||||
this->resistance->setValue(QString::number(resistance));
|
||||
this->cadence->setValue(QString::number(((bike*)bluetoothManager->device())->currentCadence()));
|
||||
}
|
||||
/*
|
||||
if(trainProgram)
|
||||
@@ -227,8 +341,13 @@ void homeform::update()
|
||||
|
||||
bool homeform::getDevice()
|
||||
{
|
||||
static bool toggle = false;
|
||||
if(!this->bluetoothManager->device())
|
||||
return false;
|
||||
{
|
||||
// toggling the bluetooth icon
|
||||
toggle = !toggle;
|
||||
return toggle;
|
||||
}
|
||||
return this->bluetoothManager->device()->connected();
|
||||
}
|
||||
|
||||
@@ -250,3 +369,43 @@ bool homeform::getZwift()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void homeform::trainprogram_open_clicked(QUrl fileName)
|
||||
{
|
||||
qDebug() << "trainprogram_open_clicked" << fileName;
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
qDebug() << file.fileName();
|
||||
if(!file.fileName().isEmpty())
|
||||
{
|
||||
if(file.fileName().endsWith("xml"))
|
||||
{
|
||||
if(trainProgram)
|
||||
delete trainProgram;
|
||||
trainProgram = trainprogram::load(file.fileName(), bluetoothManager);
|
||||
}
|
||||
else if(file.fileName().endsWith("gpx"))
|
||||
{
|
||||
if(trainProgram)
|
||||
delete trainProgram;
|
||||
gpx g;
|
||||
QList<trainrow> list;
|
||||
foreach(gpx_altitude_point_for_treadmill p, g.open(file.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;
|
||||
}
|
||||
|
||||
trainProgramSignals();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QQmlApplicationEngine>
|
||||
#include "bluetooth.h"
|
||||
#include "sessionline.h"
|
||||
#include "trainprogram.h"
|
||||
|
||||
class DataObject : public QObject
|
||||
{
|
||||
@@ -14,16 +15,19 @@ class DataObject : public QObject
|
||||
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(bool visibleItem READ visibleItem NOTIFY visibleChanged)
|
||||
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);
|
||||
void setVisible(bool visible);
|
||||
QString name() {return m_name;}
|
||||
QString icon() {return m_icon;}
|
||||
QString value() {return m_value;}
|
||||
bool writable() {return m_writable;}
|
||||
bool visibleItem() {return m_visible;}
|
||||
QString plusName() {return m_id + "_plus";}
|
||||
QString minusName() {return m_id + "_minus";}
|
||||
|
||||
@@ -32,12 +36,14 @@ public:
|
||||
QString m_icon;
|
||||
QString m_value;
|
||||
bool m_writable;
|
||||
bool m_visible = true;
|
||||
|
||||
signals:
|
||||
void valueChanged(QString value);
|
||||
void nameChanged(QString value);
|
||||
void iconChanged(QString value);
|
||||
void writableChanged(bool value);
|
||||
void visibleChanged(bool value);
|
||||
void plusNameChanged(QString value);
|
||||
void minusNameChanged(QString value);
|
||||
};
|
||||
@@ -48,29 +54,33 @@ class homeform: public QObject
|
||||
Q_PROPERTY( bool device READ getDevice NOTIFY changeOfdevice)
|
||||
Q_PROPERTY( bool zwift READ getZwift NOTIFY changeOfzwift)
|
||||
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
|
||||
Q_PROPERTY(QString signal READ signal NOTIFY signalChanged)
|
||||
|
||||
public:
|
||||
homeform(QQmlApplicationEngine* engine, bluetooth* bl);
|
||||
QString info() {return m_info;}
|
||||
QString signal();
|
||||
|
||||
private:
|
||||
QList<QObject *> dataList;
|
||||
QList<SessionLine> Session;
|
||||
bluetooth* bluetoothManager;
|
||||
bluetooth* bluetoothManager = 0;
|
||||
QQmlApplicationEngine* engine;
|
||||
trainprogram* trainProgram = 0;
|
||||
|
||||
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");
|
||||
DataObject* speed;
|
||||
DataObject* inclination;
|
||||
DataObject* cadence;
|
||||
DataObject* elevation;
|
||||
DataObject* calories;
|
||||
DataObject* odometer;
|
||||
DataObject* pace;
|
||||
DataObject* resistance;
|
||||
DataObject* watt;
|
||||
DataObject* heart;
|
||||
DataObject* fan;
|
||||
|
||||
QTimer* timer;
|
||||
|
||||
@@ -84,10 +94,14 @@ private slots:
|
||||
void Minus(QString);
|
||||
void Plus(QString);
|
||||
void deviceFound(QString name);
|
||||
void deviceConnected();
|
||||
void trainprogram_open_clicked(QUrl fileName);
|
||||
void trainProgramSignals();
|
||||
|
||||
signals:
|
||||
void changeOfdevice();
|
||||
void changeOfzwift();
|
||||
void signalChanged(QString value);
|
||||
void infoChanged(QString value);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
<file>icons/pace.png</file>
|
||||
<file>icons/chart.png</file>
|
||||
<file>icons/icon.png</file>
|
||||
<file>icons/signal-0.png</file>
|
||||
<file>icons/signal-1.png</file>
|
||||
<file>icons/signal-2.png</file>
|
||||
<file>icons/signal-3.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
src/icons/signal-0.png
Normal file
BIN
src/icons/signal-0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 429 B |
BIN
src/icons/signal-1.png
Normal file
BIN
src/icons/signal-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 423 B |
BIN
src/icons/signal-2.png
Normal file
BIN
src/icons/signal-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 412 B |
BIN
src/icons/signal-3.png
Normal file
BIN
src/icons/signal-3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 389 B |
47
src/keepawakehelper.cpp
Normal file
47
src/keepawakehelper.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <QDebug>
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QAndroidJniObject>
|
||||
#include "keepawakehelper.h"
|
||||
#include "jni.h"
|
||||
|
||||
KeepAwakeHelper::KeepAwakeHelper()
|
||||
{
|
||||
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
|
||||
if ( activity.isValid() )
|
||||
{
|
||||
QAndroidJniObject serviceName = QAndroidJniObject::getStaticObjectField<jstring>("android/content/Context","POWER_SERVICE");
|
||||
if ( serviceName.isValid() )
|
||||
{
|
||||
QAndroidJniObject powerMgr = activity.callObjectMethod("getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;",serviceName.object<jobject>());
|
||||
if ( powerMgr.isValid() )
|
||||
{
|
||||
jint levelAndFlags = QAndroidJniObject::getStaticField<jint>("android/os/PowerManager","SCREEN_DIM_WAKE_LOCK");
|
||||
|
||||
QAndroidJniObject tag = QAndroidJniObject::fromString( "My Tag" );
|
||||
|
||||
m_wakeLock = powerMgr.callObjectMethod("newWakeLock", "(ILjava/lang/String;)Landroid/os/PowerManager$WakeLock;", levelAndFlags,tag.object<jstring>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( m_wakeLock.isValid() )
|
||||
{
|
||||
m_wakeLock.callMethod<void>("acquire", "()V");
|
||||
qDebug() << "Locked device, can't go to standby anymore";
|
||||
}
|
||||
else
|
||||
{
|
||||
assert( false );
|
||||
}
|
||||
}
|
||||
|
||||
KeepAwakeHelper::~KeepAwakeHelper()
|
||||
{
|
||||
if ( m_wakeLock.isValid() )
|
||||
{
|
||||
m_wakeLock.callMethod<void>("release", "()V");
|
||||
qDebug() << "Unlocked device, can now go to standby";
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
18
src/keepawakehelper.h
Normal file
18
src/keepawakehelper.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef KEEPAWAKEHELPER_H
|
||||
#define KEEPAWAKEHELPER_H
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
#include <QAndroidJniObject>
|
||||
|
||||
class KeepAwakeHelper
|
||||
{
|
||||
public:
|
||||
KeepAwakeHelper();
|
||||
virtual ~KeepAwakeHelper();
|
||||
|
||||
private:
|
||||
QAndroidJniObject m_wakeLock;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // KEEPAWAKEHELPER_H
|
||||
132
src/main.cpp
132
src/main.cpp
@@ -2,15 +2,22 @@
|
||||
#include <QStyleFactory>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h> // getuid
|
||||
#include <QStandardPaths>
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QSettings>
|
||||
#include "virtualtreadmill.h"
|
||||
#include "domyostreadmill.h"
|
||||
#include "bluetooth.h"
|
||||
#include "mainwindow.h"
|
||||
#include "homeform.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QtAndroid>
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
|
||||
bool nologs = false;
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = true;
|
||||
@@ -18,9 +25,13 @@ bool noConsole = false;
|
||||
bool onlyVirtualBike = false;
|
||||
bool onlyVirtualTreadmill = false;
|
||||
bool testResistance = false;
|
||||
bool forceQml = false;
|
||||
bool miles = false;
|
||||
QString trainProgram;
|
||||
QString deviceName = "";
|
||||
uint32_t pollDeviceTime = 200;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
uint8_t bikeResistanceGain = 1;
|
||||
static QString logfilename = "debug-" + QDateTime::currentDateTime().toString().replace(":", "_") + ".log";
|
||||
|
||||
QCoreApplication* createApplication(int &argc, char *argv[])
|
||||
@@ -30,6 +41,10 @@ QCoreApplication* createApplication(int &argc, char *argv[])
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!qstrcmp(argv[i], "-no-gui"))
|
||||
nogui = true;
|
||||
if (!qstrcmp(argv[i], "-qml"))
|
||||
forceQml = true;
|
||||
if (!qstrcmp(argv[i], "-miles"))
|
||||
miles = true;
|
||||
if (!qstrcmp(argv[i], "-no-console"))
|
||||
noConsole = true;
|
||||
if (!qstrcmp(argv[i], "-test-resistance"))
|
||||
@@ -58,10 +73,20 @@ QCoreApplication* createApplication(int &argc, char *argv[])
|
||||
{
|
||||
pollDeviceTime = atol(argv[++i]);
|
||||
}
|
||||
if (!qstrcmp(argv[i], "-bike-resistance-gain"))
|
||||
{
|
||||
bikeResistanceGain = atoi(argv[++i]);
|
||||
}
|
||||
if (!qstrcmp(argv[i], "-bike-resistance-offset"))
|
||||
{
|
||||
bikeResistanceOffset = atoi(argv[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
if(nogui)
|
||||
return new QCoreApplication(argc, argv);
|
||||
else if(forceQml)
|
||||
return new QGuiApplication(argc, argv);
|
||||
else
|
||||
{
|
||||
QApplication* a = new QApplication(argc, argv);
|
||||
@@ -143,38 +168,99 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (getuid())
|
||||
{
|
||||
printf("Runme as root!\n");
|
||||
return -1;
|
||||
}
|
||||
else printf("%s", "OK, you are root.\n");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
|
||||
qInstallMessageHandler(myMessageOutput);
|
||||
#else
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QScopedPointer<QGuiApplication> app(new QGuiApplication(argc, argv));
|
||||
#endif
|
||||
|
||||
if(onlyVirtualBike)
|
||||
app->setOrganizationName("Roberto Viola");
|
||||
app->setOrganizationDomain("robertoviola.cloud");
|
||||
app->setApplicationName("qDomyos-Zwift");
|
||||
|
||||
QSettings settings;
|
||||
#ifdef Q_OS_ANDROID
|
||||
noHeartService = settings.value("bike_heartrate_service", !noHeartService).toBool();
|
||||
bikeResistanceOffset = settings.value("bike_resistance_offset", bikeResistanceOffset).toInt();
|
||||
bikeResistanceGain = settings.value("bike_resistance_gain", bikeResistanceGain).toInt();
|
||||
#else
|
||||
settings.setValue("miles_unit", miles);
|
||||
#endif
|
||||
|
||||
qInstallMessageHandler(myMessageOutput);
|
||||
qDebug() << "version 1.3.1";
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
if(!forceQml)
|
||||
{
|
||||
virtualbike* V = new virtualbike(new bike(), noWriteResistance, noHeartService);
|
||||
return app->exec();
|
||||
}
|
||||
else if(onlyVirtualTreadmill)
|
||||
{
|
||||
virtualtreadmill* V = new virtualtreadmill(new treadmill(), noHeartService);
|
||||
return app->exec();
|
||||
if(onlyVirtualBike)
|
||||
{
|
||||
virtualbike* V = new virtualbike(new bike(), noWriteResistance, noHeartService);
|
||||
Q_UNUSED(V)
|
||||
return app->exec();
|
||||
}
|
||||
else if(onlyVirtualTreadmill)
|
||||
{
|
||||
virtualtreadmill* V = new virtualtreadmill(new treadmill(), noHeartService);
|
||||
Q_UNUSED(V)
|
||||
return app->exec();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
bluetooth* bl = new bluetooth(!nologs, deviceName, noWriteResistance, noHeartService, pollDeviceTime, testResistance);
|
||||
bluetooth* bl = new bluetooth(!nologs, deviceName, noWriteResistance, noHeartService, pollDeviceTime, noConsole, testResistance, bikeResistanceOffset, bikeResistanceGain);
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
if(forceQml)
|
||||
#endif
|
||||
{
|
||||
QQmlApplicationEngine engine;
|
||||
const QUrl url(QStringLiteral("qrc:/main.qml"));
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||
qobject_cast<QGuiApplication *>(app.data()), [url](QObject *obj, const QUrl &objUrl) {
|
||||
if (!obj && url == objUrl)
|
||||
QCoreApplication::exit(-1);
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
#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);
|
||||
auto result = QtAndroid::checkPermission(QString("android.permission.WRITE_EXTERNAL_STORAGE"));
|
||||
if(result == QtAndroid::PermissionResult::Denied){
|
||||
QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList({"android.permission.WRITE_EXTERNAL_STORAGE"}));
|
||||
if(resultHash["android.permission.WRITE_EXTERNAL_STORAGE"] == QtAndroid::PermissionResult::Denied)
|
||||
qDebug() << "log unwritable!";
|
||||
}
|
||||
result = QtAndroid::checkPermission(QString("android.permission.READ_EXTERNAL_STORAGE"));
|
||||
if(result == QtAndroid::PermissionResult::Denied){
|
||||
QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList({"android.permission.READ_EXTERNAL_STORAGE"}));
|
||||
if(resultHash["android.permission.READ_EXTERNAL_STORAGE"] == QtAndroid::PermissionResult::Denied)
|
||||
qDebug() << "log unwritable!";
|
||||
}
|
||||
#endif
|
||||
engine.load(url);
|
||||
new homeform(&engine, bl);
|
||||
|
||||
return app.exec();
|
||||
#else
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
KeepAwakeHelper helper;
|
||||
#endif
|
||||
// screen and CPU will stay awake during this section
|
||||
// lock will be released when helper object goes out of scope
|
||||
return app->exec();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (qobject_cast<QApplication *>(app.data())) {
|
||||
// start GUI version...
|
||||
MainWindow* W = 0;
|
||||
|
||||
83
src/main.qml
83
src/main.qml
@@ -1,16 +1,50 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick.Dialogs 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
id: window
|
||||
width: 640
|
||||
height: 480
|
||||
visible: true
|
||||
objectName: "stack"
|
||||
title: qsTr("Stack")
|
||||
|
||||
signal trainprogram_open_clicked(url name)
|
||||
|
||||
Popup {
|
||||
id: popup
|
||||
parent: Overlay.overlay
|
||||
|
||||
x: Math.round((parent.width - width) / 2)
|
||||
y: Math.round((parent.height - height) / 2)
|
||||
width: 380
|
||||
height: 50
|
||||
modal: true
|
||||
focus: true
|
||||
palette.text: "white"
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
enter: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
|
||||
}
|
||||
exit: Transition
|
||||
{
|
||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
|
||||
}
|
||||
Column {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
Label {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Program has been loaded correctly. Press start to begin!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header: ToolBar {
|
||||
contentHeight: toolButton.implicitHeight
|
||||
Material.primary: Material.Purple
|
||||
|
||||
ToolButton {
|
||||
id: toolButton
|
||||
@@ -40,22 +74,55 @@ ApplicationWindow {
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
|
||||
ItemDelegate {
|
||||
text: qsTr("Settings")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("settings.qml")
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
ItemDelegate {
|
||||
id: gpx_open
|
||||
text: qsTr("Open GPX")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
fileDialog.visible = true
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
ItemDelegate {
|
||||
id: trainprogram_open
|
||||
text: qsTr("Open Train Program")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
fileDialog.visible = true
|
||||
drawer.close()
|
||||
}
|
||||
}
|
||||
ItemDelegate {
|
||||
text: qsTr("by Roberto Viola")
|
||||
width: parent.width
|
||||
/* onClicked: {
|
||||
stackView.push("Page1Form.ui.qml")
|
||||
stackView.push("Page2Form.ui.qml")
|
||||
drawer.close()
|
||||
}*/
|
||||
}
|
||||
/* ItemDelegate {
|
||||
text: qsTr("Page 2")
|
||||
width: parent.width
|
||||
onClicked: {
|
||||
stackView.push("Page2Form.ui.qml")
|
||||
drawer.close()
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: "Please choose a file"
|
||||
folder: shortcuts.home
|
||||
onAccepted: {
|
||||
console.log("You chose: " + fileDialog.fileUrl)
|
||||
trainprogram_open_clicked(fileDialog.fileUrl)
|
||||
fileDialog.close()
|
||||
popup.open()
|
||||
}
|
||||
}*/
|
||||
onRejected: {
|
||||
console.log("Canceled")
|
||||
fileDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,15 @@ void MainWindow::update()
|
||||
|
||||
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
|
||||
{
|
||||
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
|
||||
if(pace < 0) pace = 0;
|
||||
if(bluetoothManager->device()->currentSpeed())
|
||||
{
|
||||
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
|
||||
if(pace < 0) pace = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
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"));
|
||||
@@ -80,6 +87,7 @@ void MainWindow::update()
|
||||
watts = ((bike*)bluetoothManager->device())->watts();
|
||||
ui->watt->setText(QString::number(watts));
|
||||
ui->resistance->setText(QString::number(resistance));
|
||||
ui->cadence->setText(QString::number(((bike*)bluetoothManager->device())->currentCadence()));
|
||||
}
|
||||
|
||||
if(trainProgram)
|
||||
|
||||
@@ -20,10 +20,13 @@ SOURCES += \
|
||||
bluetoothdevice.cpp \
|
||||
charts.cpp \
|
||||
domyostreadmill.cpp \
|
||||
echelonconnectsport.cpp \
|
||||
gpx.cpp \
|
||||
homeform.cpp \
|
||||
keepawakehelper.cpp \
|
||||
main.cpp \
|
||||
sessionline.cpp \
|
||||
signalhandler.cpp \
|
||||
toorxtreadmill.cpp \
|
||||
treadmill.cpp \
|
||||
mainwindow.cpp \
|
||||
@@ -44,8 +47,11 @@ HEADERS += \
|
||||
bluetoothdevice.h \
|
||||
charts.h \
|
||||
domyostreadmill.h \
|
||||
echelonconnectsport.h \
|
||||
homeform.h \
|
||||
keepawakehelper.h \
|
||||
sessionline.h \
|
||||
signalhandler.h \
|
||||
toorxtreadmill.h \
|
||||
gpx.h \
|
||||
treadmill.h \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,5 +6,6 @@
|
||||
<file>Page2Form.ui.qml</file>
|
||||
<file>qtquickcontrols2.conf</file>
|
||||
<file>Home.qml</file>
|
||||
<file>settings.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
144
src/settings.qml
Normal file
144
src/settings.qml
Normal file
@@ -0,0 +1,144 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
Pane {
|
||||
id: settingsPane
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
property bool bike_heartrate_service: false
|
||||
property int bike_resistance_offset: 4
|
||||
property int bike_resistance_gain: 1
|
||||
property bool miles_unit: false
|
||||
property bool bike_cadence_sensor: false
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
id: rebootLabel
|
||||
text: qsTr("Reboot the app in order to apply the settings")
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Material.color(Material.Red)
|
||||
}
|
||||
|
||||
Label {
|
||||
id: generalLabel
|
||||
text: qsTr("General Options")
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Material.color(Material.Grey)
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: unitDelegate
|
||||
text: qsTr("Use Miles unit in UI")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
clip: false
|
||||
checked: settings.miles_unit
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.miles_unit = checked
|
||||
}
|
||||
|
||||
Label {
|
||||
id: bikeBridgeLabel
|
||||
text: qsTr("Bike Bridge Options")
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Material.color(Material.Grey)
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: cadenceSensorDelegate
|
||||
text: qsTr("Cadence Sensor (Peloton compatibility)")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
clip: false
|
||||
checked: settings.bike_cadence_sensor
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.bike_cadence_sensor = checked
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: switchDelegate
|
||||
text: qsTr("Heart Rate service outside FTMS")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
clip: false
|
||||
checked: settings.bike_heartrate_service
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.bike_heartrate_service = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
id: labelBikeResistanceOffset
|
||||
text: qsTr("Zwift Resistance Offset:")
|
||||
Layout.preferredWidth: 150
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
Layout.preferredWidth: 60
|
||||
id: bikeResistanceOffsetTextField
|
||||
text: settings.bike_resistance_offset
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onAccepted: settings.bike_resistance_offset = text
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
id: labelBikeResistanceGain
|
||||
text: qsTr("Zwift Resistance Gain:")
|
||||
Layout.preferredWidth: 150
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
Layout.preferredWidth: 60
|
||||
id: bikeResistanceGainTextField
|
||||
text: settings.bike_resistance_gain
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onAccepted: settings.bike_resistance_gain = text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/signalhandler.cpp
Normal file
169
src/signalhandler.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include "signalhandler.h"
|
||||
#include <assert.h>
|
||||
#include <cstddef>
|
||||
|
||||
#if 1
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#endif //!__MINGW32_MAJOR_VERSION
|
||||
|
||||
// There can be only ONE SignalHandler per process
|
||||
SignalHandler* g_handler(NULL);
|
||||
|
||||
#if 0
|
||||
|
||||
BOOL WINAPI WIN32_handleFunc(DWORD);
|
||||
int WIN32_physicalToLogical(DWORD);
|
||||
DWORD WIN32_logicalToPhysical(int);
|
||||
std::set<int> g_registry;
|
||||
|
||||
#else //__MINGW32_MAJOR_VERSION
|
||||
|
||||
void POSIX_handleFunc(int);
|
||||
int POSIX_physicalToLogical(int);
|
||||
int POSIX_logicalToPhysical(int);
|
||||
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
|
||||
SignalHandler::SignalHandler(int mask) : _mask(mask)
|
||||
{
|
||||
assert(g_handler == NULL);
|
||||
g_handler = this;
|
||||
|
||||
#if 0
|
||||
SetConsoleCtrlHandler(WIN32_handleFunc, TRUE);
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
|
||||
for (int i=0;i<numSignals;i++)
|
||||
{
|
||||
int logical = 0x1 << i;
|
||||
if (_mask & logical)
|
||||
{
|
||||
#if 0
|
||||
g_registry.insert(logical);
|
||||
#else
|
||||
int sig = POSIX_logicalToPhysical(logical);
|
||||
bool failed = signal(sig, POSIX_handleFunc) == SIG_ERR;
|
||||
assert(!failed);
|
||||
(void)failed; // Silence the warning in non _DEBUG; TODO: something better
|
||||
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SignalHandler::~SignalHandler()
|
||||
{
|
||||
#if 0
|
||||
SetConsoleCtrlHandler(WIN32_handleFunc, FALSE);
|
||||
#else
|
||||
for (int i=0;i<numSignals;i++)
|
||||
{
|
||||
int logical = 0x1 << i;
|
||||
if (_mask & logical)
|
||||
{
|
||||
signal(POSIX_logicalToPhysical(logical), SIG_DFL);
|
||||
}
|
||||
}
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
DWORD WIN32_logicalToPhysical(int signal)
|
||||
{
|
||||
switch (signal)
|
||||
{
|
||||
case SignalHandler::SIG_INT: return CTRL_C_EVENT;
|
||||
case SignalHandler::SIG_TERM: return CTRL_BREAK_EVENT;
|
||||
case SignalHandler::SIG_CLOSE: return CTRL_CLOSE_EVENT;
|
||||
default:
|
||||
return ~(unsigned int)0; // SIG_ERR = -1
|
||||
}
|
||||
}
|
||||
#else
|
||||
int POSIX_logicalToPhysical(int signal)
|
||||
{
|
||||
switch (signal)
|
||||
{
|
||||
case SignalHandler::SIG_INT: return SIGINT;
|
||||
case SignalHandler::SIG_TERM: return SIGTERM;
|
||||
// In case the client asks for a SIG_CLOSE handler, accept and
|
||||
// bind it to a SIGTERM. Anyway the signal will never be raised
|
||||
case SignalHandler::SIG_CLOSE: return SIGTERM;
|
||||
//case SignalHandler::SIG_RELOAD: return SIGHUP;
|
||||
default:
|
||||
return -1; // SIG_ERR = -1
|
||||
}
|
||||
}
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
|
||||
|
||||
#if 0
|
||||
int WIN32_physicalToLogical(DWORD signal)
|
||||
{
|
||||
switch (signal)
|
||||
{
|
||||
case CTRL_C_EVENT: return SignalHandler::SIG_INT;
|
||||
case CTRL_BREAK_EVENT: return SignalHandler::SIG_TERM;
|
||||
case CTRL_CLOSE_EVENT: return SignalHandler::SIG_CLOSE;
|
||||
default:
|
||||
return SignalHandler::SIG_UNHANDLED;
|
||||
}
|
||||
}
|
||||
#else
|
||||
int POSIX_physicalToLogical(int signal)
|
||||
{
|
||||
switch (signal)
|
||||
{
|
||||
case SIGINT: return SignalHandler::SIG_INT;
|
||||
case SIGTERM: return SignalHandler::SIG_TERM;
|
||||
//case SIGHUP: return SignalHandler::SIG_RELOAD;
|
||||
default:
|
||||
return SignalHandler::SIG_UNHANDLED;
|
||||
}
|
||||
}
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
BOOL WINAPI WIN32_handleFunc(DWORD signal)
|
||||
{
|
||||
if (g_handler)
|
||||
{
|
||||
int signo = WIN32_physicalToLogical(signal);
|
||||
// The std::set is thread-safe in const reading access and we never
|
||||
// write to it after the program has started so we don't need to
|
||||
// protect this search by a mutex
|
||||
std::set<int>::const_iterator found = g_registry.find(signo);
|
||||
if (signo != -1 && found != g_registry.end())
|
||||
{
|
||||
return g_handler->handleSignal(signo) ? TRUE : FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
#else
|
||||
void POSIX_handleFunc(int signal)
|
||||
{
|
||||
if (g_handler)
|
||||
{
|
||||
int signo = POSIX_physicalToLogical(signal);
|
||||
g_handler->handleSignal(signo);
|
||||
}
|
||||
}
|
||||
#endif //__MINGW32_MAJOR_VERSION
|
||||
29
src/signalhandler.h
Normal file
29
src/signalhandler.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef SIGNALHANDLER_H
|
||||
#define SIGNALHANDLER_H
|
||||
|
||||
|
||||
class SignalHandler
|
||||
{
|
||||
public:
|
||||
SignalHandler(int mask = DEFAULT_SIGNALS);
|
||||
virtual ~SignalHandler();
|
||||
|
||||
enum SIGNALS
|
||||
{
|
||||
SIG_UNHANDLED = 0, // Physical signal not supported by this class
|
||||
SIG_NOOP = 1, // The application is requested to do a no-op (only a target that platform-specific signals map to when they can't be raised anyway)
|
||||
SIG_INT = 2, // Control+C (should terminate but consider that it's a normal way to do so; can delay a bit)
|
||||
SIG_TERM = 4, // Control+Break (should terminate now without regarding the consquences)
|
||||
SIG_CLOSE = 8, // Container window closed (should perform normal termination, like Ctrl^C) [Windows only; on Linux it maps to SIG_TERM]
|
||||
SIG_RELOAD = 16, // Reload the configuration [Linux only, physical signal is SIGHUP; on Windows it maps to SIG_NOOP]
|
||||
DEFAULT_SIGNALS = SIG_INT | SIG_TERM | SIG_CLOSE,
|
||||
};
|
||||
static const int numSignals = 6;
|
||||
|
||||
virtual bool handleSignal(int signal) = 0;
|
||||
|
||||
private:
|
||||
int _mask;
|
||||
};
|
||||
|
||||
#endif // SIGNALHANDLER_H
|
||||
@@ -15,7 +15,7 @@ void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
|
||||
if(device.name().startsWith("TRX ROUTE KEY"))
|
||||
{
|
||||
bttreadmill = device;
|
||||
bluetoothDevice = device;
|
||||
|
||||
// Create a discovery agent and connect to its signals
|
||||
discoveryAgent = new QBluetoothServiceDiscoveryAgent(this);
|
||||
@@ -31,7 +31,7 @@ void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
|
||||
// In your local slot, read information about the found devices
|
||||
void toorxtreadmill::serviceDiscovered(const QBluetoothServiceInfo &service)
|
||||
{
|
||||
if(service.device().address() == bttreadmill.address())
|
||||
if(service.device().address() == bluetoothDevice.address())
|
||||
{
|
||||
debug("Found new service: " + service.serviceName()
|
||||
+ '(' + service.serviceUuid().toString() + ')');
|
||||
|
||||
@@ -46,7 +46,6 @@ private slots:
|
||||
void update();
|
||||
|
||||
private:
|
||||
QBluetoothDeviceInfo bttreadmill;
|
||||
QBluetoothServiceDiscoveryAgent *discoveryAgent;
|
||||
QBluetoothServiceInfo serialPortService;
|
||||
QBluetoothSocket *socket = nullptr;
|
||||
|
||||
@@ -58,7 +58,7 @@ void trxappgateusbtreadmill::update()
|
||||
initRequest = false;
|
||||
btinit(false);
|
||||
}
|
||||
else if(bttreadmill.isValid() &&
|
||||
else if(bluetoothDevice.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService &&
|
||||
gattWriteCharacteristic.isValid() &&
|
||||
@@ -342,8 +342,8 @@ 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);
|
||||
bluetoothDevice = device;
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
|
||||
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
|
||||
connect(m_control, SIGNAL(discoveryFinished()),
|
||||
|
||||
@@ -55,7 +55,6 @@ private:
|
||||
QTimer* refresh;
|
||||
virtualtreadmill* virtualTreadMill = 0;
|
||||
|
||||
QBluetoothDeviceInfo bttreadmill;
|
||||
QLowEnergyController* m_control = 0;
|
||||
QLowEnergyService* gattCommunicationChannelService = 0;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <QtMath>
|
||||
#include <QMetaEnum>
|
||||
#include <QDataStream>
|
||||
#include <QSettings>
|
||||
|
||||
enum FtmsControlPointCommand {
|
||||
FTMS_REQUEST_CONTROL = 0x00,
|
||||
@@ -36,72 +37,131 @@ enum FtmsResultCode {
|
||||
FTMS_CONTROL_NOT_PERMITTED
|
||||
};
|
||||
|
||||
virtualbike::virtualbike(bike* t, bool noWriteResistance, bool noHeartService)
|
||||
virtualbike::virtualbike(bike* t, bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, uint8_t bikeResistanceGain)
|
||||
{
|
||||
Bike = t;
|
||||
|
||||
this->noHeartService = noHeartService;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
|
||||
QSettings settings;
|
||||
bool cadence = settings.value("bike_cadence_sensor", false).toBool();
|
||||
|
||||
Q_UNUSED(noWriteResistance)
|
||||
|
||||
//! [Advertising Data]
|
||||
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
|
||||
advertisingData.setIncludePowerLevel(true);
|
||||
advertisingData.setLocalName("DomyosBridge");
|
||||
advertisingData.setLocalName("DomyosBridge"); // save chars for service
|
||||
QList<QBluetoothUuid> services;
|
||||
services << ((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
|
||||
|
||||
if(!cadence)
|
||||
services << ((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
|
||||
else
|
||||
services << (QBluetoothUuid::ServiceClassUuid::CyclingSpeedAndCadence);
|
||||
|
||||
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);
|
||||
if(!cadence)
|
||||
{
|
||||
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 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 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);
|
||||
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);
|
||||
serviceDataFIT.setUuid((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
|
||||
serviceDataFIT.addCharacteristic(charDataFIT);
|
||||
serviceDataFIT.addCharacteristic(charDataFIT2);
|
||||
serviceDataFIT.addCharacteristic(charDataFIT3);
|
||||
serviceDataFIT.addCharacteristic(charDataFIT4);
|
||||
}
|
||||
else
|
||||
{
|
||||
QLowEnergyCharacteristicData charData;
|
||||
charData.setUuid(QBluetoothUuid::CharacteristicType::CSCFeature);
|
||||
charData.setProperties(QLowEnergyCharacteristic::Read);
|
||||
QByteArray value;
|
||||
value.append((char)0x02); // crank supported
|
||||
value.append((char)0x00);
|
||||
charData.setValue(value);
|
||||
|
||||
QLowEnergyCharacteristicData charData2;
|
||||
charData2.setUuid(QBluetoothUuid::CharacteristicType::SensorLocation);
|
||||
charData2.setProperties(QLowEnergyCharacteristic::Read);
|
||||
QByteArray valueLocaltion;
|
||||
valueLocaltion.append((char)13); // rear hub
|
||||
charData2.setValue(valueLocaltion);
|
||||
/*const QLowEnergyDescriptorData clientConfig2(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
QByteArray(2, 0));
|
||||
charData2.addDescriptor(clientConfig2);*/
|
||||
|
||||
QLowEnergyCharacteristicData charData3;
|
||||
charData3.setUuid(QBluetoothUuid::CharacteristicType::CSCMeasurement);
|
||||
charData3.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
const QLowEnergyDescriptorData clientConfig4(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
descriptor);
|
||||
charData3.addDescriptor(clientConfig4);
|
||||
|
||||
QLowEnergyCharacteristicData charData4;
|
||||
charData4.setUuid(QBluetoothUuid::CharacteristicType::SCControlPoint);
|
||||
charData4.setProperties(QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::Indicate);
|
||||
const QLowEnergyDescriptorData cpClientConfig(QBluetoothUuid::ClientCharacteristicConfiguration,
|
||||
QByteArray(2, 0));
|
||||
charData4.addDescriptor(cpClientConfig);
|
||||
|
||||
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
|
||||
serviceData.setUuid(QBluetoothUuid::ServiceClassUuid::CyclingSpeedAndCadence);
|
||||
serviceData.addCharacteristic(charData);
|
||||
serviceData.addCharacteristic(charData3);
|
||||
serviceData.addCharacteristic(charData2);
|
||||
serviceData.addCharacteristic(charData4);
|
||||
}
|
||||
|
||||
if(!this->noHeartService)
|
||||
{
|
||||
@@ -121,11 +181,19 @@ virtualbike::virtualbike(bike* t, bool noWriteResistance, bool noHeartService)
|
||||
//! [Start Advertising]
|
||||
leController = QLowEnergyController::createPeripheral();
|
||||
Q_ASSERT(leController);
|
||||
if(!cadence)
|
||||
serviceFIT = leController->addService(serviceDataFIT);
|
||||
else
|
||||
service = leController->addService(serviceData);
|
||||
|
||||
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)));
|
||||
|
||||
if(!cadence)
|
||||
QObject::connect(serviceFIT, SIGNAL(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)), this, SLOT(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
else
|
||||
QObject::connect(service, SIGNAL(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)), this, SLOT(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)));
|
||||
|
||||
QLowEnergyAdvertisingParameters pars;
|
||||
pars.setInterval(100, 100);
|
||||
@@ -169,7 +237,7 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
|
||||
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
|
||||
Bike->changeResistance((uint8_t)(resistance * bikeResistanceGain) + bikeResistanceOffset + 1); // resistance start from 1
|
||||
}
|
||||
else if((char)newValue.at(0) == FTMS_START_RESUME)
|
||||
{
|
||||
@@ -218,20 +286,31 @@ void virtualbike::writeCharacteristic(QLowEnergyService* service, QLowEnergyChar
|
||||
|
||||
void virtualbike::reconnect()
|
||||
{
|
||||
QSettings settings;
|
||||
bool cadence = settings.value("bike_cadence_sensor", false).toBool();
|
||||
|
||||
emit debug("virtualbike::reconnect");
|
||||
leController->disconnectFromDevice();
|
||||
|
||||
if(!cadence)
|
||||
serviceFIT = leController->addService(serviceDataFIT);
|
||||
else
|
||||
service = leController->addService(serviceData);
|
||||
|
||||
if(!this->noHeartService)
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
serviceFIT = leController->addService(serviceDataFIT);
|
||||
|
||||
if (serviceFIT)
|
||||
leController->startAdvertising(QLowEnergyAdvertisingParameters(),
|
||||
advertisingData, advertisingData);
|
||||
QLowEnergyAdvertisingParameters pars;
|
||||
pars.setInterval(100, 100);
|
||||
leController->startAdvertising(pars,
|
||||
advertisingData, advertisingData);
|
||||
}
|
||||
|
||||
void virtualbike::bikeProvider()
|
||||
{
|
||||
QSettings settings;
|
||||
bool cadence = settings.value("bike_cadence_sensor", false).toBool();
|
||||
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
@@ -244,34 +323,54 @@ void virtualbike::bikeProvider()
|
||||
|
||||
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)
|
||||
if(!cadence)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
return;
|
||||
}
|
||||
writeCharacteristic(serviceFIT, characteristic, 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.append((char)0x02); // crank data present
|
||||
value.append((char)(((uint16_t)Bike->currentCrankRevolutions()) & 0xFF)); // revs count
|
||||
value.append((char)(((uint16_t)Bike->currentCrankRevolutions()) >> 8) & 0xFF); // revs count
|
||||
value.append((char)(Bike->lastCrankEventTime() & 0xff)); // eventtime
|
||||
value.append((char)(Bike->lastCrankEventTime() >> 8) & 0xFF); // eventtime
|
||||
|
||||
QLowEnergyCharacteristic characteristic
|
||||
= service->characteristic(QBluetoothUuid::CharacteristicType::CSCMeasurement);
|
||||
Q_ASSERT(characteristic.isValid());
|
||||
if(leController->state() != QLowEnergyController::ConnectedState)
|
||||
{
|
||||
emit debug("virtual bike not connected");
|
||||
return;
|
||||
}
|
||||
writeCharacteristic(service, characteristic, value);
|
||||
}
|
||||
//characteristic
|
||||
// = service->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9); // Fitness Machine Control Point
|
||||
//Q_ASSERT(characteristic.isValid());
|
||||
|
||||
@@ -28,20 +28,24 @@ class virtualbike: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtualbike(bike* t, bool noWriteResistance = false, bool noHeartService = false);
|
||||
virtualbike(bike* t, bool noWriteResistance = false, bool noHeartService = false, uint8_t bikeResistanceOffset = 4, uint8_t bikeResistanceGain = 1);
|
||||
bool connected();
|
||||
|
||||
private:
|
||||
QLowEnergyController* leController;
|
||||
QLowEnergyService* serviceHR;
|
||||
QLowEnergyService* serviceFIT;
|
||||
QLowEnergyService* service;
|
||||
QLowEnergyAdvertisingData advertisingData;
|
||||
QLowEnergyServiceData serviceDataHR;
|
||||
QLowEnergyServiceData serviceDataFIT;
|
||||
QLowEnergyServiceData serviceData;
|
||||
QTimer bikeTimer;
|
||||
bike* Bike;
|
||||
|
||||
bool noHeartService = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
uint8_t bikeResistanceGain = 1;
|
||||
|
||||
void writeCharacteristic(QLowEnergyService* service, QLowEnergyCharacteristic characteristic, QByteArray value);
|
||||
|
||||
|
||||
@@ -147,6 +147,9 @@ void virtualtreadmill::reconnect()
|
||||
if(noHeartService == false)
|
||||
serviceHR = leController->addService(serviceDataHR);
|
||||
|
||||
QLowEnergyAdvertisingParameters pars;
|
||||
pars.setInterval(100, 100);
|
||||
|
||||
if (service)
|
||||
leController->startAdvertising(QLowEnergyAdvertisingParameters(),
|
||||
advertisingData, advertisingData);
|
||||
|
||||
Reference in New Issue
Block a user