Compare commits

..

1 Commits

Author SHA1 Message Date
Roberto Viola
6e3bd576c5 logging current status to file 2020-10-02 10:30:17 +02:00
97 changed files with 256 additions and 51389 deletions

12
.github/FUNDING.yml vendored
View File

@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: cagnulein
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.buymeacoffee.com/cagnulein'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,103 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Cache Qt Linux Desktop
id: cache-qt-linux-desktop
uses: actions/cache@v1
with:
path: '${{ github.workspace }}/output/linux-desktop/'
key: ${{ runner.os }}-QtCache-Linux-Desktop
- name: Cache Qt Linux Android
id: cache-qt-android
uses: actions/cache@v1
with:
path: '${{ github.workspace }}/output/android/'
key: ${{ runner.os }}-QtCache-Android
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
# Runs a set of commands using the runners shell
- name: Install Qt Linux Desktop
uses: jurplel/install-qt-action@v2
with:
version: '5.12.9'
host: 'linux'
target: 'desktop'
modules: 'qtcharts debug_info'
dir: '${{ github.workspace }}/output/linux-desktop/'
cached: ${{ steps.cache-qt-linux-desktop.outputs.cache-hit }}
- name: Compile Linux Desktop
run: cd src; qmake; make -j4
- name: Archive linux-desktop binary
uses: actions/upload-artifact@v2
with:
name: linux-desktop-binary
path: src/qdomyos-zwift
- uses: actions/checkout@v2
with:
repository: nttld/setup-ndk
path: setup-ndk
# The packages.json in nttld/setup-ndk has already been updated,
# https://github.com/nttld/setup-ndk/commit/831db5b02a0f0cab80614619efe461a3dcc140e6
# but `dist/*` has not been rebuilt yet. Build it.
# https://github.com/nttld/setup-ndk/tree/main/dist
- name: Locally rebuilt setup-ndk
run: |
npm -prefix ./setup-ndk install
npm -prefix ./setup-ndk run all
# Install using locally rebuilt setup-ndk
- name: Setup Android NDK r21d
uses: ./setup-ndk
#- uses: nttld/setup-ndk@v1
with:
ndk-version: r21d
# waiting github.com/jurplel/install-qt-action/issues/63
- name: Install Qt Android
uses: jurplel/install-qt-action@v2
with:
version: '5.12.9'
host: 'linux'
target: 'android'
arch: 'android_armv7'
modules: 'qtcharts debug_info'
dir: '${{ github.workspace }}/output/android/'
cached: ${{ steps.cache-qt-android.outputs.cache-hit }}
- name: Compile Android
run: cd src; qmake; make -j4
# - name: Install Qt MacOS
# uses: jurplel/install-qt-action@v2
# with:
# version: '5.12.9'
# host: 'mac'
# target: 'desktop'
# modules: 'qtcharts debug_info'
# dir: '${{ github.workspace }}/output/macos/'
# - name: Compile MacOS
# run: cd src; qmake; make -j4

View File

@@ -1,85 +1,25 @@
# qdomyos-zwift
Zwift bridge for Treadmills and Bike!
Zwift bridge for Domyos treadmills
<a href="https://www.buymeacoffee.com/cagnulein" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
![UI](docs/treadmill-bridge-schema.png)
![UI](docs/ui.png)
![UI](docs/realtime-chart.png)
UI on Linux
![UI](docs/ui-mac.png)
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
![First Success](docs/first_success.jpg)
### Installation from source
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default
### Installation
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
$ cd src
$ sudo apt upgrade && sudo apt update # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ qmake
$ sudo apt install libqt5bluetooth5
$ make -j4
$ sudo hciconfig hci0 leadv 0
$ sudo ./qdomyos-zwift
### MacOs installation
You will need to (at a minimum) to install the xcode Command Line Tools (CLI) thanks to @richardwait
https://developer.apple.com/download/more/?=xcode
Download and install http://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-mac-x64-5.12.9.dmg and simply run the qdomyos-zwift relase for MacOs
### Tested on
- Raspberry PI 0W and Domyos Intense Run
- MacBook Air 2011 and Domyos Intense Run
- Raspberry 3b+ and Domyos T900C
- Raspberry 3b+ and Toorx TRX Route Key
### Your machine is not compatible?
Open an issue and follow these steps!
1. first of all you need an android device (phone or tablet)
2. you need to become developer on your phone https://wccftech.com/how-to/how-to-enable-developer-options-on-android-10-tutorial/
3. Go to Settings
4. Go into developer options
5. Enable the option Enable Bluetooth HCI snoop log
6. restart your phone
7. open your machine app and play with it collecting inclination and speed
8. Disable the option Enable Bluetooth HCI snoop log
9. on your phone you should have a file called btsnoop_hci.log
10. attach the log file in a new issue with a short description of the steps you did in the app when you used it
### No gui version
run as
$ sudo ./qdomyos-zwift -no-gui
Raspberry PI 0W and Domyos Intense Run
### Reference

View File

@@ -1,10 +0,0 @@
FROM debian:stable
MAINTAINER cagnulein
ENV MAKEFLAGS -j8
WORKDIR /usr/local/src
# utils
RUN apt -y update
RUN apt -y upgrade
RUN apt -y install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,110 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml-stylesheet type="text/xsl" href="FieldBasedDisplay.xslt"?> --><!--Copyright 2016 Bluetooth SIG, Inc. All rights reserved.-->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Cycling Power Control Point"
type="org.bluetooth.characteristic.cycling_power_control_point" uuid="2A66" last-modified="2016-05-03"
approved="Yes">
<InformativeText>
<Summary>The Cycling Power Control Point characteristic is used to request a specific function to be executed
on the receiving device.
</Summary>
</InformativeText>
<Value>
<Field name="Op Codes">
<Requirement>Mandatory</Requirement>
<Format>uint8</Format>
<Enumerations>
<Enumeration key="1" value="Set Cumulative Value"
description="Initiate the procedure to set a cumulative value. The new value is sent as parameter following op code (parameter defined per service). The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="2" value="Update Sensor Location"
description="Update to the location of the Sensor with the value sent as parameter to this op code. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="3" value="Request Supported Sensor Locations"
description="Request a list of supported locations where the Sensor can be attached. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including a list of supported Sensor locations in the Response Parameter."/>
<Enumeration key="4" value="Set Crank Length"
description="Initiate the procedure to set the crank length value to Sensor. The new value is sent as a parameter with preceding Op Code 0x04 operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="5" value="Request Crank Length"
description="Request the current crank length value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the Crank Length in the Response Parameter."/>
<Enumeration key="6" value="Set Chain Length"
description="Initiate the procedure to set the chain length value to Sensor. The new value is sent as a parameter with preceding Op Code 0x06 operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="7" value="Request Chain Length"
description="Request the current chain length value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the chain length in the Response Parameter."/>
<Enumeration key="8" value="Set Chain Weight"
description="Initiate the procedure to set the chain weight value to Sensor. The new value is sent as a parameter with preceding Op Code 0x08 operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="9" value="Request Chain Weight"
description="Request the current chain weight value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the chain weight in the Response Parameter."/>
<Enumeration key="10" value="Set Span Length"
description="Initiate the procedure to set the span length value to Sensor. The new value is sent as a parameter with preceding Op Code 0x0A operand. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="11" value="Request Span Length"
description="Request the current span length value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the span length in the Response Parameter."/>
<Enumeration key="12" value="Start Offset Compensation"
description="Starts the offset compensation process of the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the raw force or a raw torque in the Response Parameter (defined per Service)."/>
<Enumeration key="13" value="Mask Cycling Power Measurement Characteristic Content"
description="Initiate the procedure to set the content of Cycling Power Measurement Characteristic. The response to this control point is Op Code 0x20 followed by the appropriate Response Value."/>
<Enumeration key="14" value="Request Sampling Rate"
description="Request the sampling rate value set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the sampling rate in the Response Parameter."/>
<Enumeration key="15" value="Request Factory Calibration Date"
description="Request the Factory calibration date set in the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the Factory calibration date in the Response Parameter."/>
<Enumeration key="16" value="Start Enhanced Offset Compensation"
description="Starts the offset compensation process of the Sensor. The response to this control point is Op Code 0x20 followed by the appropriate Response Value, including the value of the raw force or a raw torque in the Response Parameter and an option for a manufacturer specific value (defined per Service)."/>
<Enumeration key="32" value="Response Code"
description="The Response Code is followed by the Request Op Code, the Response Value and optionally, the Response Parameter."/>
<ReservedForFutureUse start="0" end="0"/>
<ReservedForFutureUse start="17" end="31"/>
<ReservedForFutureUse start="33" end="255"/>
</Enumerations>
</Field>
<Field name="Parameter Value"><!--<InformativeText>Parameter Value for "Set Cumulative Value" Op Code</InformativeText>-->
<Requirement>Optional</Requirement>
<Format>variable</Format>
<Description>Defined per Service specification.</Description>
</Field>
<Field name="Request Op Code">
<InformativeText>The Request Op Code is a sub field of the Parameter Value for "Response Code" Op Code.
<br>
C1: This Field is Mandatory for "Response Code" Op Code, otherwise this field is Excluded.
</br>
</InformativeText>
<Requirement>C1</Requirement>
<Format>uint8</Format>
<Description>Refer to the Op Code table above for additional information on the possible values for this
field.
</Description>
</Field>
<Field name="Response Value">
<InformativeText>The Response Value is a sub field of the Parameter Value for "Response Code" Op Code
<br>
C1: This Field is Mandatory for "Response Code" Op Code, otherwise this field is Excluded.
</br>
</InformativeText>
<Requirement>C1</Requirement>
<Format>uint8</Format>
<Enumerations>
<Enumeration key="1" value="Success" description="Response for successful operation. "/>
<Enumeration key="2" value="Op Code not Supported"
description="Response if unsupported Op Code is received."/>
<Enumeration key="3" value="Invalid Parameter"
description="Response if Parameter received does not meet the requirements of the service or is outside of the supported range of the Sensor."/>
<Enumeration key="4" value="Operation Failed"
description="Response if the requested procedure failed."/>
<ReservedForFutureUse start="0" end="0"/>
<ReservedForFutureUse start="5" end="255"/>
</Enumerations>
</Field>
<Field name="Response Parameter">
<InformativeText>The Response Parameter is a sub field of the Parameter Value for "Response Code" Op Code.
<br>
C2:This Field is Optional for "Response Code" Op Code, otherwise this field is Excluded.
</br>
</InformativeText>
<Requirement>C2</Requirement>
<Format>variable</Format>
<Description>Note: The Response Parameter Value of the response to the Control Point is a variable length
field to allow a list of different values defined by the Service Specification
</Description>
</Field>
</Value>
<Note>
The fields in the above table, reading from top to bottom, are shown in the order of LSO to MSO, where LSO =
Least Significant Octet and MSO = Most Significant Octet.
The Least Significant Octet represents the eight bits numbered 0 to 7.
</Note>
</Characteristic>

View File

@@ -1,151 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml-stylesheet type="text/xsl" href="FieldBasedDisplay.xslt"?> --><!--Copyright 2016 Bluetooth SIG, Inc. All rights reserved.-->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Cycling Power Feature"
type="org.bluetooth.characteristic.cycling_power_feature" uuid="2A65" last-modified="2016-05-03"
approved="Yes">
<InformativeText>
<Summary>The CP Feature characteristic is used to report a list of features supported by the device.</Summary>
</InformativeText>
<Value>
<Field name="Cycling Power Feature">
<Requirement>Mandatory</Requirement>
<Format>32bit</Format>
<BitField>
<Bit index="0" size="1" name="Pedal Power Balance Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="1" size="1" name="Accumulated Torque Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="2" size="1" name="Wheel Revolution Data Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="3" size="1" name="Crank Revolution Data Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="4" size="1" name="Extreme Magnitudes Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="5" size="1" name="Extreme Angles Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="6" size="1" name="Top and Bottom Dead Spot Angles Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="7" size="1" name="Accumulated Energy Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="8" size="1" name="Offset Compensation Indicator Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="9" size="1" name="Offset Compensation Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="10" size="1" name="Cycling Power Measurement Characteristic Content Masking Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="11" size="1" name="Multiple Sensor Locations Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="12" size="1" name="Crank Length Adjustment Supported ">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="13" size="1" name="Chain Length Adjustment Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="14" size="1" name="Chain Weight Adjustment Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="15" size="1" name="Span Length Adjustment Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="16" size="1" name="Sensor Measurement Context">
<Enumerations>
<Enumeration key="0" value="Force based"/>
<Enumeration key="1" value="Torque based"/>
</Enumerations>
</Bit>
<Bit index="17" size="1" name="Instantaneous Measurement Direction Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="18" size="1" name="Factory Calibration Date Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="19" size="1" name="Enhanced Offset Compensation Supported">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="20" size="2" name="Distribute System Support">
<Enumerations>
<Enumeration key="0" value="Unspecified (legacy sensor)"/>
<Enumeration key="1" value="Not for use in a distributed system"/>
<Enumeration key="2" value="Can be used in a distributed system"/>
<Enumeration key="3" value="RFU"/>
</Enumerations>
</Bit>
<ReservedForFutureUse index="22" size="10"/>
</BitField>
</Field>
</Value>
<Note>
The fields in the above table, reading from top to bottom, are shown in the order of LSO to MSO, where LSO =
Least Significant Octet and MSO = Most Significant Octet.
The Least Significant Octet represents the eight bits numbered 0 to 7.
</Note>
</Characteristic>

View File

@@ -1,304 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--Copyright 2011 Bluetooth SIG, Inc. All rights reserved.-->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Cycling Power Measurement"
type="org.bluetooth.characteristic.cycling_power_measurement" uuid="2A63" last-modified="2014-07-02"
approved="Yes">
<InformativeText>
<Summary>The Cycling Power Measurement characteristic is a variable length structure containing a Flags field,
an Instantaneous Power field and, based on the contents of the Flags field, may contain one or more
additional fields as shown in the table below.
</Summary>
</InformativeText>
<Value>
<Field name="Flags">
<Requirement>Mandatory</Requirement>
<Format>16bit</Format>
<BitField>
<Bit index="0" size="1" name="Pedal Power Balance Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="1" size="1" name="Pedal Power Balance Reference">
<Enumerations>
<Enumeration key="0" value="Unknown"/>
<Enumeration key="1" value="Left"/>
</Enumerations>
</Bit>
<Bit index="2" size="1" name="Accumulated Torque Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="3" size="1" name="Accumulated Torque Source">
<Enumerations>
<Enumeration key="0" value="Wheel Based"/>
<Enumeration key="1" value="Crank Based"/>
</Enumerations>
</Bit>
<Bit index="4" size="1" name="Wheel Revolution Data Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="5" size="1" name="Crank Revolution Data Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="6" size="1" name="Extreme Force Magnitudes Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="7" size="1" name="Extreme Torque Magnitudes Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="8" size="1" name="Extreme Angles Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="9" size="1" name="Top Dead Spot Angle Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="10" size="1" name="Bottom Dead Spot Angle Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="11" size="1" name="Accumulated Energy Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="12" size="1" name="Offset Compensation Indicator ">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<ReservedForFutureUse index="13" size="3"/>
</BitField>
<b>C1:These Fields are dependent upon the Flags field</b>
<p></p>
</Field>
<Field name="Instantaneous Power">
<InformativeText>
Unit is in watts with a resolution of 1.
</InformativeText>
<Requirement>Mandatory</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.power.watt</Unit>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Pedal Power Balance">
<InformativeText>
Unit is in percentage with a resolution of 1/2.
</InformativeText>
<Requirement>Optional</Requirement>
<Format>uint8</Format>
<Unit>org.bluetooth.unit.percentage</Unit>
<BinaryExponent>-1</BinaryExponent>
</Field>
<Field name="Accumulated Torque">
<InformativeText>
Unit is in newton metres with a resolution of 1/32.
</InformativeText>
<Requirement>Optional</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
<BinaryExponent>-5</BinaryExponent>
</Field>
<Field name="Wheel Revolution Data - Cumulative Wheel Revolutions">
<InformativeText>
Unitless
<br>C1:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C1</Requirement>
<Format>uint32</Format>
<Unit>org.bluetooth.unit.unitless</Unit>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Wheel Revolution Data - Last Wheel Event Time">
<InformativeText>
Unit is in seconds with a resolution of 1/2048.
<br>C1:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C1</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.time.second</Unit>
<BinaryExponent>-11</BinaryExponent>
</Field>
<Field name="Crank Revolution Data- Cumulative Crank Revolutions">
<InformativeText>
Unitless
<br>C2:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C2</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.unitless</Unit>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Crank Revolution Data- Last Crank Event Time">
<InformativeText>
Unit is in seconds with a resolution of 1/1024.
<br>C2:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C2</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.time.second</Unit>
<BinaryExponent>-10</BinaryExponent>
</Field>
<Field name="Extreme Force Magnitudes - Maximum Force Magnitude">
<InformativeText>
Unit is in newtons with a resolution of 1.
<br>C3:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C3</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.force.newton</Unit>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Extreme Force Magnitudes - Minimum Force Magnitude">
<InformativeText>
Unit is in newtons with a resolution of 1.
<br>C3:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C3</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.force.newton</Unit>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Extreme Torque Magnitudes- Maximum Torque Magnitude">
<InformativeText>
Unit is in newton metres with a resolution of 1/32.
<br>C4:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C4</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
<BinaryExponent>-5</BinaryExponent>
</Field>
<Field name="Extreme Torque Magnitudes- Minimum Torque Magnitude">
<InformativeText>
Unit is in newton metres with a resolution of 1/32.
<br>C4:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C4</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
<BinaryExponent>-5</BinaryExponent>
</Field>
<Field name="Extreme Angles - Maximum Angle">
<InformativeText>
Unit is in degrees with a resolution of 1
<br>C5: When present, this field and the "Extreme Angles - Minimum Angle" field are always present as a
pair and are concatenated into a UINT24 value (3 octets). As an example, if the Maximum Angle is
0xABC and the Minimum Angle is 0x123, the transmitted value is 0x123ABC.
</br>
</InformativeText>
<Requirement>C5</Requirement>
<Format>uint12</Format>
<Unit>org.bluetooth.unit.plane_angle.degree
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
position. The left crank sensor (if fitted) detects the 0? when the crank it
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
crank it is attached to is in its 12 o'clock position; thus, there is a constant
180? difference between the right crank and the left crank position signals.
</Description>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Extreme Angles - Minimum Angle">
<InformativeText>
Unit is in degrees with a resolution of 1.
<br>C5: When present, this field and the "Extreme Angles - Maximum Angle" field are always present as a
pair and are concatenated into a UINT24 value (3 octets). As an example, if the Maximum Angle is
0xABC and the Minimum Angle is 0x123, the transmitted value is 0x123ABC.
</br>
</InformativeText>
<Requirement>C5</Requirement>
<Format>uint12</Format>
<Unit>org.bluetooth.unit.plane_angle.degree
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
position. The left crank sensor (if fitted) detects the 0? when the crank it
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
crank it is attached to is in its 12 o'clock position; thus, there is a constant
180? difference between the right crank and the left crank position signals.
</Description>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Top Dead Spot Angle">
<InformativeText>
Unit is in degrees with a resolution of 1.
</InformativeText>
<Requirement>Optional</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.plane_angle.degree
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
position. The left crank sensor (if fitted) detects the 0? when the crank it
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
crank it is attached to is in its 12 o'clock position; thus, there is a constant
180? difference between the right crank and the left crank position signals.
</Description>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Bottom Dead Spot Angle">
<InformativeText>
Unit is in degrees with a resolution of 1.
</InformativeText>
<Requirement>Optional</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.plane_angle.degree
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
position. The left crank sensor (if fitted) detects the 0? when the crank it
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
crank it is attached to is in its 12 o'clock position; thus, there is a constant
180? difference between the right crank and the left crank position signals.
</Description>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Accumulated Energy">
<InformativeText>
Unit is in kilojoules with a resolution of 1.
</InformativeText>
<Requirement>Optional</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.energy.joule</Unit>
<DecimalExponent>3</DecimalExponent>
</Field>
</Value>
<Note>
<p>
The fields in the above table, reading from top to bottom, are shown in the order of LSO to MSO, where LSO
= Least Significant Octet and MSO = Most Significant Octet.
The Least Significant Octet represents the eight bits numbered 0 to 7.
</p>
</Note>
</Characteristic>

View File

@@ -1,128 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--Copyright 2011 Bluetooth SIG, Inc. All rights reserved.-->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Cycling Power Vector"
type="org.bluetooth.characteristic.cycling_power_vector" uuid="2A64" last-modified="2014-07-02"
approved="Yes">
<InformativeText>
<Summary>The Cycling Power Vector characteristic is a variable length structure containing a Flags fieldand
based on the contents of the Flags field, may contain one or more additional fields as shown in the table
below.
</Summary>
</InformativeText>
<Value>
<Field name="Flags">
<Requirement>Mandatory</Requirement>
<Format>8bit</Format>
<BitField>
<Bit index="0" size="1" name="Crank Revolution Data Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="1" size="1" name="First Crank Measurement Angle Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="2" size="1" name="Instantaneous Force Magnitude Array Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="3" size="1" name="Instantaneous Torque Magnitude Array Present">
<Enumerations>
<Enumeration key="0" value="False"/>
<Enumeration key="1" value="True"/>
</Enumerations>
</Bit>
<Bit index="4" size="2" name="Instantaneous Measurement Direction">
<Enumerations>
<Enumeration key="0" value="Unknown"/>
<Enumeration key="1" value="Tangential Component"/>
<Enumeration key="2" value="Radial Component"/>
<Enumeration key="3" value="Lateral Component"/>
</Enumerations>
</Bit>
<ReservedForFutureUse index="6" size="2"/>
</BitField>
<br>C1:These Fields are dependent upon the Flags field</br>
<p></p>
</Field>
<Field name="Crank Revolution Data - Cumulative Crank Revolutions">
<InformativeText>
Unitless
<br>C1:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C1</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.unitless</Unit>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Crank Revolution Data - Last Crank Event Time">
<InformativeText>
Unit is in seconds with a resolution of 1/1024.
<br>C1:When present, these fields are always present as a pair.</br>
</InformativeText>
<Requirement>C1</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.time.second</Unit>
<BinaryExponent>-10</BinaryExponent>
</Field>
<Field name="First Crank Measurement Angle ">
<InformativeText>
Unit is in degrees with a resolution of 1.
</InformativeText>
<Requirement>Optional</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.plane_angle.degree
</Unit><!-- 2014-07-02 - Added the Description tag to show the informational text per request from SF WG -->
<Description>When observed with the front wheel to the right of the pedals, a value of 0 degrees represents
the angle when the crank is in the 12 o'clock position and a value of 90 degrees
represents the angle, measured clockwise, when the crank points towards the front wheel in a 3 o'clock
position. The left crank sensor (if fitted) detects the 0? when the crank it
is attached to is in the 12 o'clock position and the right sensor (if fitted) detects the 0? when the
crank it is attached to is in its 12 o'clock position; thus, there is a constant
180? difference between the right crank and the left crank position signals.
</Description>
<DecimalExponent>0</DecimalExponent>
</Field>
<Field name="Instantaneous Force Magnitude Array">
<InformativeText>
The unit is in newtons with a resolution of 1
<br>Array Order - Older is towards the LSO and Newer is towards the MSO</br>
<br>C2: These fields are mutually exclusive. When this field is present, the presence of the
Instantaneous Torque Magnitude Array is excluded.
</br>
</InformativeText>
<Requirement>C2</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.force.newton</Unit>
<DecimalExponent>0</DecimalExponent>
<Repeated>1</Repeated>
</Field>
<Field name="Instantaneous Torque Magnitude Array">
<InformativeText>
Unit is in newton/meter with a resolution of 1/32
<br>Array Order - Older is towards the LSO and Newer is towards the MSO</br>
<br>C2: These fields are mutually exclusive. When this field is present, the presence of the
Instantaneous Force Magnitude Array is excluded.
</br>
</InformativeText>
<Requirement>C2</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.moment_of_force.newton_metre</Unit>
<BinaryExponent>-5</BinaryExponent>
<Repeated>1</Repeated>
</Field>
</Value>
<Note>
<p>
The fields in the above table, reading from top to bottom, are shown in the order of LSO to MSO, where LSO
= Least Significant Octet and MSO = Most Significant Octet.
The Least Significant Octet represents the eight bits numbered 0 to 7.
</p>
</Note>
</Characteristic>

View File

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

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright 2017 Bluetooth SIG, Inc. All rights reserved.-->
<Characteristic xsi:noNamespaceSchemaLocation="http://schemas.bluetooth.org/Documents/characteristic.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Supported Resistance Level Range"
type="org.bluetooth.characteristic.supported_resistance_level_range"
uuid="2AD6" last-modified="2017-02-14" approved="Yes">
<InformativeText>
<Summary>The Supported Resistance Level Range characteristic is
used to send the supported resistance level range as well as
the minimum resistance increment supported by the
Server.</Summary>
</InformativeText>
<Value>
<Field name="Minimum Resistance Level">
<InformativeText>Unitless with a resolution of
0.1</InformativeText>
<Requirement>Mandatory</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.unitless</Unit>
<DecimalExponent>-1</DecimalExponent>
</Field>
<Field name="Maximum Resistance Level">
<InformativeText>Unitless with a resolution of
0.1</InformativeText>
<Requirement>Mandatory</Requirement>
<Format>sint16</Format>
<Unit>org.bluetooth.unit.unitless</Unit>
<DecimalExponent>-1</DecimalExponent>
</Field>
<Field name="Minimum Increment">
<InformativeText>Unitless with a resolution of
0.1</InformativeText>
<Requirement>Mandatory</Requirement>
<Format>uint16</Format>
<Unit>org.bluetooth.unit.unitless</Unit>
<DecimalExponent>-1</DecimalExponent>
</Field>
</Value>
</Characteristic>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -1,114 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12
import QtGraphicalEffects 1.12
HomeForm{
objectName: "home"
signal start_clicked;
signal stop_clicked;
signal plus_clicked(string name)
signal minus_clicked(string name)
start.onClicked: { start_clicked(); }
stop.onClicked: { stop_clicked(); }
Component.onCompleted: { console.log("completed"); }
GridView {
anchors.horizontalCenter: parent.horizontalCenter
anchors.fill: parent
cellWidth: 175
cellHeight: 125
focus: true
model: appModel
leftMargin: { (parent.width % 175) / 2; }
anchors.topMargin: 150
id: gridView
objectName: "gridview"
// highlight: Rectangle {
// width: 150
// height: 150
// color: "lightsteelblue"
// }
delegate: Item {
id: id1
width: 175
height: 125
Component.onCompleted: console.log("completed " + objectName)
Rectangle {
width: 173
height: 123
radius: 3
border.width: 1
color: Material.backgroundColor
}
Image {
id: myIcon
x: 5
anchors {
top: myValue.bottom
}
width: 48
height: 48
source: icon
}
Text {
objectName: "value"
id: myValue
color: Material.textSelectionColor
y: 0
anchors {
horizontalCenter: parent.horizontalCenter
}
text: value
horizontalAlignment: Text.AlignHCenter
font.pointSize: 48
font.bold: true
}
Text {
id: myText
anchors {
top: myIcon.top
}
font.bold: true
color: "white"
text: name
anchors.left: parent.left
anchors.leftMargin: 55
anchors.topMargin: 20
}
RoundButton {
objectName: minusName
text: "-"
onClicked: minus_clicked(objectName)
visible: writable
anchors.top: myValue.top
anchors.left: parent.left
anchors.leftMargin: 2
width: 48
height: 48
}
RoundButton {
objectName: plusName
text: "+"
onClicked: plus_clicked(objectName)
visible: writable
anchors.top: myValue.top
anchors.right: parent.right
anchors.rightMargin: 2
width: 48
height: 48
}
/*MouseArea {
anchors.fill: parent
onClicked: parent.GridView.view.currentIndex = index
}*/
}
}
}

View File

@@ -1,120 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12
import QtGraphicalEffects 1.12
Page {
title: qsTr("qDomyos-Zwift")
id: page
property alias start: start
property alias stop: stop
property alias row: row
Item {
width: parent.width
height: 120
Row {
id: row
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
height: 100
spacing: 5
padding: 5
Rectangle {
width: 50
height: 100
color: Material.backgroundColor
Image {
anchors.verticalCenter: parent.verticalCenter
id: treadmill_connection
width: 48
height: 48
source: "icons/icons/bluetooth-icon.png"
enabled: rootItem.device
smooth: true
}
ColorOverlay {
anchors.fill: treadmill_connection
source: treadmill_connection
color: treadmill_connection.enabled ? "#00000000" : "#B0D3d3d3"
}
}
Rectangle {
width: 120
height: 100
color: Material.backgroundColor
RoundButton {
icon.source: "icons/icons/start.png"
icon.height: 46
icon.width: 46
text: "Start"
enabled: true
id: start
width: 120
height: 96
}
}
Rectangle {
width: 120
height: 100
color: Material.backgroundColor
RoundButton {
icon.source: "icons/icons/stop.png"
icon.height: 46
icon.width: 46
text: "Stop"
enabled: true
id: stop
width: 120
height: 96
}
}
Rectangle {
id: item2
width: 50
height: 100
color: Material.backgroundColor
Image {
anchors.verticalCenter: parent.verticalCenter
id: zwift_connection
width: 48
height: 48
source: "icons/icons/zwift-on.png"
enabled: rootItem.zwift
smooth: true
}
ColorOverlay {
anchors.fill: zwift_connection
source: zwift_connection
color: zwift_connection.enabled ? "#00000000" : "#B0D3d3d3"
}
}
}
Row {
id: row1
width: parent.width
anchors.bottom: row.bottom
anchors.bottomMargin: -10
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: rootItem.info
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;formeditorZoom:0.6600000262260437;height:480;width:640}
}
##^##*/

View File

@@ -1,14 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
Page {
width: 600
height: 400
title: qsTr("Page 1")
Label {
text: qsTr("You are on Page 1.")
anchors.centerIn: parent
}
}

View File

@@ -1,14 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
Page {
width: 600
height: 400
title: qsTr("Page 2")
Label {
text: qsTr("You are on Page 2.")
anchors.centerIn: parent
}
}

View File

@@ -1,79 +0,0 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:debuggable="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="qdomyos-zwift" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="qdomyos-zwift"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="full"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
</activity>
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
</manifest>

View File

@@ -1,68 +0,0 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
android {
/*******************************************************
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
* are defined in gradle.properties file. This file is
* updated by QtCreator and androiddeployqt tools.
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion '28.0.3'
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
lintOptions {
abortOnError false
}
// Do not compress Qt binary resources file
aaptOptions {
noCompress 'rcc'
}
defaultConfig {
resConfig "en"
minSdkVersion = qtMinSdkVersion
targetSdkVersion = qtTargetSdkVersion
}
}

Binary file not shown.

View File

@@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
src/android/gradlew vendored
View File

@@ -1,172 +0,0 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@@ -1,84 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,22 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
</array>
<array name="qt_libs">
<!-- %%INSERT_QT_LIBS%% -->
</array>
<array name="load_local_libs">
<!-- %%INSERT_LOCAL_LIBS%% -->
</array>
</resources>

View File

@@ -1,8 +0,0 @@
package org.cagnulen.qdomyoszwift;
public class MyActivity extends org.qtproject.qt5.android.bindings.QtActivity {
@Override
public void onCreate(android.os.Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}

View File

@@ -1,122 +0,0 @@
#include <QDebug>
#include "bike.h"
bike::bike()
{
}
void bike::changeResistance(int8_t resistance) { requestResistance = resistance;}
double bike::currentCrankRevolutions() { return CrankRevs;}
uint16_t bike::lastCrankEventTime() { return LastCrankEventTime;}
int8_t bike::currentResistance() { return Resistance;}
uint8_t bike::currentCadence() { return Cadence;}
uint8_t bike::fanSpeed() { return FanSpeed; }
bool bike::connected() { return false; }
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
uint16_t bike::watts()
{
const uint8_t max_resistance = 15;
// ref https://translate.google.com/translate?hl=it&sl=en&u=https://support.wattbike.com/hc/en-us/articles/115001881825-Power-Resistance-and-Cadence-Tables&prev=search&pto=aue
const uint16_t watt_cad40_min = 25;
const uint16_t watt_cad40_max = 55;
const uint16_t watt_cad45_min = 35;
const uint16_t watt_cad45_max = 65;
const uint16_t watt_cad50_min = 40;
const uint16_t watt_cad50_max = 80;
const uint16_t watt_cad55_min = 50;
const uint16_t watt_cad55_max = 105;
const uint16_t watt_cad60_min = 60;
const uint16_t watt_cad60_max = 125;
const uint16_t watt_cad65_min = 70;
const uint16_t watt_cad65_max = 160;
const uint16_t watt_cad70_min = 85;
const uint16_t watt_cad70_max = 190;
const uint16_t watt_cad75_min = 100;
const uint16_t watt_cad75_max = 240;
const uint16_t watt_cad80_min = 115;
const uint16_t watt_cad80_max = 280;
const uint16_t watt_cad85_min = 130;
const uint16_t watt_cad85_max = 340;
const uint16_t watt_cad90_min = 150;
const uint16_t watt_cad90_max = 390;
const uint16_t watt_cad95_min = 175;
const uint16_t watt_cad95_max = 450;
const uint16_t watt_cad100_min = 195;
const uint16_t watt_cad100_max = 520;
const uint16_t watt_cad105_min = 210;
const uint16_t watt_cad105_max = 600;
const uint16_t watt_cad110_min = 245;
const uint16_t watt_cad110_max = 675;
const uint16_t watt_cad115_min = 270;
const uint16_t watt_cad115_max = 760;
const uint16_t watt_cad120_min = 300;
const uint16_t watt_cad120_max = 850;
const uint16_t watt_cad125_min = 330;
const uint16_t watt_cad125_max = 945;
const uint16_t watt_cad130_min = 360;
const uint16_t watt_cad130_max = 1045;
if(currentSpeed() <= 0) return 0;
if(currentCadence() < 41)
return((((watt_cad40_max-watt_cad40_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad40_min);
else if(currentCadence() < 46)
return((((watt_cad45_max-watt_cad45_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad45_min);
else if(currentCadence() < 51)
return((((watt_cad50_max-watt_cad50_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad50_min);
else if(currentCadence() < 56)
return((((watt_cad55_max-watt_cad55_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad55_min);
else if(currentCadence() < 61)
return((((watt_cad60_max-watt_cad60_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad60_min);
else if(currentCadence() < 66)
return((((watt_cad65_max-watt_cad65_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad65_min);
else if(currentCadence() < 71)
return((((watt_cad70_max-watt_cad70_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad70_min);
else if(currentCadence() < 76)
return((((watt_cad75_max-watt_cad75_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad75_min);
else if(currentCadence() < 81)
return((((watt_cad80_max-watt_cad80_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad80_min);
else if(currentCadence() < 86)
return((((watt_cad85_max-watt_cad85_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad85_min);
else if(currentCadence() < 91)
return((((watt_cad90_max-watt_cad90_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad90_min);
else if(currentCadence() < 96)
return((((watt_cad95_max-watt_cad95_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad95_min);
else if(currentCadence() < 101)
return((((watt_cad100_max-watt_cad100_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad100_min);
else if(currentCadence() < 106)
return((((watt_cad105_max-watt_cad105_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad105_min);
else if(currentCadence() < 111)
return((((watt_cad110_max-watt_cad110_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad110_min);
else if(currentCadence() < 116)
return((((watt_cad115_max-watt_cad115_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad115_min);
else if(currentCadence() < 121)
return((((watt_cad120_max-watt_cad120_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad120_min);
else if(currentCadence() < 126)
return((((watt_cad125_max-watt_cad125_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad125_min);
else
return((((watt_cad130_max-watt_cad130_min) / (max_resistance - 1)) * (currentResistance() - 1))+watt_cad130_min);
return 0;
}

View File

@@ -1,36 +0,0 @@
#ifndef BIKE_H
#define BIKE_H
#include <QObject>
#include "bluetoothdevice.h"
class bike:public bluetoothdevice
{
Q_OBJECT
public:
bike();
virtual int8_t currentResistance();
virtual uint8_t currentCadence();
virtual uint8_t fanSpeed();
virtual double currentCrankRevolutions();
virtual uint16_t lastCrankEventTime();
virtual bool connected();
uint16_t watts();
bluetoothdevice::BLUETOOTH_TYPE deviceType();
public slots:
virtual void changeResistance(int8_t res);
signals:
void bikeStarted();
protected:
uint8_t Cadence = 0;
int8_t Resistance = 0;
uint16_t LastCrankEventTime = 0;
int8_t requestResistance = -1;
double CrankRevs = 0;
};
#endif // BIKE_H

View File

@@ -1,133 +0,0 @@
#include "bluetooth.h"
#include <QFile>
#include <QDateTime>
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool noHeartService, uint32_t pollDeviceTime, bool noConsole, bool testResistance) : QObject(nullptr)
{
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
filterDevice = deviceName;
this->testResistance = testResistance;
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->pollDeviceTime = pollDeviceTime;
this->noConsole = noConsole;
this->logs = logs;
#ifndef WIN32
if(!QBluetoothLocalDevice::allDevices().count())
{
debug("no bluetooth dongle found!");
}
else
#endif
{
// Create a discovery agent and connect to its signals
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
// Start a discovery
discoveryAgent->start();
}
}
void bluetooth::debug(QString text)
{
QString debug = QDateTime::currentDateTime().toString() + " " + QString::number(QDateTime::currentMSecsSinceEpoch()) + " " + text;
if(logs)
qDebug() << debug;
}
void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
emit deviceFound(device.name());
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')' + " " + device.majorDeviceClass() + ":" + device.minorDeviceClass());
/* only on qt 5.12
foreach(quint16 i, device.manufacturerIds())
{
debug("manufacturer id: " + QString::number(i) + " -> " + device.manufacturerData(i));
}*/
bool filter = true;
if(filterDevice.length())
{
filter = (device.name().compare(filterDevice, Qt::CaseInsensitive) == 0);
}
if(device.name().startsWith("Domyos-Bike") && !device.name().startsWith("DomyosBridge") && filter)
{
discoveryAgent->stop();
domyosBike = new domyosbike(noWriteResistance, noHeartService, testResistance);
emit(deviceConnected());
connect(domyosBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(domyosBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
domyosBike->deviceDiscovered(device);
}
else if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge") && filter)
{
discoveryAgent->stop();
domyos = new domyostreadmill(this->pollDeviceTime, noConsole, noHeartService);
emit(deviceConnected());
connect(domyos, SIGNAL(disconnected()), this, SLOT(restart()));
connect(domyos, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
domyos->deviceDiscovered(device);
}
else if((device.name().startsWith("TRX ROUTE KEY")) && filter)
{
discoveryAgent->stop();
toorx = new toorxtreadmill();
emit(deviceConnected());
connect(toorx, SIGNAL(disconnected()), this, SLOT(restart()));
connect(toorx, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
toorx->deviceDiscovered(device);
}
else if((device.name().startsWith("TOORX")) && filter)
{
discoveryAgent->stop();
trxappgateusb = new trxappgateusbtreadmill();
emit(deviceConnected());
connect(trxappgateusb, SIGNAL(disconnected()), this, SLOT(restart()));
connect(trxappgateusb, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
trxappgateusb->deviceDiscovered(device);
}
}
void bluetooth::restart()
{
if(domyos)
{
delete domyos;
domyos = 0;
}
if(domyosBike)
{
delete domyosBike;
domyosBike = 0;
}
if(toorx)
{
delete toorx;
toorx = 0;
}
if(trxappgateusb)
{
delete trxappgateusb;
trxappgateusb = 0;
}
discoveryAgent->start();
}
bluetoothdevice* bluetooth::device()
{
if(domyos)
return domyos;
else if(domyosBike)
return domyosBike;
else if(toorx)
return toorx;
else if(trxappgateusb)
return trxappgateusb;
return nullptr;
}

View File

@@ -1,62 +0,0 @@
#ifndef BLUETOOTH_H
#define BLUETOOTH_H
#include <QObject>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtCore/qbytearray.h>
#include <QtCore/qloggingcategory.h>
#include <QFile>
#include "treadmill.h"
#include "domyostreadmill.h"
#include "domyosbike.h"
#include "trxappgateusbtreadmill.h"
#include "toorxtreadmill.h"
#include "bluetoothdevice.h"
class bluetooth : public QObject
{
Q_OBJECT
public:
explicit bluetooth(bool logs, QString deviceName = "", bool noWriteResistance = false, bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false, bool testResistance = false);
bluetoothdevice* device();
private:
QFile* debugCommsLog = 0;
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
domyostreadmill* domyos = 0;
domyosbike* domyosBike = 0;
toorxtreadmill* toorx = 0;
trxappgateusbtreadmill* trxappgateusb = 0;
QString filterDevice = "";
bool testResistance = false;
bool noWriteResistance = false;
bool noHeartService = false;
bool noConsole = false;
bool logs = true;
uint32_t pollDeviceTime = 200;
signals:
void deviceConnected();
void deviceFound(QString name);
public slots:
void restart();
void debug(QString string);
private slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
signals:
};
#endif // BLUETOOTH_H

View File

@@ -1,20 +0,0 @@
#include "bluetoothdevice.h"
#include <QTime>
bluetoothdevice::bluetoothdevice()
{
}
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
void bluetoothdevice::start(){ requestStart = 1; }
void bluetoothdevice::stop(){ requestStop = 1; }
unsigned char bluetoothdevice::currentHeart(){ return Heart; }
double bluetoothdevice::currentSpeed(){ return Speed; }
QTime bluetoothdevice::currentPace(){ return QTime(0, (int)(1.0 / (Speed / 60.0)), (((double)(1.0 / (Speed / 60.0)) - ((double)((int)(1.0 / (Speed / 60.0))))) * 60.0), 0 ); }
double bluetoothdevice::odometer(){ return Distance; }
double bluetoothdevice::calories(){ return KCal; }
uint8_t bluetoothdevice::fanSpeed() { return FanSpeed; };
void* bluetoothdevice::VirtualDevice() { return nullptr; }
bool bluetoothdevice::changeFanSpeed(uint8_t speed) { Q_UNUSED(speed); return false; }
bool bluetoothdevice::connected() { return false; }

View File

@@ -1,49 +0,0 @@
#ifndef BLUETOOTHDEVICE_H
#define BLUETOOTHDEVICE_H
#include <QObject>
class bluetoothdevice : public QObject
{
Q_OBJECT
public:
bluetoothdevice();
virtual unsigned char currentHeart();
virtual double currentSpeed();
virtual QTime currentPace();
virtual double odometer();
virtual double calories();
virtual uint8_t fanSpeed();
virtual bool connected();
virtual void* VirtualDevice();
uint16_t watts(double weight=75.0);
virtual bool changeFanSpeed(uint8_t speed);
enum BLUETOOTH_TYPE {
UNKNOWN = 0,
TREADMILL,
BIKE,
ROWING
};
virtual BLUETOOTH_TYPE deviceType();
public slots:
virtual void start();
virtual void stop();
protected:
double elapsed = 0;
double Speed = 0;
double KCal = 0;
double Distance = 0;
uint8_t FanSpeed = 0;
uint8_t Heart = 0;
int8_t requestStart = -1;
int8_t requestStop = -1;
int8_t requestIncreaseFan = -1;
int8_t requestDecreaseFan = -1;
};
#endif // BLUETOOTHDEVICE_H

View File

@@ -1,163 +0,0 @@
#include "charts.h"
#include "ui_charts.h"
charts::charts(MainWindow *parent) :
QDialog(parent),
ui(new Ui::charts)
{
ui->setupUi(this);
this->parent = parent;
chart = new QtCharts::QChart();
chart_view = new QtCharts::QChartView(chart, ui->widget);
ui->widget->setVisible(false);
chart_series_speed = new QtCharts::QLineSeries();
chart_series_pace = new QtCharts::QLineSeries();
chart_series_inclination = new QtCharts::QLineSeries();
chart_series_heart = new QtCharts::QLineSeries();
chart_series_watt = new QtCharts::QLineSeries();
chart_series_resistance = new QtCharts::QLineSeries();
chart_series_speed->setPointLabelsVisible(false); // is false by default
chart_series_speed->setPointLabelsColor(Qt::black);
chart_series_speed->setPointLabelsFormat("@yPoint km/h");
chart_series_pace->setPointLabelsVisible(false); // is false by default
chart_series_pace->setPointLabelsColor(Qt::black);
chart_series_pace->setPointLabelsFormat("@yPoint min/km");
chart_series_inclination->setPointLabelsVisible(false); // is false by default
chart_series_inclination->setPointLabelsColor(Qt::black);
chart_series_inclination->setPointLabelsFormat("@yPoint%");
chart_series_heart->setPointLabelsVisible(false); // is false by default
chart_series_heart->setPointLabelsColor(Qt::black);
chart_series_heart->setPointLabelsFormat("@yPoint bpm");
chart_series_watt->setPointLabelsVisible(false); // is false by default
chart_series_watt->setPointLabelsColor(Qt::black);
chart_series_watt->setPointLabelsFormat("@yPoint W");
chart_series_resistance->setPointLabelsVisible(false); // is false by default
chart_series_resistance->setPointLabelsColor(Qt::black);
chart_series_resistance->setPointLabelsFormat("@yPoint lvl");
chart_series_speed->setName("Speed (km/h)");
chart_series_pace->setName("Pace (min/km)");
chart_series_inclination->setName("Inclination (%)");
chart_series_heart->setName("Heart (bpm)");
chart_series_watt->setName("Watt (W)");
chart_series_resistance->setName("Resistance (lvl)");
chart->legend()->setAlignment(Qt::AlignBottom);
chart_view->setRenderHint(QPainter::Antialiasing);
chart_view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->frame->layout()->addWidget(chart_view);
}
void charts::update()
{
if(chart->series().count())
{
if(ui->speed->isChecked())
chart->removeSeries(chart_series_speed);
if(ui->pace->isChecked())
chart->removeSeries(chart_series_pace);
if(ui->inclination->isChecked())
chart->removeSeries(chart_series_inclination);
if(ui->heart->isChecked())
chart->removeSeries(chart_series_heart);
if(ui->watt->isChecked())
chart->removeSeries(chart_series_watt);
if(ui->resistance->isChecked())
chart->removeSeries(chart_series_resistance);
}
chart_series_inclination->clear();
chart_series_speed->clear();
chart_series_pace->clear();
chart_series_heart->clear();
chart_series_watt->clear();
chart_series_resistance->clear();
const int maxQueue = 100;
for(int g=0; g<(parent->Session.count() > maxQueue ? maxQueue : parent->Session.count()); g++)
{
int index = g + (parent->Session.count() > maxQueue ? parent->Session.count() % maxQueue : 0);
if(ui->inclination->isChecked())
chart_series_inclination->append(g, static_cast<double>(parent->Session[index].inclination));
if(ui->speed->isChecked())
chart_series_speed->append(g, static_cast<qreal>(parent->Session[index].speed));
if(ui->pace->isChecked())
chart_series_pace->append(g, static_cast<qreal>(parent->Session[index].pace));
if(ui->heart->isChecked())
chart_series_heart->append(g, static_cast<qreal>(parent->Session[index].heart));
if(ui->watt->isChecked())
chart_series_watt->append(g, static_cast<qreal>(parent->Session[index].watt));
if(ui->resistance->isChecked())
chart_series_resistance->append(g, static_cast<qreal>(parent->Session[index].resistance));
}
if(ui->inclination->isChecked())
chart->addSeries(chart_series_inclination);
if(ui->speed->isChecked())
chart->addSeries(chart_series_speed);
if(ui->pace->isChecked())
chart->addSeries(chart_series_pace);
if(ui->heart->isChecked())
chart->addSeries(chart_series_heart);
if(ui->watt->isChecked())
chart->addSeries(chart_series_watt);
if(ui->resistance->isChecked())
chart->addSeries(chart_series_resistance);
chart->createDefaultAxes();
}
charts::~charts()
{
delete ui;
}
void charts::on_valueOnChart_stateChanged(int arg1)
{
Q_UNUSED(arg1);
if(ui->valueOnChart->checkState() == Qt::Checked)
{
chart_series_speed->setPointLabelsVisible(true);
chart_series_pace->setPointLabelsVisible(true);
chart_series_inclination->setPointLabelsVisible(true); // is false by default
chart_series_heart->setPointLabelsVisible(true); // is false by default
chart_series_watt->setPointLabelsVisible(true); // is false by default
chart_series_resistance->setPointLabelsVisible(true); // is false by default
}
else
{
chart_series_speed->setPointLabelsVisible(false);
chart_series_pace->setPointLabelsVisible(false);
chart_series_inclination->setPointLabelsVisible(false); // is false by default
chart_series_heart->setPointLabelsVisible(false); // is false by default
chart_series_watt->setPointLabelsVisible(false); // is false by default
chart_series_resistance->setPointLabelsVisible(false); // is false by default
}
}
void charts::on_speed_clicked()
{
}
void charts::on_Inclination_clicked()
{
}
void charts::on_watt_clicked()
{
}
void charts::on_resistance_clicked()
{
}
void charts::on_heart_clicked()
{
}

View File

@@ -1,48 +0,0 @@
#ifndef CHARTS_H
#define CHARTS_H
#include <QDialog>
#include <QtCharts>
#include "mainwindow.h"
namespace Ui {
class charts;
}
class charts : public QDialog
{
Q_OBJECT
public:
explicit charts(MainWindow *parent = nullptr);
void update();
~charts();
private slots:
void on_valueOnChart_stateChanged(int arg1);
void on_speed_clicked();
void on_Inclination_clicked();
void on_watt_clicked();
void on_resistance_clicked();
void on_heart_clicked();
private:
Ui::charts *ui;
MainWindow* parent = 0;
QtCharts::QChart* chart = 0;
QtCharts::QChartView* chart_view = 0;
QtCharts::QLineSeries* chart_series_speed = 0;
QtCharts::QLineSeries* chart_series_inclination = 0;
QtCharts::QLineSeries* chart_series_heart = 0;
QtCharts::QLineSeries* chart_series_watt = 0;
QtCharts::QLineSeries* chart_series_resistance = 0;
QtCharts::QLineSeries* chart_series_pace = 0;
};
#endif // CHARTS_H

View File

@@ -1,210 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>charts</class>
<widget class="QDialog" name="charts">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>Charts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="speed">
<property name="text">
<string>Speed</string>
</property>
<property name="icon">
<iconset>
<normalon>:/icons/icons/speed.png</normalon>
</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="inclination">
<property name="text">
<string>Inclination</string>
</property>
<property name="icon">
<iconset>
<normalon>:/icons/icons/inclination.png</normalon>
</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="watt">
<property name="text">
<string>Watt</string>
</property>
<property name="icon">
<iconset>
<normalon>:/icons/icons/watt.png</normalon>
</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resistance">
<property name="text">
<string>Resistance</string>
</property>
<property name="icon">
<iconset>
<normalon>:/icons/icons/inclination.png</normalon>
</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="heart">
<property name="text">
<string>Heart</string>
</property>
<property name="icon">
<iconset>
<normalon>:/icons/icons/heart_red.png</normalon>
</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pace">
<property name="text">
<string>Pace</string>
</property>
<property name="icon">
<iconset>
<normalon>:/icons/icons/pace.png</normalon>
</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="valueOnChart">
<property name="text">
<string>Value on Chart</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,518 +0,0 @@
#include "domyosbike.h"
#include "virtualbike.h"
#include <QFile>
#include <QDateTime>
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance)
{
refresh = new QTimer(this);
this->testResistance = testResistance;
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
initDone = false;
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
refresh->start(200);
}
void domyosbike::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
{
QEventLoop loop;
if(wait_for_response)
{
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
&loop, SLOT(quit()));
}
else
{
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
&loop, SLOT(quit()));
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
if(!disable_log)
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
loop.exec();
}
void domyosbike::updateDisplay(uint16_t elapsed)
{
uint8_t display[] = {0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00};
display[3] = (elapsed / 60) & 0xFF; // high byte for elapsed time (in seconds)
display[4] = (elapsed % 60 & 0xFF); // low byte for elasped time (in seconds)
display[7] = ((uint8_t)((uint16_t)(currentSpeed()) >> 8)) & 0xFF;
display[8] = (uint8_t)(currentSpeed()) & 0xFF;
display[12] = currentHeart();
//display[13] = ((((uint8_t)calories())) >> 8) & 0xFF;
//display[14] = (((uint8_t)calories())) & 0xFF;
display[16] = (uint8_t)currentCadence();
display[19] = ((((uint16_t)calories())) >> 8) & 0xFF;
display[20] = (((uint16_t)calories())) & 0xFF;
for(uint8_t i=0; i<sizeof(display)-1; i++)
{
display[26] += display[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed) );
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed) );
//if(bike_type == CHANG_YOW)
{
uint8_t display2[] = {0xf0, 0xcd, 0x01, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
display2[3] = ((((uint16_t)(odometer() * 10))) >> 8) & 0xFF;
display2[4] = (((uint16_t)(odometer() * 10))) & 0xFF;
for(uint8_t i=0; i<sizeof(display2)-1; i++)
{
display2[26] += display2[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(display2, 20, "updateDisplay2");
writeCharacteristic(&display2[20], sizeof (display2) - 20, "updateDisplay2");
}
}
void domyosbike::forceResistance(int8_t requestResistance)
{
uint8_t write[] = {0xf0, 0xad, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff,
0xff, 0xff, 0x00};
write[10] = requestResistance;
for(uint8_t i=0; i<sizeof(write)-1; i++)
{
write[22] += write[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(write, 20, "forceResistance " + QString::number(requestResistance));
writeCharacteristic(&write[20], sizeof (write) - 20, "forceResistance " + QString::number(requestResistance));
}
void domyosbike::update()
{
static QDateTime lastTime;
static bool first = true;
uint8_t noOpData[] = { 0xf0, 0xac, 0x9c };
// stop tape
uint8_t initDataF0C800B8[] = { 0xf0, 0xc8, 0x00, 0xb8 };
static uint8_t sec1 = 0;
if(m_control->state() == QLowEnergyController::UnconnectedState)
{
emit disconnected();
return;
}
if(initRequest)
{
initRequest = false;
//if(bike_type == CHANG_YOW)
btinit_changyow(false);
//else
// btinit_telink(false);
}
else if(btbike.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
gattNotifyCharacteristic.isValid() &&
initDone)
{
QDateTime current = QDateTime::currentDateTime();
if(currentSpeed() > 0.0 && !first)
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
lastTime = current;
// updating the treadmill console every second
if(sec1++ == (500 / refresh->interval()))
{
sec1 = 0;
updateDisplay(elapsed);
}
writeCharacteristic(noOpData, sizeof(noOpData), "noOp", true);
if(testResistance)
{
if((((int)elapsed) % 5) == 0)
{
uint8_t new_res = currentResistance() + 1;
if(new_res > 15)
new_res = 1;
forceResistance(new_res);
}
}
if(requestResistance != -1)
{
if(requestResistance > 15) requestResistance = 15;
else if(requestResistance == 0) requestResistance = 1;
if(requestResistance != currentResistance())
{
debug("writing resistance " + QString::number(requestResistance));
forceResistance(requestResistance);
}
requestResistance = -1;
}
if(requestStart != -1)
{
debug("starting...");
//if(bike_type == CHANG_YOW)
btinit_changyow(true);
//else
// btinit_telink(true);
requestStart = -1;
emit bikeStarted();
}
if(requestStop != -1)
{
debug("stopping...");
writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
first = false;
}
void domyosbike::serviceDiscovered(const QBluetoothUuid &gatt)
{
debug("serviceDiscovered " + gatt.toString());
}
static QByteArray lastPacket;
void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
static QDateTime lastRefresh = QDateTime::currentDateTime();
debug(" << " + newValue.toHex(' '));
if (lastPacket.length() && lastPacket == newValue)
return;
lastPacket = newValue;
if (newValue.length() != 26)
return;
if (newValue.at(22) == 0x06)
{
debug("start button pressed!");
requestStart = 1;
}
else if (newValue.at(22) == 0x07)
{
debug("stop button pressed!");
requestStop = 1;
}
/*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the inclination and speed status
return;*/
double speed = GetSpeedFromPacket(newValue);
double kcal = GetKcalFromPacket(newValue);
double distance = GetDistanceFromPacket(newValue);
Cadence = newValue.at(9);
Resistance = newValue.at(14);
if(Resistance < 1)
{
debug("invalid resistance value " + QString::number(Resistance) + " putting to default");
Resistance = 1;
}
Heart = newValue.at(18);
CrankRevs += ((double)(lastRefresh.msecsTo(QDateTime::currentDateTime())) * ((double)Cadence / 60000.0) );
LastCrankEventTime += (uint16_t)((lastRefresh.msecsTo(QDateTime::currentDateTime())) * 1.024);
lastRefresh = QDateTime::currentDateTime();
debug("Current speed: " + QString::number(speed));
debug("Current cadence: " + QString::number(Cadence));
debug("Current resistance: " + QString::number(Resistance));
debug("Current heart: " + QString::number(Heart));
debug("Current KCal: " + QString::number(kcal));
debug("Current Distance: " + QString::number(distance));
debug("Current CrankRevs: " + QString::number(CrankRevs));
debug("Last CrankEventTime: " + QString::number(LastCrankEventTime));
debug("Current Watt: " + QString::number(watts()));
if(m_control->error() != QLowEnergyController::NoError)
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
Speed = speed;
KCal = kcal;
Distance = distance;
}
double domyosbike::GetSpeedFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(6) << 8) | packet.at(7);
double data = (double)convertedData / 10.0f;
return data;
}
double domyosbike::GetKcalFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(10) << 8) | packet.at(11);
return (double)convertedData;
}
double domyosbike::GetDistanceFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(12) << 8) | packet.at(13);
double data = ((double)convertedData) / 10.0f;
return data;
}
void domyosbike::btinit_changyow(bool startTape)
{
// set speed and incline to 0
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
uint8_t initData2[] = { 0xf0, 0xc9, 0xb9 };
// main startup sequence
uint8_t initDataStart[] = { 0xf0, 0xa3, 0x93 };
uint8_t initDataStart2[] = { 0xf0, 0xa4, 0x94 };
uint8_t initDataStart3[] = { 0xf0, 0xa5, 0x95 };
uint8_t initDataStart4[] = { 0xf0, 0xab, 0x9b };
uint8_t initDataStart5[] = { 0xf0, 0xc4, 0x03, 0xb7 };
uint8_t initDataStart6[] =
{
0xf0, 0xad, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff
};
uint8_t initDataStart7[] = { 0xff, 0xff, 0x8b }; // power on bt icon
uint8_t initDataStart8[] =
{
0xf0, 0xcb, 0x02, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00
};
uint8_t initDataStart9[] = { 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xb6 }; // power on bt word
uint8_t initDataStart10[] =
{
0xf0, 0xad, 0xff, 0xff, 0x00, 0x05, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x01, 0xff
};
uint8_t initDataStart11[] = { 0xff, 0xff, 0x94 }; // start tape
uint8_t initDataStart12[] =
{
0xf0, 0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x14, 0x01, 0xff, 0xff
};
uint8_t initDataStart13[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbd };
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData2, sizeof(initData2), "init", false, true);
writeCharacteristic(initDataStart, sizeof(initDataStart), "init", false, true);
writeCharacteristic(initDataStart2, sizeof(initDataStart2), "init", false, true);
writeCharacteristic(initDataStart3, sizeof(initDataStart3), "init", false, true);
writeCharacteristic(initDataStart4, sizeof(initDataStart4), "init", false, true);
writeCharacteristic(initDataStart5, sizeof(initDataStart5), "init", false, true);
writeCharacteristic(initDataStart6, sizeof(initDataStart6), "init", false, false);
writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
writeCharacteristic(initDataStart8, sizeof(initDataStart8), "init", false, false);
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
if(startTape)
{
writeCharacteristic(initDataStart11, sizeof(initDataStart11), "init", false, true);
writeCharacteristic(initDataStart12, sizeof(initDataStart12), "init", false, false);
writeCharacteristic(initDataStart13, sizeof(initDataStart13), "init", false, true);
}
initDone = true;
}
void domyosbike::btinit_telink(bool startTape)
{
Q_UNUSED(startTape)
// set speed and incline to 0
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
uint8_t initData2[] = { 0xf0, 0xc9, 0xb9 };
uint8_t noOpData[] = { 0xf0, 0xac, 0x9c };
// main startup sequence
uint8_t initDataStart[] = { 0xf0, 0xcc, 0xff, 0xff, 0xff, 0xff, 0x01, 0xff, 0xb8 };
writeCharacteristic(initData1, sizeof(initData1), "init");
writeCharacteristic(initData2, sizeof(initData2), "init");
writeCharacteristic(noOpData, sizeof(noOpData), "noOp");
writeCharacteristic(initDataStart, sizeof(initDataStart), "init");
updateDisplay(0);
initDone = true;
}
void domyosbike::stateChanged(QLowEnergyService::ServiceState state)
{
QBluetoothUuid _gattWriteCharacteristicId((QString)"49535343-8841-43f4-a8d4-ecbe34729bb3");
QBluetoothUuid _gattNotifyCharacteristicId((QString)"49535343-1e4d-4bd9-ba61-23c647249616");
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if(state == QLowEnergyService::ServiceDiscovered)
{
//qDebug() << gattCommunicationChannelService->characteristics();
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotifyCharacteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
this, SLOT(errorService(QLowEnergyService::ServiceError)));
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
// ******************************************* virtual bike init *************************************
static uint8_t first = 0;
if(!first)
{
debug("creating virtual bike interface...");
virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
connect(virtualBike,&virtualbike::debug ,this,&domyosbike::debug);
}
first = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void domyosbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
{
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
}
void domyosbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
Q_UNUSED(characteristic);
debug("characteristicWritten " + newValue.toHex(' '));
}
void domyosbike::serviceScanDone(void)
{
debug("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((QString)"49535343-fe7d-4ae5-8fa9-9fafd205e455");
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
gattCommunicationChannelService->discoverDetails();
}
void domyosbike::errorService(QLowEnergyService::ServiceError err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
debug("domyosbike::errorService" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
}
void domyosbike::error(QLowEnergyController::Error err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
debug("domyosbike::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
m_control->disconnect();
}
void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("Domyos-Bike") && !device.name().startsWith("DomyosBridge"))
{
btbike = device;
if(device.address().toString().startsWith("57"))
{
debug("domyos telink bike found");
bike_type = TELINK;
}
else
{
debug("domyos changyow bike found");
bike_type = CHANG_YOW;
}
m_control = QLowEnergyController::createCentral(btbike, this);
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
connect(m_control, SIGNAL(discoveryFinished()),
this, SLOT(serviceScanDone()));
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
this, SLOT(error(QLowEnergyController::Error)));
connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
debug("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
debug("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
debug("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool domyosbike::connected()
{
if(!m_control)
return false;
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void* domyosbike::VirtualBike()
{
return virtualBike;
}
void* domyosbike::VirtualDevice()
{
return VirtualBike();
}

View File

@@ -1,95 +0,0 @@
#ifndef DOMYOSBIKE_H
#define DOMYOSBIKE_H
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qmutex.h>
#include <QObject>
#include <QString>
#include "virtualbike.h"
#include "bike.h"
class domyosbike : public bike
{
Q_OBJECT
public:
domyosbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false);
bool connected();
void* VirtualBike();
void* VirtualDevice();
private:
double GetSpeedFromPacket(QByteArray packet);
double GetInclinationFromPacket(QByteArray packet);
double GetKcalFromPacket(QByteArray packet);
double GetDistanceFromPacket(QByteArray packet);
void forceResistance(int8_t requestResistance);
void updateDisplay(uint16_t elapsed);
void btinit_changyow(bool startTape);
void btinit_telink(bool startTape);
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log=false, bool wait_for_response = false);
void startDiscover();
QTimer* refresh;
virtualbike* virtualBike = 0;
QBluetoothDeviceInfo btbike;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotifyCharacteristic;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
bool testResistance = false;
enum _BIKE_TYPE {
CHANG_YOW,
TELINK,
};
_BIKE_TYPE bike_type = CHANG_YOW;
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // DOMYOSBIKE_H

View File

@@ -1,9 +1,6 @@
#include "domyostreadmill.h"
#include "virtualtreadmill.h"
#include <QFile>
#include <QDateTime>
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
// set speed and incline to 0
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
@@ -14,6 +11,17 @@ uint8_t noOpData[] = { 0xf0, 0xac, 0x9c };
// stop tape
uint8_t initDataF0C800B8[] = { 0xf0, 0xc8, 0x00, 0xb8 };
#if 0
uint8_t initDataStart[] = { 0xf0, 0xc8, 0x00, 0xb8 };
uint8_t initDataStart2[] = { 0xf0, 0xcb, 0x01, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00 };
uint8_t initDataStart3[] = { 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xb6 };
uint8_t initDataStart4[] = { 0xf0, 0xc8, 0x00, 0xb8 };
uint8_t initDataStart5[] = { 0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00 };
uint8_t initDataStart6[] = { 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xc1 };
#endif
// main startup sequence
uint8_t initDataStart[] = { 0xf0, 0xa3, 0x93 };
uint8_t initDataStart2[] = { 0xf0, 0xa4, 0x94 };
@@ -49,253 +57,129 @@ 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;
QBluetoothDeviceInfo treadmill;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotifyCharacteristic;
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
bool initDone = false;
bool initRequest = false;
domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService)
extern volatile double currentSpeed;
extern volatile double currentIncline;
extern volatile uint8_t currentHeart;
extern volatile double requestSpeed;
extern volatile double requestIncline;
extern volatile int8_t requestStart;
extern volatile int8_t requestStop;
domyostreadmill::domyostreadmill()
{
this->noConsole = noConsole;
this->noHeartService = noHeartService;
refresh = new QTimer(this);
QTimer* refresh = new QTimer(this);
initDone = false;
// Create a discovery agent and connect to its signals
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
this, SLOT(deviceDiscovered(QBluetoothDeviceInfo)));
// Start a discovery
discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
refresh->start(pollDeviceTime);
refresh->start(200);
}
void domyostreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline, uint16_t elapsed)
{
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()));
}
uint8_t writeIncline[] = {0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00,
(uint8_t)(requestSpeed * 10), 0x01, 0xff, 0xff, 0xff, 0xff, 0x00};
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
writeIncline[3] = (elapsed >> 8) & 0xFF; // high byte for elapsed time (in seconds)
writeIncline[4] = (elapsed & 0xFF); // low byte for elasped time (in seconds)
if(!disable_log)
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
writeIncline[12] = currentHeart;
loop.exec();
}
void domyostreadmill::updateDisplay(uint16_t elapsed)
{
uint8_t display[] = {0xf0, 0xcb, 0x03, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x01, 0x01, 0x00,
0x0c, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00};
if(elapsed > 5999) // 99:59
{
display[3] = ((elapsed / 60) / 60) & 0xFF; // high byte for elapsed time (in seconds)
display[4] = ((elapsed / 60) % 60) & 0xFF; // low byte for elasped time (in seconds)
}
else
{
display[3] = (elapsed / 60) & 0xFF; // high byte for elapsed time (in seconds)
display[4] = (elapsed % 60 & 0xFF); // low byte for elasped time (in seconds)
}
if(odometer() < 10.0)
{
display[7] = ((uint8_t)((uint16_t)(odometer() * 100) >> 8)) & 0xFF;
display[8] = (uint8_t)(odometer() * 100) & 0xFF;
display[9] = 0x02; // decimal position
}
else if(odometer() < 100.0)
{
display[7] = ((uint8_t)(odometer() * 10) >> 8) & 0xFF;
display[8] = (uint8_t)(odometer() * 10) & 0xFF;
display[9] = 0x01; // decimal position
}
else
{
display[7] = ((uint8_t)(odometer()) >> 8) & 0xFF;
display[8] = (uint8_t)(odometer()) & 0xFF;
display[9] = 0x00; // decimal position
}
display[12] = currentHeart();
display[23] = ((uint8_t)(calories()) >> 8) & 0xFF;
display[24] = (uint8_t)(calories()) & 0xFF;
for(uint8_t i=0; i<sizeof(display)-1; i++)
{
display[26] += display[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed) );
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed) );
}
void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline)
{
uint8_t writeIncline[] = {0xf0, 0xad, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00};
writeIncline[4] = ((uint16_t)(requestSpeed*10) >> 8) & 0xFF;
writeIncline[5] = ((uint16_t)(requestSpeed*10) & 0xFF);
writeIncline[13] = ((uint16_t)(requestIncline*10) >> 8) & 0xFF;
writeIncline[14] = ((uint16_t)(requestIncline*10) & 0xFF);
writeIncline[16] = (uint8_t)(requestIncline * 10);
for(uint8_t i=0; i<sizeof(writeIncline)-1; i++)
{
//qDebug() << QString::number(writeIncline[i], 16);
writeIncline[22] += writeIncline[i]; // the last byte is a sort of a checksum
writeIncline[26] += writeIncline[i]; // the last byte is a sort of a checksum
}
//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));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)writeIncline, sizeof(writeIncline)));
}
bool domyostreadmill::changeFanSpeed(uint8_t speed)
{
uint8_t fanSpeed[] = {0xf0, 0xca, 0x00, 0x00};
if(speed > 5) return false;
fanSpeed[2] = speed;
for(uint8_t i=0; i<sizeof(fanSpeed)-1; i++)
{
fanSpeed[3] += fanSpeed[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(fanSpeed, 4, "changeFanSpeed speed=" + QString::number(speed));
return true;
}
void domyostreadmill::update()
{
static uint8_t sec1 = 0;
static QDateTime lastTime;
static bool first = true;
if(m_control->state() == QLowEnergyController::UnconnectedState)
{
emit disconnected();
return;
}
if(initRequest)
{
initRequest = false;
btinit(false);
}
else if(bttreadmill.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
static uint8_t first = 0;
static virtualtreadmill* v;
static uint32_t counter = 0;
Q_UNUSED(v);
//qDebug() << treadmill.isValid() << m_control->state() << gattCommunicationChannelService << gattWriteCharacteristic.isValid() << gattNotifyCharacteristic.isValid() << initDone;
if(treadmill.isValid() &&
(m_control->state() == QLowEnergyController::ConnectedState || m_control->state() == QLowEnergyController::DiscoveredState) &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
gattNotifyCharacteristic.isValid() &&
initDone)
{
QDateTime current = QDateTime::currentDateTime();
if(currentSpeed() > 0.0 && !first)
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
lastTime = current;
// updating the treadmill console every second
if(sec1++ >= (500 / refresh->interval()))
counter++;
if(!first)
{
if(incompletePackets == false && noConsole == false)
{
sec1 = 0;
updateDisplay(elapsed);
}
qDebug() << "creating virtual treadmill interface...";
v = new virtualtreadmill();
}
if(incompletePackets == false)
writeCharacteristic(noOpData, sizeof(noOpData), "noOp", true);
first = 1;
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)noOpData, sizeof(noOpData)));
// byte 3 - 4 = elapsed time
// byte 17 = inclination
if(incompletePackets == false)
if(requestSpeed != -1)
{
if(requestSpeed != -1)
{
if(requestSpeed != currentSpeed())
{
debug("writing speed " + QString::number(requestSpeed));
double inc = Inclination;
if(requestInclination != -1)
{
inc = requestInclination;
requestInclination = -1;
}
forceSpeedOrIncline(requestSpeed, inc);
}
requestSpeed = -1;
}
if(requestInclination != -1)
{
if(requestInclination != currentInclination())
{
debug("writing incline " + QString::number(requestInclination));
double speed = currentSpeed();
if(requestSpeed != -1)
{
speed = requestSpeed;
requestSpeed = -1;
}
forceSpeedOrIncline(speed, requestInclination);
}
requestInclination = -1;
}
if(requestStart != -1)
{
debug("starting...");
btinit(true);
requestStart = -1;
emit tapeStarted();
}
if(requestStop != -1)
{
debug("stopping...");
writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
if(requestIncreaseFan != -1)
{
debug("increasing fan speed...");
changeFanSpeed(FanSpeed + 1);
requestIncreaseFan = -1;
}
else if(requestDecreaseFan != -1)
{
debug("decreasing fan speed...");
changeFanSpeed(FanSpeed - 1);
requestDecreaseFan = -1;
}
qDebug() << "writing speed" << requestSpeed;
forceSpeedOrIncline(requestSpeed, currentIncline, counter/5);
requestSpeed = -1;
}
if(requestIncline != -1)
{
qDebug() << "writing incline" << requestIncline;
forceSpeedOrIncline(currentSpeed, requestIncline, counter/5);
requestIncline = -1;
}
if(requestStart != -1)
{
qDebug() << "starting...(TODO)";
requestStart = -1;
}
if(requestStop != -1)
{
qDebug() << "stopping...";
/*gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData1, sizeof(initData1)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData2, sizeof(initData2)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart, sizeof(initDataStart)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart2, sizeof(initDataStart2)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart3, sizeof(initDataStart3)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart4, sizeof(initDataStart4)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart5, sizeof(initDataStart5)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart6, sizeof(initDataStart6)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart7, sizeof(initDataStart7)));*/
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataF0C800B8, sizeof(initDataF0C800B8)));
requestStop = -1;
}
elevationAcc += (currentSpeed() / 3600.0) * 1000 * (currentInclination() / 100) * (refresh->interval() / 1000);
}
first = false;
}
void domyostreadmill::serviceDiscovered(const QBluetoothUuid &gatt)
{
debug("serviceDiscovered " + gatt.toString());
qDebug() << "serviceDiscovered" << gatt;
}
static QByteArray lastPacket;
@@ -303,100 +187,40 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
{
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
static QDateTime lastTime;
static bool first = true;
QByteArray value = newValue;
debug(" << " + QString::number(value.length()) + " " + value.toHex(' '));
if (lastPacket.length() && lastPacket == value)
if (lastPacket.length() && lastPacket == newValue)
return;
QByteArray startBytes;
startBytes.append(0xf0);
startBytes.append(0xbc);
QByteArray startBytes2;
startBytes2.append(0xf0);
startBytes2.append(0xdb);
// on some treadmills, the 26bytes has splitted in 2 packets
if((lastPacket.length() == 20 && lastPacket.startsWith(startBytes) && value.length() == 6) ||
(lastPacket.length() == 20 && lastPacket.startsWith(startBytes2) && value.length() == 7))
{
incompletePackets = false;
debug("...final bytes received");
lastPacket.append(value);
value = lastPacket;
}
lastPacket = value;
if (value.length() != 26)
{
// semaphore for any writing packets (for example, update display)
if(value.length() == 20 && (value.startsWith(startBytes) || value.startsWith(startBytes2)))
{
debug("waiting for other bytes...");
incompletePackets = true;
}
debug("packet ignored");
lastPacket = newValue;
if (newValue.length() != 26)
return;
}
if (value.at(22) == 0x06)
if (newValue.at(22) == 0x07)
{
debug("start button pressed!");
requestStart = 1;
}
else if (value.at(22) == 0x07)
{
debug("stop button pressed!");
qDebug() << "STOP PRESSED!";
requestStop = 1;
}
else if (value.at(22) == 0x0b)
{
debug("increase speed fan pressed!");
requestIncreaseFan = 1;
}
else if (value.at(22) == 0x0a)
{
debug("decrease speed fan pressed!");
requestDecreaseFan = 1;
}
/*if ((uint8_t)value.at(1) != 0xbc && value.at(2) != 0x04) // intense run, these are the bytes for the inclination and speed status
/*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the inclination and speed status
return;*/
double speed = GetSpeedFromPacket(value);
double incline = GetInclinationFromPacket(value);
double kcal = GetKcalFromPacket(value);
double distance = GetDistanceFromPacket(value);
double speed = GetSpeedFromPacket(newValue);
double incline = GetInclinationFromPacket(newValue);
//var isStartPressed = GetIsStartPressedFromPacket(currentPacket);
//var isStopPressed = GetIsStopPressedFromPacket(currentPacket);
Heart = value.at(18);
FanSpeed = value.at(23);
#if DEBUG
Debug.WriteLine(args.CharacteristicValue.ToArray().HexDump());
#endif
if(!first)
DistanceCalculated += ((speed / 3600.0) / ( 1000.0 / (lastTime.msecsTo(QDateTime::currentDateTime()))));
currentHeart = newValue.at(18);
debug("Current speed: " + QString::number(speed));
debug("Current incline: " + QString::number(incline));
debug("Current heart: " + QString::number(Heart));
debug("Current KCal: " + QString::number(kcal));
debug("Current Distance: " + QString::number(distance));
debug("Current Distance Calculated: " + QString::number(DistanceCalculated));
qDebug() << "Current speed: " << speed;
qDebug() << "Current incline: " << incline;
qDebug() << "Current heart:" << currentHeart;
if(m_control->error() != QLowEnergyController::NoError)
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
Speed = speed;
Inclination = incline;
KCal = kcal;
Distance = distance;
lastTime = QDateTime::currentDateTime();
first = false;
currentSpeed = speed;
currentIncline = incline;
}
double domyostreadmill::GetSpeedFromPacket(QByteArray packet)
@@ -406,157 +230,94 @@ double domyostreadmill::GetSpeedFromPacket(QByteArray packet)
return data;
}
double domyostreadmill::GetKcalFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(10) << 8) | packet.at(11);
return (double)convertedData;
}
double domyostreadmill::GetDistanceFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(12) << 8) | packet.at(13);
double data = ((double)convertedData) / 10.0f;
return data;
}
double domyostreadmill::GetInclinationFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(2) << 8) | packet.at(3);
qDebug() << convertedData;
double data = ((double)convertedData - 1000.0f) / 10.0f;
if (data < 0) return 0;
return data;
}
void domyostreadmill::btinit(bool startTape)
{
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData2, sizeof(initData2), "init", false, true);
writeCharacteristic(initDataStart, sizeof(initDataStart), "init", false, true);
writeCharacteristic(initDataStart2, sizeof(initDataStart2), "init", false, true);
writeCharacteristic(initDataStart3, sizeof(initDataStart3), "init", false, true);
writeCharacteristic(initDataStart4, sizeof(initDataStart4), "init", false, true);
writeCharacteristic(initDataStart5, sizeof(initDataStart5), "init", false, true);
writeCharacteristic(initDataStart6, sizeof(initDataStart6), "init", false, false);
writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
writeCharacteristic(initDataStart8, sizeof(initDataStart8), "init", false, false);
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
if(startTape)
{
writeCharacteristic(initDataStart11, sizeof(initDataStart11), "init", false, true);
writeCharacteristic(initDataStart12, sizeof(initDataStart12), "init", false, false);
writeCharacteristic(initDataStart13, sizeof(initDataStart13), "init", false, true);
}
initDone = true;
}
void domyostreadmill::stateChanged(QLowEnergyService::ServiceState state)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
qDebug() << "stateChanged" << state;
if(state == QLowEnergyService::ServiceDiscovered)
{
//qDebug() << gattCommunicationChannelService->characteristics();
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotifyCharacteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
this, SLOT(errorService(QLowEnergyService::ServiceError)));
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
// ******************************************* virtual treadmill init *************************************
static uint8_t first = 0;
if(!first)
{
debug("creating virtual treadmill interface...");
virtualTreadMill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&domyostreadmill::debug);
}
first = 1;
// ********************************************************************************************************
// await _gattNotifyCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData1, sizeof(initData1)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initData2, sizeof(initData2)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart, sizeof(initDataStart)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart2, sizeof(initDataStart2)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart3, sizeof(initDataStart3)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart4, sizeof(initDataStart4)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart5, sizeof(initDataStart5)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart6, sizeof(initDataStart6)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart7, sizeof(initDataStart7)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart8, sizeof(initDataStart8)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart9, sizeof(initDataStart9)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart10, sizeof(initDataStart10)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart11, sizeof(initDataStart11)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart12, sizeof(initDataStart12)));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)initDataStart13, sizeof(initDataStart13)));
initDone = true;
}
}
void domyostreadmill::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
{
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
}
void domyostreadmill::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
Q_UNUSED(characteristic);
debug("characteristicWritten " + newValue.toHex(' '));
}
void domyostreadmill::serviceScanDone(void)
{
debug("serviceScanDone");
qDebug() << "serviceScanDone";
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
gattCommunicationChannelService->discoverDetails();
}
void domyostreadmill::errorService(QLowEnergyService::ServiceError err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
debug("domyostreadmill::errorService " + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
}
void domyostreadmill::error(QLowEnergyController::Error err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
debug("domyostreadmill::error " + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
m_control->disconnect();
}
void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge"))
qDebug() << "Found new device:" << device.name() << '(' << device.address().toString() << ')';
if(device.name().startsWith("Domyos"))
{
bttreadmill = device;
m_control = QLowEnergyController::createCentral(bttreadmill, this);
discoveryAgent->stop();
treadmill = device;
m_control = QLowEnergyController::createCentral(treadmill, 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();
qDebug() << "Cannot connect to remote device.";
exit(1);
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
debug("Controller connected. Search services...");
qDebug() << "Controller connected. Search services...";
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
debug("LowEnergy controller disconnected");
emit disconnected();
qDebug() << "LowEnergy controller disconnected";
exit(2);
});
// Connect
@@ -564,25 +325,3 @@ void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
return;
}
}
bool domyostreadmill::connected()
{
if(!m_control)
return false;
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void* domyostreadmill::VirtualTreadMill()
{
return virtualTreadMill;
}
void* domyostreadmill::VirtualDevice()
{
return VirtualTreadMill();
}
double domyostreadmill::odometer()
{
return DistanceCalculated;
}

View File

@@ -18,65 +18,33 @@
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qmutex.h>
#include <QObject>
#include "virtualtreadmill.h"
#include "treadmill.h"
class domyostreadmill : public treadmill
class domyostreadmill : QObject
{
Q_OBJECT
public:
domyostreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false);
bool connected();
bool changeFanSpeed(uint8_t speed);
double odometer();
void* VirtualTreadMill();
void* VirtualDevice();
domyostreadmill();
private:
double GetSpeedFromPacket(QByteArray packet);
double GetInclinationFromPacket(QByteArray packet);
double GetKcalFromPacket(QByteArray packet);
double GetDistanceFromPacket(QByteArray packet);
void forceSpeedOrIncline(double requestSpeed, double requestIncline);
void updateDisplay(uint16_t elapsed);
void btinit(bool startTape);
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log = false, bool wait_for_response = false);
void startDiscover();
double DistanceCalculated = 0;
volatile bool incompletePackets = false;
bool noConsole = false;
bool noHeartService = false;
uint32_t pollDeviceTime = 200;
QTimer* refresh;
virtualtreadmill* virtualTreadMill = 0;
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void forceSpeedOrIncline(double requestSpeed, double requestIncline, uint16_t elapsed);
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 serviceScanDone(void);
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // DOMYOSTREADMILL_H

View File

@@ -1,49 +0,0 @@
#include "gpx.h"
#include <QDomDocument>
#include <QDebug>
#include "math.h"
gpx::gpx(QObject *parent) : QObject(parent)
{
}
QList<gpx_altitude_point_for_treadmill> gpx::open(QString gpx)
{
QFile input(gpx);
input.open(QIODevice::ReadOnly);
QDomDocument doc;
doc.setContent(&input);
QDomNodeList points = doc.elementsByTagName("trkpt");
for (int i = 0; i < points.size(); i++)
{
QDomNode point = points.item(i);
QDomNamedNodeMap att = point.attributes();
QString lat = att.namedItem("lat").nodeValue();
QString lon = att.namedItem("lon").nodeValue();
QDomElement ele = point.firstChildElement("ele");
QDomElement time = point.firstChildElement("time");
gpx_point g;
//2020-10-10T10:54:45
g.time = QDateTime::fromString(time.text(), Qt::ISODate);
g.p.setAltitude(ele.text().toFloat());
g.p.setLatitude(lat.toFloat());
g.p.setLongitude(lon.toFloat());
this->points.append(g);
}
const uint8_t secondsInclination = 60;
QList<gpx_altitude_point_for_treadmill> inclinationList;
for(int32_t i=secondsInclination; i<this->points.count(); i+=secondsInclination)
{
double distance = this->points[i].p.distanceTo(this->points[i-secondsInclination].p);
double elevation = this->points[i].p.altitude() - this->points[i-secondsInclination].p.altitude();
gpx_altitude_point_for_treadmill g;
g.seconds = secondsInclination;
g.speed = (distance / 1000.0) * (3600 / secondsInclination);
g.inclination = (elevation / distance) * 100;
inclinationList.append(g);
}
return inclinationList;
}

View File

@@ -1,38 +0,0 @@
#ifndef GPX_H
#define GPX_H
#include <QObject>
#include <QFile>
#include <QTime>
#include <QGeoCoordinate>
class gpx_altitude_point_for_treadmill
{
public:
uint32_t seconds;
float inclination;
float speed;
};
class gpx_point
{
public:
QDateTime time;
QGeoCoordinate p;
};
class gpx : public QObject
{
Q_OBJECT
public:
explicit gpx(QObject *parent = nullptr);
QList<gpx_altitude_point_for_treadmill> open(QString gpx);
private:
QList<gpx_point> points;
signals:
};
#endif // GPX_H

View File

@@ -1,252 +0,0 @@
#include "homeform.h"
#include <QQmlContext>
#include <QTime>
DataObject::DataObject(QString name, QString icon, QString value, bool writable, QString id)
{
m_name = name;
m_icon = icon;
m_value = value;
m_writable = writable;
m_id = id;
emit plusNameChanged(plusName());
emit minusNameChanged(minusName());
}
void DataObject::setValue(QString v) {m_value = v; emit valueChanged(m_value);}
homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
{
this->bluetoothManager = bl;
connect(bluetoothManager, SIGNAL(deviceFound(QString)), this, SLOT(deviceFound(QString)));
engine->rootContext()->setContextProperty("rootItem", (QObject *)this);
dataList = {
speed,
inclination,
cadence,
elevation,
calories,
odometer,
pace,
resistance,
watt,
heart,
fan
};
engine->rootContext()->setContextProperty("appModel", QVariant::fromValue(dataList));
QObject *rootObject = engine->rootObjects().first();
QObject *home = rootObject->findChild<QObject*>("home");
QObject::connect(home, SIGNAL(start_clicked()),
this, SLOT(Start()));
QObject::connect(home, SIGNAL(stop_clicked()),
this, SLOT(Stop()));
QObject::connect(home, SIGNAL(plus_clicked(QString)),
this, SLOT(Plus(QString)));
QObject::connect(home, SIGNAL(minus_clicked(QString)),
this, SLOT(Minus(QString)));
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &homeform::update);
timer->start(1000);
}
void homeform::deviceFound(QString name)
{
m_info = name + " founded";
emit infoChanged(m_info);
}
void homeform::Plus(QString name)
{
if(name.contains("speed"))
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() + 0.5);
}
}
}
else if(name.contains("inclination"))
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() + 0.5);
}
}
}
else if(name.contains("resistance"))
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() + 1);
}
}
}
else if(name.contains("fan"))
{
if(bluetoothManager->device())
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() + 1);
}
else
{
qDebug() << name << "not handled";
}
}
void homeform::Minus(QString name)
{
if(name.contains("speed"))
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() - 0.5);
}
}
}
else if(name.contains("inclination"))
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() - 0.5);
}
}
}
else if(name.contains("resistance"))
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() - 1);
}
}
}
else if(name.contains("fan"))
{
if(bluetoothManager->device())
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() - 1);
}
else
{
qDebug() << name << "not handled";
}
}
void homeform::Start()
{
//trainProgram->restart();
if(bluetoothManager->device())
bluetoothManager->device()->start();
}
void homeform::Stop()
{
if(bluetoothManager->device())
bluetoothManager->device()->stop();
}
void homeform::update()
{
if(bluetoothManager->device())
{
double inclination = 0;
double resistance = 0;
double watts = 0;
double pace = 0;
speed->setValue(QString::number(bluetoothManager->device()->currentSpeed(), 'f', 2));
heart->setValue(QString::number(bluetoothManager->device()->currentHeart()));
odometer->setValue(QString::number(bluetoothManager->device()->odometer(), 'f', 2));
calories->setValue(QString::number(bluetoothManager->device()->calories(), 'f', 0));
fan->setValue(QString::number(bluetoothManager->device()->fanSpeed()));
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
if(pace < 0) pace = 0;
watts = ((treadmill*)bluetoothManager->device())->watts(/*weight->text().toFloat()*/); // TODO: add weight to settings
inclination = ((treadmill*)bluetoothManager->device())->currentInclination();
this->pace->setValue(((treadmill*)bluetoothManager->device())->currentPace().toString("m:ss"));
watt->setValue(QString::number(watts, 'f', 0));
this->inclination->setValue(QString::number(inclination, 'f', 1));
elevation->setValue(QString::number(((treadmill*)bluetoothManager->device())->elevationGain(), 'f', 1));
}
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
resistance = ((bike*)bluetoothManager->device())->currentResistance();
watts = ((bike*)bluetoothManager->device())->watts();
watt->setValue(QString::number(watts));
this->resistance->setValue(QString::number(resistance));
}
/*
if(trainProgram)
{
trainProgramElapsedTime->setText(trainProgram->totalElapsedTime().toString("hh:mm:ss"));
trainProgramCurrentRowElapsedTime->setText(trainProgram->currentRowElapsedTime().toString("hh:mm:ss"));
trainProgramDuration->setText(trainProgram->duration().toString("hh:mm:ss"));
double distance = trainProgram->totalDistance();
if(distance > 0)
{
trainProgramTotalDistance->setText(QString::number(distance));
}
else
trainProgramTotalDistance->setText("N/A");
}
*/
SessionLine s(
bluetoothManager->device()->currentSpeed(),
inclination,
bluetoothManager->device()->odometer(),
watts,
resistance,
bluetoothManager->device()->currentHeart(),
pace);
Session.append(s);
}
emit changeOfdevice();
emit changeOfzwift();
}
bool homeform::getDevice()
{
if(!this->bluetoothManager->device())
return false;
return this->bluetoothManager->device()->connected();
}
bool homeform::getZwift()
{
if(!this->bluetoothManager->device())
return false;
if(!this->bluetoothManager->device()->VirtualDevice())
return false;
if(this->bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL &&
((virtualtreadmill*)((treadmill*)bluetoothManager->device())->VirtualDevice())->connected())
{
return true;
}
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE &&
((virtualbike*)((bike*)bluetoothManager->device())->VirtualDevice())->connected())
{
return true;
}
return false;
}

View File

@@ -1,94 +0,0 @@
#ifndef HOMEFORM_H
#define HOMEFORM_H
#include <QQuickItem>
#include <QQmlApplicationEngine>
#include "bluetooth.h"
#include "sessionline.h"
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(bool writable READ writable NOTIFY writableChanged)
Q_PROPERTY(QString plusName READ plusName NOTIFY plusNameChanged)
Q_PROPERTY(QString minusName READ minusName NOTIFY minusNameChanged)
public:
DataObject(QString name, QString icon, QString value, bool writable, QString id);
void setValue(QString value);
QString name() {return m_name;}
QString icon() {return m_icon;}
QString value() {return m_value;}
bool writable() {return m_writable;}
QString plusName() {return m_id + "_plus";}
QString minusName() {return m_id + "_minus";}
QString m_id;
QString m_name;
QString m_icon;
QString m_value;
bool m_writable;
signals:
void valueChanged(QString value);
void nameChanged(QString value);
void iconChanged(QString value);
void writableChanged(bool value);
void plusNameChanged(QString value);
void minusNameChanged(QString value);
};
class homeform: public QObject
{
Q_OBJECT
Q_PROPERTY( bool device READ getDevice NOTIFY changeOfdevice)
Q_PROPERTY( bool zwift READ getZwift NOTIFY changeOfzwift)
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
public:
homeform(QQmlApplicationEngine* engine, bluetooth* bl);
QString info() {return m_info;}
private:
QList<QObject *> dataList;
QList<SessionLine> Session;
bluetooth* bluetoothManager;
QString m_info = "Connecting...";
DataObject* speed = new DataObject("Speed (km/h)", "icons/icons/speed.png", "0.0", true, "speed");
DataObject* inclination = new DataObject("Inclination (%)", "icons/icons/inclination.png", "0.0", true, "inclination");
DataObject* cadence = new DataObject("Cadence (bpm)", "icons/icons/cadence.png", "0", false, "cadence");
DataObject* elevation = new DataObject("Elev. Gain (m)", "icons/icons/elevationgain.png", "0", false, "elevation");
DataObject* calories = new DataObject("Calories (KCal)", "icons/icons/kcal.png", "0", false, "calories");
DataObject* odometer = new DataObject("Odometer (km)", "icons/icons/odometer.png", "0.0", false, "odometer");
DataObject* pace = new DataObject("Pace (m/km)", "icons/icons/pace.png", "0:00", false, "pace");
DataObject* resistance = new DataObject("Resistance (%)", "icons/icons/resistance.png", "0", true, "resistance");
DataObject* watt = new DataObject("Watt", "icons/icons/watt.png", "0", false, "watt");
DataObject* heart = new DataObject("Heart (bpm)", "icons/icons/heart_red.png", "0", false, "heart");
DataObject* fan = new DataObject("Fan Speed", "icons/icons/fan.png", "0", true, "fan");
QTimer* timer;
void update();
bool getDevice();
bool getZwift();
private slots:
void Start();
void Stop();
void Minus(QString);
void Plus(QString);
void deviceFound(QString name);
signals:
void changeOfdevice();
void changeOfzwift();
void infoChanged(QString value);
};
#endif // HOMEFORM_H

View File

@@ -1,22 +0,0 @@
<RCC>
<qresource prefix="/icons">
<file>icons/bluetooth-icon.png</file>
<file>icons/zwift-on.png</file>
<file>icons/fan.png</file>
<file>icons/speed.png</file>
<file>icons/inclination.png</file>
<file>icons/heart_red.png</file>
<file>icons/watt.png</file>
<file>icons/odometer.png</file>
<file>icons/elevationgain.png</file>
<file>icons/kcal.png</file>
<file>icons/weight.png</file>
<file>icons/start.png</file>
<file>icons/stop.png</file>
<file>icons/cadence.png</file>
<file>icons/resistance.png</file>
<file>icons/pace.png</file>
<file>icons/chart.png</file>
<file>icons/icon.png</file>
</qresource>
</RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -1,191 +1,21 @@
#include <QApplication>
#include <QStyleFactory>
#include <stdio.h>
#include <stdlib.h>
#include <QStandardPaths>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtCore/qcoreapplication.h>
#include "virtualtreadmill.h"
#include "domyostreadmill.h"
#include "bluetooth.h"
#include "mainwindow.h"
#include "homeform.h"
bool nologs = false;
bool noWriteResistance = false;
bool noHeartService = true;
bool noConsole = false;
bool onlyVirtualBike = false;
bool onlyVirtualTreadmill = false;
bool testResistance = false;
QString trainProgram;
QString deviceName = "";
uint32_t pollDeviceTime = 200;
static QString logfilename = "debug-" + QDateTime::currentDateTime().toString().replace(":", "_") + ".log";
QCoreApplication* createApplication(int &argc, char *argv[])
{
bool nogui = false;
for (int i = 1; i < argc; ++i) {
if (!qstrcmp(argv[i], "-no-gui"))
nogui = true;
if (!qstrcmp(argv[i], "-no-console"))
noConsole = true;
if (!qstrcmp(argv[i], "-test-resistance"))
testResistance = true;
if (!qstrcmp(argv[i], "-no-log"))
nologs = true;
if (!qstrcmp(argv[i], "-no-write-resistance"))
noWriteResistance = true;
if (!qstrcmp(argv[i], "-no-heart-service"))
noHeartService = true;
if (!qstrcmp(argv[i], "-heart-service"))
noHeartService = false;
if (!qstrcmp(argv[i], "-only-virtualbike"))
onlyVirtualBike = true;
if (!qstrcmp(argv[i], "-only-virtualtreadmill"))
onlyVirtualTreadmill = true;
if (!qstrcmp(argv[i], "-train"))
{
trainProgram = argv[++i];
}
if (!qstrcmp(argv[i], "-name"))
{
deviceName = argv[++i];
}
if (!qstrcmp(argv[i], "-poll-device-time"))
{
pollDeviceTime = atol(argv[++i]);
}
}
if(nogui)
return new QCoreApplication(argc, argv);
else
{
QApplication* a = new QApplication(argc, argv);
a->setStyle(QStyleFactory::create("Fusion"));
/*QFont defaultFont = QApplication::font();
defaultFont.setPointSize(defaultFont.pointSize()+2);
qApp->setFont(defaultFont);*/
// modify palette to dark
QPalette darkPalette;
darkPalette.setColor(QPalette::Window,QColor(53,53,53));
darkPalette.setColor(QPalette::WindowText,Qt::white);
darkPalette.setColor(QPalette::Disabled,QPalette::WindowText,QColor(127,127,127));
darkPalette.setColor(QPalette::Base,QColor(42,42,42));
darkPalette.setColor(QPalette::AlternateBase,QColor(66,66,66));
darkPalette.setColor(QPalette::ToolTipBase,Qt::white);
darkPalette.setColor(QPalette::ToolTipText,Qt::white);
darkPalette.setColor(QPalette::Text,Qt::white);
darkPalette.setColor(QPalette::Disabled,QPalette::Text,QColor(127,127,127));
darkPalette.setColor(QPalette::Dark,QColor(35,35,35));
darkPalette.setColor(QPalette::Shadow,QColor(20,20,20));
darkPalette.setColor(QPalette::Button,QColor(53,53,53));
darkPalette.setColor(QPalette::ButtonText,Qt::white);
darkPalette.setColor(QPalette::Disabled,QPalette::ButtonText,QColor(127,127,127));
darkPalette.setColor(QPalette::BrightText,Qt::red);
darkPalette.setColor(QPalette::Link,QColor(42,130,218));
darkPalette.setColor(QPalette::Highlight,QColor(42,130,218));
darkPalette.setColor(QPalette::Disabled,QPalette::Highlight,QColor(80,80,80));
darkPalette.setColor(QPalette::HighlightedText,Qt::white);
darkPalette.setColor(QPalette::Disabled,QPalette::HighlightedText,QColor(127,127,127));
qApp->setPalette(darkPalette);
return a;
}
}
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
const char *file = context.file ? context.file : "";
const char *function = context.function ? context.function : "";
QString txt;
switch (type) {
case QtInfoMsg:
txt = QString("Info: %1 %2 %3\n").arg(file).arg(function).arg(msg);
break;
case QtDebugMsg:
txt = QString("Debug: %1 %2 %3\n").arg(file).arg(function).arg(msg);
break;
case QtWarningMsg:
txt = QString("Warning: %1 %2 %3\n").arg(file).arg(function).arg(msg);
break;
case QtCriticalMsg:
txt = QString("Critical: %1 %2 %3\n").arg(file).arg(function).arg(msg);
break;
case QtFatalMsg:
txt = QString("Fatal: %1 %2 %3\n").arg(file).arg(function).arg(msg);
abort();
}
if(nologs == false)
{
QString path = "";
#ifdef Q_OS_ANDROID
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
#endif
QFile outFile(path + logfilename);
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream ts(&outFile);
ts << txt;
fprintf(stderr, txt.toLocal8Bit());
}
}
int main(int argc, char *argv[])
{
//QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
#ifndef Q_OS_ANDROID
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
qInstallMessageHandler(myMessageOutput);
if(onlyVirtualBike)
{
virtualbike* V = new virtualbike(new bike(), noWriteResistance, noHeartService);
return app->exec();
}
else if(onlyVirtualTreadmill)
{
virtualtreadmill* V = new virtualtreadmill(new treadmill(), noHeartService);
return app->exec();
}
#endif
bluetooth* bl = new bluetooth(!nologs, deviceName, noWriteResistance, noHeartService, pollDeviceTime, testResistance);
#ifdef Q_OS_ANDROID
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication app(argc, argv);
#else
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);
#endif
//virtualtreadmill* V = new virtualtreadmill();
domyostreadmill* D = new domyostreadmill();
//Q_UNUSED(V);
Q_UNUSED(D);
return app.exec();
#else
if (qobject_cast<QApplication *>(app.data())) {
// start GUI version...
MainWindow* W = 0;
if(trainProgram.isEmpty())
W = new MainWindow(bl);
else
W = new MainWindow(bl, trainProgram);
W->show();
} else {
// start non-GUI version...
}
return app->exec();
#endif
}

View File

@@ -1,67 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
title: qsTr("Stack")
header: ToolBar {
contentHeight: toolButton.implicitHeight
ToolButton {
id: toolButton
icon.source: "icons/icons/icon.png"
text: stackView.depth > 1 ? "\u25C0" : "\u2630"
font.pixelSize: Qt.application.font.pixelSize * 1.6
onClicked: {
if (stackView.depth > 1) {
stackView.pop()
} else {
drawer.open()
}
}
}
Label {
text: stackView.currentItem.title
anchors.centerIn: parent
}
}
Drawer {
id: drawer
width: window.width * 0.66
height: window.height
Column {
anchors.fill: parent
ItemDelegate {
text: qsTr("by Roberto Viola")
width: parent.width
/* onClicked: {
stackView.push("Page1Form.ui.qml")
drawer.close()
}*/
}
/* ItemDelegate {
text: qsTr("Page 2")
width: parent.width
onClicked: {
stackView.push("Page2Form.ui.qml")
drawer.close()
}
}*/
}
}
StackView {
id: stackView
initialItem: "Home.qml"
anchors.fill: parent
}
}

View File

@@ -1,540 +0,0 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include "gpx.h"
#include "charts.h"
charts* Charts = 0;
void MainWindow::load(bluetooth* b)
{
ui->setupUi(this);
this->bluetoothManager = b;
connect(this->bluetoothManager, SIGNAL(deviceConnected()), this, SLOT(trainProgramSignals()));
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::update);
timer->start(1000);
#ifdef Q_OS_ANDROID
ui->groupTrain->setVisible(false);
ui->fanBar->setVisible(false);
ui->fanSpeedMinus->setVisible(false);
ui->fanSpeedPlus->setVisible(false);
ui->weight->setVisible(false);
ui->weightDescription->setVisible(false);
ui->weightIcon->setVisible(false);
ui->load->setVisible(false);
ui->reset->setVisible(false);
ui->save->setVisible(false);
#endif
update();
}
MainWindow::MainWindow(bluetooth* b) :
QDialog(nullptr),
ui(new Ui::MainWindow)
{
load(b);
this->trainProgram = new trainprogram(QList<trainrow>(), b);
}
MainWindow::MainWindow(bluetooth* b, QString trainProgram) :
QDialog(nullptr),
ui(new Ui::MainWindow)
{
load(b);
loadTrainProgram(trainProgram);
}
void MainWindow::update()
{
if(bluetoothManager->device())
{
double inclination = 0;
double resistance = 0;
double watts = 0;
double pace = 0;
ui->speed->setText(QString::number(bluetoothManager->device()->currentSpeed(), 'f', 2));
ui->heartrate->setText(QString::number(bluetoothManager->device()->currentHeart()));
ui->odometer->setText(QString::number(bluetoothManager->device()->odometer(), 'f', 2));
ui->calories->setText(QString::number(bluetoothManager->device()->calories(), 'f', 0));
ui->fanBar->setValue(bluetoothManager->device()->fanSpeed());
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
if(pace < 0) pace = 0;
watts = ((treadmill*)bluetoothManager->device())->watts(ui->weight->text().toFloat());
inclination = ((treadmill*)bluetoothManager->device())->currentInclination();
ui->pace->setText(((treadmill*)bluetoothManager->device())->currentPace().toString("m:ss"));
ui->watt->setText(QString::number(watts, 'f', 0));
ui->inclination->setText(QString::number(inclination, 'f', 1));
ui->elevationGain->setText(QString::number(((treadmill*)bluetoothManager->device())->elevationGain(), 'f', 1));
}
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
resistance = ((bike*)bluetoothManager->device())->currentResistance();
watts = ((bike*)bluetoothManager->device())->watts();
ui->watt->setText(QString::number(watts));
ui->resistance->setText(QString::number(resistance));
}
if(trainProgram)
{
ui->trainProgramElapsedTime->setText(trainProgram->totalElapsedTime().toString("hh:mm:ss"));
ui->trainProgramCurrentRowElapsedTime->setText(trainProgram->currentRowElapsedTime().toString("hh:mm:ss"));
ui->trainProgramDuration->setText(trainProgram->duration().toString("hh:mm:ss"));
double distance = trainProgram->totalDistance();
if(distance > 0)
{
ui->trainProgramTotalDistance->setText(QString::number(distance));
}
else
ui->trainProgramTotalDistance->setText("N/A");
}
if(bluetoothManager->device()->connected())
{
ui->connectionToTreadmill->setEnabled(true);
if(bluetoothManager->device()->VirtualDevice())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL &&
((virtualtreadmill*)((treadmill*)bluetoothManager->device())->VirtualDevice())->connected())
{
ui->connectionToZwift->setEnabled(true);
}
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE &&
((virtualbike*)((bike*)bluetoothManager->device())->VirtualDevice())->connected())
{
ui->connectionToZwift->setEnabled(true);
}
else
ui->connectionToZwift->setEnabled(false);
}
else
ui->connectionToZwift->setEnabled(false);
}
else
ui->connectionToTreadmill->setEnabled(false);
SessionLine s(
bluetoothManager->device()->currentSpeed(),
inclination,
bluetoothManager->device()->odometer(),
watts,
resistance,
bluetoothManager->device()->currentHeart(),
pace);
Session.append(s);
if(ui->chart->isChecked())
{
if(!Charts)
{
Charts = new charts(this);
Charts->show();
}
Charts->update();
}
}
else
{
ui->connectionToTreadmill->setEnabled(false);
ui->connectionToZwift->setEnabled(false);
/*
* DEBUG CHARTS
*
if(!Charts)
{
Charts = new charts(this);
Charts->show();
}
SessionLine s(
(double)QRandomGenerator::global()->bounded(22),
QRandomGenerator::global()->bounded(15),
(double)QRandomGenerator::global()->bounded(15),
QRandomGenerator::global()->bounded(150),
0,
QRandomGenerator::global()->bounded(180));
Session.append(s);
Charts->update();*/
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addEmptyRow()
{
int row = ui->tableWidget->rowCount();
editing = true;
ui->tableWidget->insertRow(row);
ui->tableWidget->setItem(row, 0, new QTableWidgetItem("00:00:00"));
ui->tableWidget->setItem(row, 1, new QTableWidgetItem("10"));
ui->tableWidget->setItem(row, 2, new QTableWidgetItem("0"));
ui->tableWidget->setItem(row, 3, new QTableWidgetItem(""));
ui->tableWidget->item(row, 0)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidget->item(row, 1)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidget->item(row, 2)->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidget->item(row, 3)->setCheckState(Qt::CheckState::Checked);
editing = false;
}
void MainWindow::on_tableWidget_cellChanged(int row, int column)
{
if(editing) return;
if(column == 0)
{
switch(ui->tableWidget->currentItem()->text().length())
{
case 4:
ui->tableWidget->currentItem()->setText("00:0" + ui->tableWidget->currentItem()->text());
break;
case 5:
ui->tableWidget->currentItem()->setText("00:" + ui->tableWidget->currentItem()->text());
break;
case 7:
ui->tableWidget->currentItem()->setText("0" + ui->tableWidget->currentItem()->text());
break;
}
QString fmt = "hh:mm:ss";
QTime dt = QTime::fromString(ui->tableWidget->currentItem()->text());
QString timeStr = dt.toString("hh:mm:ss");
ui->tableWidget->currentItem()->setText(timeStr);
}
if(row + 1 == ui->tableWidget->rowCount() && ui->tableWidget->currentItem()->text().length() )
addEmptyRow();
QList<trainrow> rows;
for(int i = 0; i < ui->tableWidget->rowCount(); i++)
{
if(!ui->tableWidget->item(i, 0)->text().contains("00:00:00"))
{
trainrow t;
t.duration = QTime::fromString(ui->tableWidget->item(i, 0)->text(), "hh:mm:ss");
t.speed = ui->tableWidget->item(i, 1)->text().toFloat();
t.inclination = ui->tableWidget->item(i, 2)->text().toFloat();
t.forcespeed = ui->tableWidget->item(i, 3)->checkState() == Qt::CheckState::Checked;
rows.append(t);
}
else
{
break;
}
createTrainProgram(rows);
}
}
void MainWindow::trainProgramSignals()
{
if(bluetoothManager->device())
{
disconnect(trainProgram, SIGNAL(start()), bluetoothManager->device(), SLOT(start()));
disconnect(trainProgram, SIGNAL(stop()), bluetoothManager->device(), SLOT(stop()));
disconnect(trainProgram, SIGNAL(changeSpeed(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeed(double)));
disconnect(trainProgram, SIGNAL(changeInclination(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeInclination(double)));
disconnect(trainProgram, SIGNAL(changeSpeedAndInclination(double, double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeedAndInclination(double, double)));
disconnect(trainProgram, SIGNAL(changeResistance(double)), ((bike*)bluetoothManager->device()), SLOT(changeResistance(double)));
disconnect(((treadmill*)bluetoothManager->device()), SIGNAL(tapeStarted()), trainProgram, SLOT(onTapeStarted()));
disconnect(((bike*)bluetoothManager->device()), SIGNAL(bikeStarted()), trainProgram, SLOT(onTapeStarted()));
connect(trainProgram, SIGNAL(start()), bluetoothManager->device(), SLOT(start()));
connect(trainProgram, SIGNAL(stop()), bluetoothManager->device(), SLOT(stop()));
connect(trainProgram, SIGNAL(changeSpeed(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeed(double)));
connect(trainProgram, SIGNAL(changeInclination(double)), ((treadmill*)bluetoothManager->device()), SLOT(changeInclination(double)));
connect(trainProgram, SIGNAL(changeSpeedAndInclination(double, double)), ((treadmill*)bluetoothManager->device()), SLOT(changeSpeedAndInclination(double, double)));
connect(trainProgram, SIGNAL(changeResistance(double)), ((bike*)bluetoothManager->device()), SLOT(changeResistance(double)));
connect(((treadmill*)bluetoothManager->device()), SIGNAL(tapeStarted()), trainProgram, SLOT(onTapeStarted()));
connect(((bike*)bluetoothManager->device()), SIGNAL(bikeStarted()), trainProgram, SLOT(onTapeStarted()));
qDebug() << "trainProgram associated to a device";
}
else
{
qDebug() << "trainProgram NOT associated to a device";
}
}
void MainWindow::createTrainProgram(QList<trainrow> rows)
{
if(trainProgram) delete trainProgram;
trainProgram = new trainprogram(rows, bluetoothManager);
if(rows.length() == 0)
addEmptyRow();
trainProgramSignals();
}
void MainWindow::on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
{
Q_UNUSED(current);
Q_UNUSED(previous);
}
void MainWindow::on_save_clicked()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
"train.xml",
tr("Train Program (*.xml)"));
if(!fileName.isEmpty() && trainProgram)
trainProgram->save(fileName);
}
void MainWindow::loadTrainProgram(QString fileName)
{
if(!fileName.isEmpty())
{
ui->difficulty->setValue(50);
int rows = ui->tableWidget->rowCount();
for(int i = 0; i<rows; i++)
ui->tableWidget->removeRow(ui->tableWidget->rowCount() - 1);
if(fileName.endsWith("xml"))
{
if(trainProgram)
delete trainProgram;
trainProgram = trainprogram::load(fileName, bluetoothManager);
}
else if(fileName.endsWith("gpx"))
{
if(trainProgram)
delete trainProgram;
gpx g;
QList<trainrow> list;
foreach(gpx_altitude_point_for_treadmill p, g.open(fileName))
{
trainrow r;
r.speed = p.speed;
r.duration = QTime(0,0,0,0);
r.duration = r.duration.addSecs(p.seconds);
r.inclination = p.inclination;
r.forcespeed = true;
list.append(r);
}
trainProgram = new trainprogram(list, bluetoothManager);
}
else
{
return;
}
int countRow = 0;
foreach(trainrow row, trainProgram->rows)
{
if(ui->tableWidget->rowCount() <= countRow)
addEmptyRow();
QTableWidgetItem* i;
editing = true;
i = ui->tableWidget->takeItem(countRow, 0);
i->setText(row.duration.toString("hh:mm:ss"));
ui->tableWidget->setItem(countRow, 0, i);
i = ui->tableWidget->takeItem(countRow, 1);
i->setText(QString::number(row.speed));
ui->tableWidget->setItem(countRow, 1, i);
i = ui->tableWidget->takeItem(countRow, 2);
i->setText(QString::number(row.inclination));
ui->tableWidget->setItem(countRow, 2, i);
i = ui->tableWidget->takeItem(countRow, 3);
i->setCheckState(row.forcespeed?Qt::CheckState::Checked:Qt::CheckState::Unchecked);
ui->tableWidget->setItem(countRow, 3, i);
editing = false;
countRow++;
}
trainProgramSignals();
ui->groupTrain->setChecked(true);
}
}
void MainWindow::on_load_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
"train.xml",
tr("Train Program (*.xml *.gpx)"));
loadTrainProgram(fileName);
}
void MainWindow::on_reset_clicked()
{
if(bluetoothManager->device() && bluetoothManager->device()->currentSpeed() > 0) return;
int countRow = 0;
foreach(trainrow row, trainProgram->rows)
{
QTableWidgetItem* i;
editing = true;
i = ui->tableWidget->takeItem(countRow, 0);
i->setText("00:00:00");
ui->tableWidget->setItem(countRow, 0, i);
i = ui->tableWidget->takeItem(countRow, 1);
i->setText("0");
ui->tableWidget->setItem(countRow, 1, i);
i = ui->tableWidget->takeItem(countRow, 2);
i->setText("0");
ui->tableWidget->setItem(countRow, 2, i);
i = ui->tableWidget->takeItem(countRow, 3);
i->setCheckState(row.forcespeed?Qt::CheckState::Checked:Qt::CheckState::Unchecked);
ui->tableWidget->setItem(countRow, 3, i);
editing = false;
countRow++;
}
createTrainProgram(QList<trainrow>());
}
void MainWindow::on_stop_clicked()
{
if(bluetoothManager->device())
bluetoothManager->device()->stop();
}
void MainWindow::on_start_clicked()
{
trainProgram->restart();
if(bluetoothManager->device())
bluetoothManager->device()->start();
}
void MainWindow::on_groupTrain_clicked()
{
if(!trainProgram)
createTrainProgram(QList<trainrow>());
trainProgram->enabled = ui->groupTrain->isChecked();
}
void MainWindow::on_fanSpeedMinus_clicked()
{
if(bluetoothManager->device())
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() - 1);
}
void MainWindow::on_fanSpeedPlus_clicked()
{
if(bluetoothManager->device())
bluetoothManager->device()->changeFanSpeed(bluetoothManager->device()->fanSpeed() + 1);
}
void MainWindow::on_difficulty_valueChanged(int value)
{
if(editing) return;
for(int i=0;i<trainProgram->rows.count(); i++)
{
trainProgram->rows[i].speed = trainProgram->loadedRows[i].speed +
(trainProgram->loadedRows[i].speed * (0.02 * (value - 50)));
trainProgram->rows[i].inclination = trainProgram->loadedRows[i].inclination +
(trainProgram->loadedRows[i].inclination * (0.02 * (value - 50)));
}
int countRow = 0;
foreach(trainrow row, trainProgram->rows)
{
QTableWidgetItem* i;
editing = true;
i = ui->tableWidget->takeItem(countRow, 1);
i->setText(QString::number(row.speed));
ui->tableWidget->setItem(countRow, 1, i);
i = ui->tableWidget->takeItem(countRow, 2);
i->setText(QString::number(row.inclination));
ui->tableWidget->setItem(countRow, 2, i);
editing = false;
countRow++;
}
ui->difficulty->setToolTip(QString::number(value) + "%");
}
void MainWindow::on_speedMinus_clicked()
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() - 0.5);
}
}
}
void MainWindow::on_speedPlus_clicked()
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeSpeed(((treadmill*)bluetoothManager->device())->currentSpeed() + 0.5);
}
}
}
void MainWindow::on_inclinationMinus_clicked()
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() - 0.5);
}
}
}
void MainWindow::on_inclinationPlus_clicked()
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
((treadmill*)bluetoothManager->device())->changeInclination(((treadmill*)bluetoothManager->device())->currentInclination() + 0.5);
}
}
}
void MainWindow::on_resistanceMinus_clicked()
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() - 1);
}
}
}
void MainWindow::on_resistancePlus_clicked()
{
if(bluetoothManager->device())
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
((bike*)bluetoothManager->device())->changeResistance(((bike*)bluetoothManager->device())->currentResistance() + 1);
}
}
}
void MainWindow::on_chart_clicked()
{
}

View File

@@ -1,63 +0,0 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QDialog>
#include <QTimer>
#include <QTime>
#include <QDebug>
#include <QTableWidgetItem>
#include "trainprogram.h"
#include "domyostreadmill.h"
#include "sessionline.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QDialog
{
Q_OBJECT
public:
QList<SessionLine> Session;
explicit MainWindow(bluetooth* t);
explicit MainWindow(bluetooth* t, QString trainProgram);
~MainWindow();
private:
void addEmptyRow();
void load(bluetooth* device);
void loadTrainProgram(QString fileName);
void createTrainProgram(QList<trainrow> rows);
bool editing = false;
trainprogram* trainProgram = 0;
Ui::MainWindow *ui;
QTimer *timer;
bluetooth* bluetoothManager;
private slots:
void update();
void on_tableWidget_cellChanged(int row, int column);
void on_tableWidget_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
void on_save_clicked();
void on_load_clicked();
void on_reset_clicked();
void on_stop_clicked();
void on_start_clicked();
void on_groupTrain_clicked();
void on_fanSpeedMinus_clicked();
void on_fanSpeedPlus_clicked();
void on_difficulty_valueChanged(int value);
void trainProgramSignals();
void on_speedMinus_clicked();
void on_speedPlus_clicked();
void on_inclinationMinus_clicked();
void on_inclinationPlus_clicked();
void on_resistanceMinus_clicked();
void on_resistancePlus_clicked();
void on_chart_clicked();
};
#endif // MAINWINDOW_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
QT += bluetooth widgets xml positioning charts quick
unix:android: QT += androidextras
QT -= gui
QT += bluetooth xml
CONFIG += c++11 console debug app_bundle
CONFIG += c++11 console
CONFIG -= app_bundle
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
@@ -15,23 +16,9 @@ DEFINES += QT_DEPRECATED_WARNINGS
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
bike.cpp \
bluetooth.cpp \
bluetoothdevice.cpp \
charts.cpp \
domyostreadmill.cpp \
gpx.cpp \
homeform.cpp \
main.cpp \
sessionline.cpp \
toorxtreadmill.cpp \
treadmill.cpp \
mainwindow.cpp \
trainprogram.cpp \
trxappgateusbtreadmill.cpp \
virtualbike.cpp \
virtualtreadmill.cpp \
domyosbike.cpp
virtualtreadmill.cpp
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
@@ -39,41 +26,5 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
bike.h \
bluetooth.h \
bluetoothdevice.h \
charts.h \
domyostreadmill.h \
homeform.h \
sessionline.h \
toorxtreadmill.h \
gpx.h \
treadmill.h \
mainwindow.h \
trainprogram.h \
trxappgateusbtreadmill.h \
virtualbike.h \
virtualtreadmill.h \
domyosbike.h
FORMS += \
charts.ui \
mainwindow.ui
RESOURCES += \
icons.qrc \
qml.qrc
DISTFILES += \
android/AndroidManifest.xml \
android/build.gradle \
android/gradle/wrapper/gradle-wrapper.jar \
android/gradle/wrapper/gradle-wrapper.properties \
android/gradlew \
android/gradlew.bat \
android/res/values/libs.xml \
android/src/MyActivity.java
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
ANDROID_ABIS = armeabi-v7a
virtualtreadmill.h

View File

@@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.271
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "qdomyos-zwift", "qdomyos-zwift.vcxproj", "{9D0092A7-A461-39F4-9B1F-4C5838A323A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Debug|x64.ActiveCfg = Debug|x64
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Debug|x64.Build.0 = Debug|x64
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Release|x64.ActiveCfg = Release|x64
{9D0092A7-A461-39F4-9B1F-4C5838A323A6}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7EDA6C26-B7B8-4AFB-AD08-977E1DEF7E75}
EndGlobalSection
EndGlobal

View File

@@ -1,298 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{9D0092A7-A461-39F4-9B1F-4C5838A323A6}</ProjectGuid>
<RootNamespace>qdomyos-zwift</RootNamespace>
<Keyword>QtVS_v303</Keyword>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
<QtMsBuild Condition="'$(QtMsBuild)'=='' or !Exists('$(QtMsBuild)\qt.targets')">$(MSBuildProjectDirectory)\QtMsBuild</QtMsBuild>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<PlatformToolset>v141</PlatformToolset>
<OutputDirectory>debug\</OutputDirectory>
<ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
<CharacterSet>NotSet</CharacterSet>
<ConfigurationType>Application</ConfigurationType>
<IntermediateDirectory>debug\</IntermediateDirectory>
<PrimaryOutput>qdomyos-zwift</PrimaryOutput>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<PlatformToolset>v140</PlatformToolset>
<OutputDirectory>release\</OutputDirectory>
<ATLMinimizesCRunTimeLibraryUsage>false</ATLMinimizesCRunTimeLibraryUsage>
<CharacterSet>NotSet</CharacterSet>
<ConfigurationType>Application</ConfigurationType>
<IntermediateDirectory>release\</IntermediateDirectory>
<PrimaryOutput>qdomyos-zwift</PrimaryOutput>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Target Name="QtMsBuildNotFound" BeforeTargets="CustomBuild;ClCompile" Condition="!Exists('$(QtMsBuild)\qt.targets') or !Exists('$(QtMsBuild)\qt.props')">
<Message Importance="High" Text="QtMsBuild: could not locate qt.targets, qt.props; project may not build correctly." />
</Target>
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt_defaults.props')">
<Import Project="$(QtMsBuild)\qt_defaults.props" />
</ImportGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>release\</OutDir>
<IntDir>release\</IntDir>
<TargetName>qdomyos-zwift</TargetName>
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>debug\</OutDir>
<IntDir>debug\</IntDir>
<TargetName>qdomyos-zwift</TargetName>
<IgnoreImportLibrary>true</IgnoreImportLibrary>
</PropertyGroup>
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<QtInstall>msvc2015_64</QtInstall>
<QtModules>core;xml;gui;widgets;bluetooth;positioning;charts</QtModules>
</PropertyGroup>
<PropertyGroup Label="QtSettings" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<QtInstall>msvc2015_64</QtInstall>
<QtModules>core;xml;gui;widgets;bluetooth;positioning;charts</QtModules>
</PropertyGroup>
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.props')">
<Import Project="$(QtMsBuild)\qt.props" />
</ImportGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;debug;/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>debug\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ExceptionHandling>Sync</ExceptionHandling>
<ObjectFileName>debug\</ObjectFileName>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessToFile>false</PreprocessToFile>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<SuppressStartupBanner>true</SuppressStartupBanner>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
<GenerateDebugInformation>true</GenerateDebugInformation>
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<OutputFile>$(OutDir)\qdomyos-zwift.exe</OutputFile>
<RandomizedBaseAddress>true</RandomizedBaseAddress>
<SubSystem>Console</SubSystem>
<SuppressStartupBanner>true</SuppressStartupBanner>
</Link>
<Midl>
<DefaultCharType>Unsigned</DefaultCharType>
<EnableErrorChecks>None</EnableErrorChecks>
<WarningLevel>0</WarningLevel>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;QT_CHARTS_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_BLUETOOTH_LIB;QT_XML_LIB;QT_POSITIONING_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<QtMoc>
<CompilerFlavor>msvc</CompilerFlavor>
<Include>./$(Configuration)/moc_predefs.h</Include>
<ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
<DynamicSource>output</DynamicSource>
<QtMocDir>$(Configuration)</QtMocDir>
<QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
</QtMoc>
<QtRcc>
<InitFuncName>icons</InitFuncName>
<Compression>default</Compression>
<ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
<QtRccDir>$(Configuration)</QtRccDir>
<QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
</QtRcc>
<QtUic>
<ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
<QtUicDir>$(ProjectDir)</QtUicDir>
<QtUicFileName>ui_%(Filename).h</QtUicFileName>
</QtUic>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;release;/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>-Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions)</AdditionalOptions>
<AssemblerListingLocation>release\</AssemblerListingLocation>
<BrowseInformation>false</BrowseInformation>
<DebugInformationFormat>None</DebugInformationFormat>
<DisableSpecificWarnings>4577;4467;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<ExceptionHandling>Sync</ExceptionHandling>
<ObjectFileName>release\</ObjectFileName>
<Optimization>MaxSpeed</Optimization>
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessToFile>false</PreprocessToFile>
<ProgramDataBaseFileName>
</ProgramDataBaseFileName>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<SuppressStartupBanner>true</SuppressStartupBanner>
<TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalOptions>"/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions)</AdditionalOptions>
<DataExecutionPrevention>true</DataExecutionPrevention>
<GenerateDebugInformation>false</GenerateDebugInformation>
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>false</LinkIncremental>
<OptimizeReferences>true</OptimizeReferences>
<OutputFile>$(OutDir)\qdomyos-zwift.exe</OutputFile>
<RandomizedBaseAddress>true</RandomizedBaseAddress>
<SubSystem>Console</SubSystem>
<SuppressStartupBanner>true</SuppressStartupBanner>
</Link>
<Midl>
<DefaultCharType>Unsigned</DefaultCharType>
<EnableErrorChecks>None</EnableErrorChecks>
<WarningLevel>0</WarningLevel>
</Midl>
<ResourceCompile>
<PreprocessorDefinitions>_CONSOLE;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_DEPRECATED_WARNINGS;NDEBUG;QT_NO_DEBUG;QT_CHARTS_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_BLUETOOTH_LIB;QT_XML_LIB;QT_POSITIONING_LIB;QT_CORE_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
<QtMoc>
<CompilerFlavor>msvc</CompilerFlavor>
<Include>./$(Configuration)/moc_predefs.h</Include>
<ExecutionDescription>Moc'ing %(Identity)...</ExecutionDescription>
<DynamicSource>output</DynamicSource>
<QtMocDir>$(Configuration)</QtMocDir>
<QtMocFileName>moc_%(Filename).cpp</QtMocFileName>
</QtMoc>
<QtRcc>
<InitFuncName>icons</InitFuncName>
<Compression>default</Compression>
<ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
<QtRccDir>$(Configuration)</QtRccDir>
<QtRccFileName>qrc_%(Filename).cpp</QtRccFileName>
</QtRcc>
<QtUic>
<ExecutionDescription>Uic'ing %(Identity)...</ExecutionDescription>
<QtUicDir>$(ProjectDir)</QtUicDir>
<QtUicFileName>ui_%(Filename).h</QtUicFileName>
</QtUic>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="bike.cpp" />
<ClCompile Include="bluetooth.cpp" />
<ClCompile Include="bluetoothdevice.cpp" />
<ClCompile Include="charts.cpp" />
<ClCompile Include="domyosbike.cpp" />
<ClCompile Include="domyostreadmill.cpp" />
<ClCompile Include="gpx.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="mainwindow.cpp" />
<ClCompile Include="toorxtreadmill.cpp" />
<ClCompile Include="trainprogram.cpp" />
<ClCompile Include="treadmill.cpp" />
<ClCompile Include="trxappgateusbtreadmill.cpp" />
<ClCompile Include="virtualbike.cpp" />
<ClCompile Include="virtualtreadmill.cpp" />
</ItemGroup>
<ItemGroup>
<QtMoc Include="bike.h">
</QtMoc>
<QtMoc Include="bluetooth.h">
</QtMoc>
<QtMoc Include="bluetoothdevice.h">
</QtMoc>
<QtMoc Include="charts.h">
</QtMoc>
<QtMoc Include="domyosbike.h">
</QtMoc>
<QtMoc Include="domyostreadmill.h">
</QtMoc>
<QtMoc Include="gpx.h">
</QtMoc>
<QtMoc Include="mainwindow.h">
</QtMoc>
<QtMoc Include="toorxtreadmill.h">
</QtMoc>
<QtMoc Include="trainprogram.h">
</QtMoc>
<QtMoc Include="treadmill.h">
</QtMoc>
<QtMoc Include="trxappgateusbtreadmill.h">
</QtMoc>
<QtMoc Include="virtualbike.h">
</QtMoc>
<QtMoc Include="virtualtreadmill.h">
</QtMoc>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
<FileType>Document</FileType>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;debug\moc_predefs.h</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Generate moc_predefs.h</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">debug\moc_predefs.h;%(Outputs)</Outputs>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</CustomBuild>
<CustomBuild Include="release\moc_predefs.h.cbt">
<FileType>Document</FileType>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs)</AdditionalInputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2&gt;NUL &gt;release\moc_predefs.h</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Generate moc_predefs.h</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">release\moc_predefs.h;%(Outputs)</Outputs>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<QtUic Include="charts.ui">
</QtUic>
<QtUic Include="mainwindow.ui">
</QtUic>
</ItemGroup>
<ItemGroup>
<None Include="icons\bluetooth-icon.png" />
<None Include="icons\cadence.png" />
<None Include="icons\elevationgain.png" />
<None Include="icons\fan.png" />
<None Include="icons\heart_red.png" />
<QtRcc Include="icons.qrc">
</QtRcc>
<None Include="icons\inclination.png" />
<None Include="icons\kcal.png" />
<None Include="icons\odometer.png" />
<None Include="icons\resistance.png" />
<None Include="icons\speed.png" />
<None Include="icons\start.png" />
<None Include="icons\stop.png" />
<None Include="icons\watt.png" />
<None Include="icons\weight.png" />
<None Include="icons\zwift-on.png" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Condition="Exists('$(QtMsBuild)\qt.targets')">
<Import Project="$(QtMsBuild)\qt.targets" />
</ImportGroup>
<ImportGroup Label="ExtensionTargets" />
</Project>

View File

@@ -1,10 +0,0 @@
<RCC>
<qresource prefix="/">
<file>HomeForm.ui.qml</file>
<file>main.qml</file>
<file>Page1Form.ui.qml</file>
<file>Page2Form.ui.qml</file>
<file>qtquickcontrols2.conf</file>
<file>Home.qml</file>
</qresource>
</RCC>

View File

@@ -1,14 +0,0 @@
; This file can be edited to change the style of the application
; Read "Qt Quick Controls 2 Configuration File" for details:
; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html
[Controls]
Style=Material
[Universal]
Theme=System
Accent=Red
[Material]
Theme=Dark
Primary=BlueGrey

View File

@@ -1,15 +0,0 @@
#include "sessionline.h"
SessionLine::SessionLine(double speed, int8_t inclination, double distance, uint8_t watt, int8_t resistance, uint8_t heart, double pace, QTime time)
{
this->speed = speed;
this->inclination = inclination;
this->distance = distance;
this->watt = watt;
this->resistance = resistance;
this->heart = heart;
this->pace = pace;
this->time = time;
}
SessionLine::SessionLine() {}

View File

@@ -1,22 +0,0 @@
#ifndef SESSIONLINE_H
#define SESSIONLINE_H
#include <QTimer>
#include <QTime>
class SessionLine
{
public:
double speed;
int8_t inclination;
double distance;
uint8_t watt;
int8_t resistance;
uint8_t heart;
double pace;
QTime time;
SessionLine();
SessionLine(double speed, int8_t inclination, double distance, uint8_t watt, int8_t resistance, uint8_t heart, double pace, QTime time = QTime::currentTime());
};
#endif // SESSIONLINE_H

View File

@@ -1,73 +0,0 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

View File

@@ -1,8 +0,0 @@
#include <QCoreApplication>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
return a.exec();
}

View File

@@ -1,23 +0,0 @@
QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

View File

@@ -1,137 +0,0 @@
#include "toorxtreadmill.h"
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
toorxtreadmill::toorxtreadmill()
{
refresh = new QTimer(this);
initDone = false;
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
refresh->start(200);
}
void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("TRX ROUTE KEY"))
{
bttreadmill = device;
// Create a discovery agent and connect to its signals
discoveryAgent = new QBluetoothServiceDiscoveryAgent(this);
connect(discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)),
this, SLOT(serviceDiscovered(QBluetoothServiceInfo)));
// Start a discovery
discoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);
return;
}
}
// In your local slot, read information about the found devices
void toorxtreadmill::serviceDiscovered(const QBluetoothServiceInfo &service)
{
if(service.device().address() == bttreadmill.address())
{
debug("Found new service: " + service.serviceName()
+ '(' + service.serviceUuid().toString() + ')');
if(service.serviceName().contains("SerialPort"))
{
debug("Serial port service found");
discoveryAgent->stop();
serialPortService = service;
socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
connect(socket, &QBluetoothSocket::readyRead, this, &toorxtreadmill::readSocket);
connect(socket, &QBluetoothSocket::connected, this, QOverload<>::of(&toorxtreadmill::rfCommConnected));
connect(socket, &QBluetoothSocket::disconnected, this, &toorxtreadmill::disconnected);
connect(socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error),
this, &toorxtreadmill::onSocketErrorOccurred);
debug("Create socket");
socket->connectToService(serialPortService);
debug("ConnectToService done");
}
}
}
void toorxtreadmill::update()
{
if(initDone)
{
const char poll[] = {0x55, 0x17, 0x01, 0x01, 0x53};
socket->write(poll, sizeof(poll));
debug("write poll");
}
}
void toorxtreadmill::rfCommConnected()
{
debug("connected " + socket->peerName());
initDone = true;
}
void toorxtreadmill::readSocket()
{
if (!socket)
return;
while (socket->canReadLine()) {
QByteArray line = socket->readLine();
debug(socket->peerName() +
QString::fromUtf8(line.constData(), line.length()));
if(line.length() == 17)
{
elapsed = GetElapsedTimeFromPacket(line);
Distance = GetDistanceFromPacket(line);
KCal = GetCaloriesFromPacket(line);
Speed = GetSpeedFromPacket(line);
Inclination = GetInclinationFromPacket(line);
Heart = GetHeartRateFromPacket(line);
}
}
}
uint8_t toorxtreadmill::GetHeartRateFromPacket(QByteArray packet)
{
return packet.at(16);
}
uint8_t toorxtreadmill::GetInclinationFromPacket(QByteArray packet)
{
return packet.at(15);
}
double toorxtreadmill::GetSpeedFromPacket(QByteArray packet)
{
double convertedData = (double)(packet.at(13) << 8) + ((double)packet.at(14) / 100.0);
return convertedData;
}
uint16_t toorxtreadmill::GetCaloriesFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(11) << 8) | packet.at(12);
return convertedData;
}
uint16_t toorxtreadmill::GetDistanceFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(9) << 8) | packet.at(10);
return convertedData;
}
uint16_t toorxtreadmill::GetElapsedTimeFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
return convertedData;
}
void toorxtreadmill::onSocketErrorOccurred(QBluetoothSocket::SocketError error)
{
debug("onSocketErrorOccurred " + QString::number(error));
}

View File

@@ -1,70 +0,0 @@
#ifndef TOORX_H
#define TOORX_H
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QBluetoothServiceDiscoveryAgent>
#include <QBluetoothSocket>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qmutex.h>
#include <QObject>
#include "virtualtreadmill.h"
#include "treadmill.h"
class toorxtreadmill : public treadmill
{
Q_OBJECT
public:
explicit toorxtreadmill();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void serviceDiscovered(const QBluetoothServiceInfo &service);
void readSocket();
void rfCommConnected();
void onSocketErrorOccurred(QBluetoothSocket::SocketError);
void update();
private:
QBluetoothDeviceInfo bttreadmill;
QBluetoothServiceDiscoveryAgent *discoveryAgent;
QBluetoothServiceInfo serialPortService;
QBluetoothSocket *socket = nullptr;
QTimer* refresh;
bool initDone = false;
uint16_t GetElapsedTimeFromPacket(QByteArray packet);
uint16_t GetDistanceFromPacket(QByteArray packet);
uint16_t GetCaloriesFromPacket(QByteArray packet);
double GetSpeedFromPacket(QByteArray packet);
uint8_t GetInclinationFromPacket(QByteArray packet);
uint8_t GetHeartRateFromPacket(QByteArray packet);
signals:
void disconnected();
void debug(QString string);
};
#endif // TOORX_H

View File

@@ -1,177 +0,0 @@
#include "trainprogram.h"
#include <QFile>
#include <QtXml/QtXml>
trainprogram::trainprogram(QList<trainrow> rows, bluetooth* b)
{
this->bluetoothManager = b;
this->rows = rows;
this->loadedRows = rows;
connect(&timer, SIGNAL(timeout()), this, SLOT(scheduler()));
timer.setInterval(1000);
timer.start();
}
void trainprogram::scheduler()
{
if(
rows.count() == 0 ||
started == false ||
enabled == false ||
bluetoothManager->device() == nullptr ||
bluetoothManager->device()->currentSpeed() <= 0
)
{
return;
}
ticks++;
elapsed = ticks;
ticksCurrentRow++;
elapsedCurrentRow = ticksCurrentRow;
// entry point
if(ticks == 1 && currentStep == 0)
{
if(rows[0].forcespeed && rows[0].speed)
{
qDebug() << "trainprogram change speed" + QString::number(rows[0].speed);
emit changeSpeedAndInclination(rows[0].speed, rows[0].inclination);
}
else
{
qDebug() << "trainprogram change inclination" + QString::number(rows[0].inclination);
emit changeInclination(rows[0].inclination);
}
}
uint32_t currentRowLen = rows[currentStep].duration.second() +
(rows[currentStep].duration.minute() * 60) +
(rows[currentStep].duration.hour() * 3600);
uint32_t nextRowLen = 0;
if(rows.count() > currentStep + 1)
nextRowLen = rows[currentStep + 1].duration.second() +
(rows[currentStep + 1].duration.minute() * 60) +
(rows[currentStep + 1].duration.hour() * 3600);
qDebug() << "trainprogram elapsed current row" + QString::number(elapsedCurrentRow) + "current row len" + QString::number(currentRowLen);
if(elapsedCurrentRow >= currentRowLen && currentRowLen)
{
if(nextRowLen)
{
currentStep++;
ticksCurrentRow = 0;
elapsedCurrentRow = 0;
if(rows[currentStep].forcespeed && rows[currentStep].speed)
{
qDebug() << "trainprogram change speed" + QString::number(rows[currentStep].speed);
emit changeSpeedAndInclination(rows[currentStep].speed, rows[currentStep].inclination);
}
qDebug() << "trainprogram change inclination" + QString::number(rows[currentStep].inclination);
emit changeInclination(rows[currentStep].inclination);
}
else
{
qDebug() << "trainprogram ends!";
started = false;
emit stop();
}
}
}
void trainprogram::onTapeStarted()
{
started = true;
}
void trainprogram::restart()
{
ticks = 0;
ticksCurrentRow = 0;
elapsed = 0;
elapsedCurrentRow = 0;
currentStep = 0;
started = true;
}
void trainprogram::save(QString filename)
{
QFile output(filename);
output.open(QIODevice::WriteOnly);
QXmlStreamWriter stream(&output);
stream.setAutoFormatting(true);
stream.writeStartDocument();
stream.writeStartElement("rows");
foreach (trainrow row, rows) {
stream.writeStartElement("row");
stream.writeAttribute("duration", row.duration.toString());
stream.writeAttribute("speed", QString::number(row.speed));
stream.writeAttribute("inclination", QString::number(row.inclination));
stream.writeAttribute("forcespeed", row.forcespeed?"1":"0");
stream.writeEndElement();
}
stream.writeEndElement();
stream.writeEndDocument();
}
trainprogram* trainprogram::load(QString filename, bluetooth* b)
{
QList<trainrow> list;
QFile input(filename);
input.open(QIODevice::ReadOnly);
QXmlStreamReader stream(&input);
while(!stream.atEnd())
{
stream.readNext();
trainrow row;
QXmlStreamAttributes atts = stream.attributes();
if(atts.length())
{
row.duration = QTime::fromString(atts.value("duration").toString(), "hh:mm:ss");
row.speed = atts.value("speed").toDouble();
row.inclination = atts.value("inclination").toDouble();
row.forcespeed = atts.value("forcespeed").toInt()?true:false ;
list.append(row);
}
}
trainprogram *tr = new trainprogram(list, b);
return tr;
}
QTime trainprogram::totalElapsedTime()
{
return QTime(0,0,elapsed);
}
QTime trainprogram::currentRowElapsedTime()
{
return QTime(0,0,elapsedCurrentRow);
}
QTime trainprogram::duration()
{
QTime total(0,0,0,0);
foreach (trainrow row, rows) {
total = total.addSecs((row.duration.hour() * 3600) + (row.duration.minute() * 60) + row.duration.second());
}
return total;
}
double trainprogram::totalDistance()
{
double distance = 0;
foreach (trainrow row, rows) {
if(row.duration.hour() || row.duration.minute() || row.duration.second())
{
if(!row.forcespeed)
{
return -1;
}
distance += ((row.duration.hour() * 3600) + (row.duration.minute() * 60) + row.duration.second()) * (row.speed / 3600);
}
}
return distance;
}

View File

@@ -1,59 +0,0 @@
#ifndef TRAINPROGRAM_H
#define TRAINPROGRAM_H
#include <QTime>
#include <QTimer>
#include <QObject>
#include "bluetooth.h"
class trainrow
{
public:
QTime duration;
double speed;
double inclination;
bool forcespeed;
};
class trainprogram: public QObject
{
Q_OBJECT
public:
trainprogram(QList<trainrow>, bluetooth* b);
void save(QString filename);
static trainprogram* load(QString filename, bluetooth* b);
QTime totalElapsedTime();
QTime currentRowElapsedTime();
QTime duration();
double totalDistance();
QList<trainrow> rows;
QList<trainrow> loadedRows; // rows as loaded
uint32_t elapsed = 0;
bool enabled = true;
void restart();
void scheduler(int tick);
public slots:
void onTapeStarted();
void scheduler();
signals:
void start();
void stop();
void changeSpeed(double speed);
void changeInclination(double inclination);
void changeSpeedAndInclination(double speed, double inclination);
private:
bluetooth* bluetoothManager;
bool started = false;
uint32_t ticks = 0;
uint16_t currentStep = 0;
uint32_t ticksCurrentRow = 0;
uint32_t elapsedCurrentRow = 0;
QTimer timer;
};
#endif // TRAINPROGRAM_H

View File

@@ -1,32 +0,0 @@
#include "treadmill.h"
treadmill::treadmill()
{
}
void treadmill::changeSpeed(double speed){ requestSpeed = speed;}
void treadmill::changeInclination(double inclination){ requestInclination = inclination; }
void treadmill::changeSpeedAndInclination(double speed, double inclination){ requestSpeed = speed; requestInclination = inclination;}
double treadmill::currentInclination(){ return Inclination; }
double treadmill::elevationGain(){ return elevationAcc; }
uint8_t treadmill::fanSpeed() { return FanSpeed; };
bool treadmill::connected() { return false; }
bluetoothdevice::BLUETOOTH_TYPE treadmill::deviceType() { return bluetoothdevice::TREADMILL; }
uint16_t treadmill::watts(double weight)
{
// calc Watts ref. https://alancouzens.com/blog/Run_Power.html
uint16_t watts=0;
if(currentSpeed() > 0)
{
double pace=60/currentSpeed();
double VO2R=210.0/pace;
double VO2A=(VO2R*weight)/1000.0;
double hwatts=75*VO2A;
double vwatts=((9.8*weight) * (currentInclination()/100));
watts=hwatts+vwatts;
}
return watts;
}

View File

@@ -1,34 +0,0 @@
#ifndef TREADMILL_H
#define TREADMILL_H
#include <QObject>
#include "bluetoothdevice.h"
class treadmill:public bluetoothdevice
{
Q_OBJECT
public:
treadmill();
virtual double currentInclination();
virtual double elevationGain();
virtual uint8_t fanSpeed();
virtual bool connected();
uint16_t watts(double weight=75.0);
bluetoothdevice::BLUETOOTH_TYPE deviceType();
public slots:
virtual void changeSpeed(double speed);
virtual void changeInclination(double inclination);
virtual void changeSpeedAndInclination(double speed, double inclination);
signals:
void tapeStarted();
protected:
double elevationAcc = 0;
double Inclination = 0;
double requestSpeed = -1;
double requestInclination = -1;
};
#endif // TREADMILL_H

View File

@@ -1,398 +0,0 @@
#include "trxappgateusbtreadmill.h"
#include "virtualtreadmill.h"
#include <QFile>
#include <QDateTime>
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
trxappgateusbtreadmill::trxappgateusbtreadmill()
{
refresh = new QTimer(this);
initDone = false;
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
refresh->start(200);
}
void trxappgateusbtreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log)
{
QEventLoop loop;
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
&loop, SLOT(quit()));
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
if(!disable_log)
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
loop.exec();
}
void trxappgateusbtreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline)
{
Q_UNUSED(requestSpeed);
Q_UNUSED(requestIncline);
}
bool trxappgateusbtreadmill::changeFanSpeed(uint8_t speed)
{
Q_UNUSED(speed);
return false;
}
void trxappgateusbtreadmill::update()
{
static uint8_t sec1 = 0;
static QTime lastTime;
static bool first = true;
//qDebug() << treadmill.isValid() << m_control->state() << gattCommunicationChannelService << gattWriteCharacteristic.isValid() << gattNotifyCharacteristic.isValid() << initDone;
if(m_control->state() == QLowEnergyController::UnconnectedState)
{
emit disconnected();
return;
}
if(initRequest)
{
initRequest = false;
btinit(false);
}
else if(bttreadmill.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
gattNotifyCharacteristic.isValid() &&
initDone)
{
if(currentSpeed() > 0.0 && !first)
elapsed += ((double)lastTime.msecsTo(QTime::currentTime()) / 1000.0);
// updating the treadmill console every second
if(sec1++ == (1000 / refresh->interval()))
{
sec1 = 0;
//updateDisplay(elapsed);
}
const uint8_t noOpData[] = { 0xf0, 0xa2, 0x01, 0xd3, 0x66 };
writeCharacteristic((uint8_t*)noOpData, sizeof(noOpData), "noOp", true);
if(requestSpeed != -1)
{
if(requestSpeed != currentSpeed())
{
debug("writing speed " + QString::number(requestSpeed));
double inc = Inclination;
if(requestInclination != -1)
{
inc = requestInclination;
requestInclination = -1;
}
forceSpeedOrIncline(requestSpeed, inc);
}
requestSpeed = -1;
}
if(requestInclination != -1)
{
if(requestInclination != currentInclination())
{
debug("writing incline " + QString::number(requestInclination));
double speed = currentSpeed();
if(requestSpeed != -1)
{
speed = requestSpeed;
requestSpeed = -1;
}
forceSpeedOrIncline(speed, requestInclination);
}
requestInclination = -1;
}
if(requestStart != -1)
{
debug("starting...");
btinit(true);
requestStart = -1;
emit tapeStarted();
}
if(requestStop != -1)
{
debug("stopping...");
//writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
if(requestIncreaseFan != -1)
{
debug("increasing fan speed...");
changeFanSpeed(FanSpeed + 1);
requestIncreaseFan = -1;
}
else if(requestDecreaseFan != -1)
{
debug("decreasing fan speed...");
changeFanSpeed(FanSpeed - 1);
requestDecreaseFan = -1;
}
elevationAcc += (currentSpeed() / 3600.0) * 1000 * (currentInclination() / 100) * (refresh->interval() / 1000);
}
lastTime = QTime::currentTime();
first = false;
}
void trxappgateusbtreadmill::serviceDiscovered(const QBluetoothUuid &gatt)
{
debug("serviceDiscovered " + gatt.toString());
}
static QByteArray lastPacket;
void trxappgateusbtreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
static QTime lastTime;
static bool first = true;
debug(" << " + newValue.toHex(' '));
if (lastPacket.length() && lastPacket == newValue)
return;
lastPacket = newValue;
if (newValue.length() != 19)
return;
double speed = GetSpeedFromPacket(newValue);
double incline = GetInclinationFromPacket(newValue);
double kcal = GetKcalFromPacket(newValue);
double distance = GetDistanceFromPacket(newValue);
Heart = 0;
FanSpeed = 0;
if(!first)
DistanceCalculated += ((speed / 3600.0) / ( 1000.0 / (lastTime.msecsTo(QTime::currentTime()))));
debug("Current speed: " + QString::number(speed));
debug("Current incline: " + QString::number(incline));
debug("Current heart: " + QString::number(Heart));
debug("Current KCal: " + QString::number(kcal));
debug("Current Distance: " + QString::number(distance));
debug("Current Elapsed from the treadmill (not used): " + QString::number(GetElapsedFromPacket(newValue)));
debug("Current Distance Calculated: " + QString::number(DistanceCalculated));
if(m_control->error() != QLowEnergyController::NoError)
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
Speed = speed;
Inclination = incline;
KCal = kcal;
Distance = distance;
lastTime = QTime::currentTime();
first = false;
}
uint16_t trxappgateusbtreadmill::GetElapsedFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(4) - 1);
convertedData += ((packet.at(5) - 1) * 60);
return convertedData;
}
double trxappgateusbtreadmill::GetSpeedFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(13) - 1) + ((packet.at(12) - 1) * 100);
double data = (double)(convertedData) / 10.0f;
return data;
}
double trxappgateusbtreadmill::GetKcalFromPacket(QByteArray packet)
{
uint16_t convertedData = ((packet.at(8) - 1) << 8) | (packet.at(9) - 1);
return (double)(convertedData);
}
double trxappgateusbtreadmill::GetDistanceFromPacket(QByteArray packet)
{
uint16_t convertedData = ((packet.at(6) - 1) << 8) | (packet.at(7) - 1);
double data = ((double)(convertedData)) / 100.0f;
return data;
}
double trxappgateusbtreadmill::GetInclinationFromPacket(QByteArray packet)
{
uint16_t convertedData = packet.at(14);
double data = (convertedData - 1);
if (data < 0) return 0;
return data;
}
void trxappgateusbtreadmill::btinit(bool startTape)
{
Q_UNUSED(startTape);
const uint8_t initData1[] = { 0xf0, 0xa0, 0x01, 0x01, 0x92 };
const uint8_t initData2[] = { 0xf0, 0xa5, 0x01, 0xd3, 0x04, 0x6d };
const uint8_t initData3[] = { 0xf0, 0xa0, 0x01, 0xd3, 0x64 };
const uint8_t initData4[] = { 0xf0, 0xa1, 0x01, 0xd3, 0x65 };
const uint8_t initData5[] = { 0xf0, 0xa3, 0x01, 0xd3, 0x01, 0x15, 0x01, 0x02, 0x51, 0x01, 0x51, 0x23 };
const uint8_t initData6[] = { 0xf0, 0xa4, 0x01, 0xd3, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x73 };
const uint8_t initData7[] = { 0xf0, 0xaf, 0x01, 0xd3, 0x02, 0x75 };
writeCharacteristic((uint8_t*)initData1, sizeof(initData1), "init");
writeCharacteristic((uint8_t*)initData2, sizeof(initData2), "init");
writeCharacteristic((uint8_t*)initData3, sizeof(initData3), "init");
writeCharacteristic((uint8_t*)initData4, sizeof(initData4), "init");
writeCharacteristic((uint8_t*)initData3, sizeof(initData3), "init");
writeCharacteristic((uint8_t*)initData5, sizeof(initData5), "init");
writeCharacteristic((uint8_t*)initData6, sizeof(initData6), "init");
writeCharacteristic((uint8_t*)initData7, sizeof(initData7), "init");
initDone = true;
}
void trxappgateusbtreadmill::stateChanged(QLowEnergyService::ServiceState state)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if(state == QLowEnergyService::ServiceDiscovered)
{
foreach(QLowEnergyCharacteristic c,gattCommunicationChannelService->characteristics())
{
debug("characteristic " + c.uuid().toString());
}
QBluetoothUuid _gattWriteCharacteristicId((QString)"0000fff2-0000-1000-8000-00805f9b34fb");
QBluetoothUuid _gattNotifyCharacteristicId((QString)"0000fff1-0000-1000-8000-00805f9b34fb");
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotifyCharacteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
this, SLOT(errorService(QLowEnergyService::ServiceError)));
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
// ******************************************* virtual treadmill init *************************************
static uint8_t first = 0;
if(!first)
{
debug("creating virtual treadmill interface...");
virtualTreadMill = new virtualtreadmill(this, false);
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&trxappgateusbtreadmill::debug);
}
first = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void trxappgateusbtreadmill::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
{
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
}
void trxappgateusbtreadmill::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
Q_UNUSED(characteristic);
debug("characteristicWritten " + newValue.toHex(' '));
}
void trxappgateusbtreadmill::serviceScanDone(void)
{
debug("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((QString)"0000fff0-0000-1000-8000-00805f9b34fb");
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
gattCommunicationChannelService->discoverDetails();
}
void trxappgateusbtreadmill::errorService(QLowEnergyService::ServiceError err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
debug("trxappgateusbtreadmill::errorService" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
}
void trxappgateusbtreadmill::error(QLowEnergyController::Error err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
debug("trxappgateusbtreadmill::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
m_control->disconnect();
}
void trxappgateusbtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("TOORX"))
{
bttreadmill = device;
m_control = QLowEnergyController::createCentral(bttreadmill, this);
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
connect(m_control, SIGNAL(discoveryFinished()),
this, SLOT(serviceScanDone()));
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
this, SLOT(error(QLowEnergyController::Error)));
connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
debug("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
debug("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
debug("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool trxappgateusbtreadmill::connected()
{
if(!m_control)
return false;
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void* trxappgateusbtreadmill::VirtualTreadMill()
{
return virtualTreadMill;
}
void* trxappgateusbtreadmill::VirtualDevice()
{
return VirtualTreadMill();
}
double trxappgateusbtreadmill::odometer()
{
return DistanceCalculated;
}

View File

@@ -1,89 +0,0 @@
#ifndef TRXAPPGATEUSBTREADMILL_H
#define TRXAPPGATEUSBTREADMILL_H
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qmutex.h>
#include <QObject>
#include "virtualtreadmill.h"
#include "treadmill.h"
class trxappgateusbtreadmill : public treadmill
{
Q_OBJECT
public:
trxappgateusbtreadmill();
bool connected();
bool changeFanSpeed(uint8_t speed);
double odometer();
void* VirtualTreadMill();
void* VirtualDevice();
private:
double GetSpeedFromPacket(QByteArray packet);
double GetInclinationFromPacket(QByteArray packet);
double GetKcalFromPacket(QByteArray packet);
double GetDistanceFromPacket(QByteArray packet);
uint16_t GetElapsedFromPacket(QByteArray packet);
void forceSpeedOrIncline(double requestSpeed, double requestIncline);
void updateDisplay(uint16_t elapsed);
void btinit(bool startTape);
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log=false);
void startDiscover();
double DistanceCalculated = 0;
QTimer* refresh;
virtualtreadmill* virtualTreadMill = 0;
QBluetoothDeviceInfo bttreadmill;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotifyCharacteristic;
bool initDone = false;
bool initRequest = false;
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // TRXAPPGATEUSBTREADMILL_H

View File

@@ -1,311 +0,0 @@
#include "virtualbike.h"
#include <QtMath>
#include <QMetaEnum>
#include <QDataStream>
enum FtmsControlPointCommand {
FTMS_REQUEST_CONTROL = 0x00,
FTMS_RESET,
FTMS_SET_TARGET_SPEED,
FTMS_SET_TARGET_INCLINATION,
FTMS_SET_TARGET_RESISTANCE_LEVEL,
FTMS_SET_TARGET_POWER,
FTMS_SET_TARGET_HEARTRATE,
FTMS_START_RESUME,
FTMS_STOP_PAUSE,
FTMS_SET_TARGETED_EXP_ENERGY,
FTMS_SET_TARGETED_STEPS,
FTMS_SET_TARGETED_STRIDES,
FTMS_SET_TARGETED_DISTANCE,
FTMS_SET_TARGETED_TIME,
FTMS_SET_TARGETED_TIME_TWO_HR_ZONES,
FTMS_SET_TARGETED_TIME_THREE_HR_ZONES,
FTMS_SET_TARGETED_TIME_FIVE_HR_ZONES,
FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS,
FTMS_SET_WHEEL_CIRCUMFERENCE,
FTMS_SPIN_DOWN_CONTROL,
FTMS_SET_TARGETED_CADENCE,
FTMS_RESPONSE_CODE = 0x80
};
enum FtmsResultCode {
FTMS_SUCCESS = 0x01,
FTMS_NOT_SUPPORTED,
FTMS_INVALID_PARAMETER,
FTMS_OPERATION_FAILED,
FTMS_CONTROL_NOT_PERMITTED
};
virtualbike::virtualbike(bike* t, bool noWriteResistance, bool noHeartService)
{
Bike = t;
this->noHeartService = noHeartService;
Q_UNUSED(noWriteResistance)
//! [Advertising Data]
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("DomyosBridge");
QList<QBluetoothUuid> services;
services << ((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
if(!this->noHeartService)
services << QBluetoothUuid::HeartRate;
advertisingData.setServices(services);
//! [Advertising Data]
serviceDataFIT.setType(QLowEnergyServiceData::ServiceTypePrimary);
QLowEnergyCharacteristicData charDataFIT;
charDataFIT.setUuid((QBluetoothUuid::CharacteristicType)0x2ACC); //FitnessMachineFeatureCharacteristicUuid
QByteArray valueFIT;
valueFIT.append((char)0x80); // resistance level supported
valueFIT.append((char)0x14); // heart rate and elapsed time
valueFIT.append((char)0x00);
valueFIT.append((char)0x00);
valueFIT.append((char)0x00);
valueFIT.append((char)0x00);
valueFIT.append((char)0x00);
valueFIT.append((char)0x00);
charDataFIT.setValue(valueFIT);
charDataFIT.setProperties(QLowEnergyCharacteristic::Read);
QLowEnergyCharacteristicData charDataFIT2;
charDataFIT2.setUuid((QBluetoothUuid::CharacteristicType)0x2AD6); //supported_resistance_level_rangeCharacteristicUuid
charDataFIT2.setProperties(QLowEnergyCharacteristic::Read);
QByteArray valueFIT2;
valueFIT2.append((char)0x0A); // min resistance value
valueFIT2.append((char)0x00); // min resistance value
valueFIT2.append((char)0x96); // max resistance value
valueFIT2.append((char)0x00); // max resistance value
valueFIT2.append((char)0x0A); // step resistance
valueFIT2.append((char)0x00); // step resistance
charDataFIT2.setValue(valueFIT2);
QLowEnergyCharacteristicData charDataFIT3;
charDataFIT3.setUuid((QBluetoothUuid::CharacteristicType)0x2AD9); //Fitness Machine Control Point
charDataFIT3.setProperties(QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::Indicate);
const QLowEnergyDescriptorData cpClientConfig(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charDataFIT3.addDescriptor(cpClientConfig);
QLowEnergyCharacteristicData charDataFIT4;
charDataFIT4.setUuid((QBluetoothUuid::CharacteristicType)0x2AD2); //indoor bike
charDataFIT4.setProperties(QLowEnergyCharacteristic::Notify | QLowEnergyCharacteristic::Read);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
const QLowEnergyDescriptorData clientConfig4(QBluetoothUuid::ClientCharacteristicConfiguration,
descriptor);
charDataFIT4.addDescriptor(clientConfig4);
serviceDataFIT.setUuid((QBluetoothUuid::ServiceClassUuid)0x1826); //FitnessMachineServiceUuid
serviceDataFIT.addCharacteristic(charDataFIT);
serviceDataFIT.addCharacteristic(charDataFIT2);
serviceDataFIT.addCharacteristic(charDataFIT3);
serviceDataFIT.addCharacteristic(charDataFIT4);
if(!this->noHeartService)
{
QLowEnergyCharacteristicData charDataHR;
charDataHR.setUuid(QBluetoothUuid::HeartRateMeasurement);
charDataHR.setValue(QByteArray(2, 0));
charDataHR.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfigHR(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charDataHR.addDescriptor(clientConfigHR);
serviceDataHR.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceDataHR.setUuid(QBluetoothUuid::HeartRate);
serviceDataHR.addCharacteristic(charDataHR);
}
//! [Start Advertising]
leController = QLowEnergyController::createPeripheral();
Q_ASSERT(leController);
if(!this->noHeartService)
serviceHR = leController->addService(serviceDataHR);
serviceFIT = leController->addService(serviceDataFIT);
QObject::connect(serviceFIT, SIGNAL(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)), this, SLOT(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)));
QLowEnergyAdvertisingParameters pars;
pars.setInterval(100, 100);
leController->startAdvertising(pars, advertisingData,
advertisingData);
//! [Start Advertising]
//! [Provide Heartbeat]
QObject::connect(&bikeTimer, SIGNAL(timeout()), this, SLOT(bikeProvider()));
bikeTimer.start(1000);
//! [Provide Heartbeat]
QObject::connect(leController, SIGNAL(disconnected()), this, SLOT(reconnect()));
QObject::connect(leController, SIGNAL(error(QLowEnergyController::Error)), this, SLOT(error(QLowEnergyController::Error)));
}
void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
QByteArray reply;
emit debug("characteristicChanged " + QString::number(characteristic.uuid().toUInt16()) + " " + newValue.toHex(' '));
switch(characteristic.uuid().toUInt16())
{
case 0x2AD9: // Fitness Machine Control Point
if((char)newValue.at(0) == FTMS_SET_TARGET_RESISTANCE_LEVEL)
{
// Set Target Resistance
uint8_t uresistance = newValue.at(1);
uresistance = uresistance / 10;
Bike->changeResistance(uresistance);
emit debug("new requested resistance " + QString::number(uresistance));
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)FTMS_SET_TARGET_RESISTANCE_LEVEL);
reply.append((quint8)FTMS_SUCCESS);
}
else if((char)newValue.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS) // simulation parameter
{
emit debug("indoor bike simulation parameters");
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS);
reply.append((quint8)FTMS_SUCCESS);
int16_t iresistance = (newValue.at(3) + (newValue.at(4) << 8));
double resistance = ((double)iresistance) / 100.0;
Bike->changeResistance((uint8_t)resistance + 1); // resistance start from 1
}
else if((char)newValue.at(0) == FTMS_START_RESUME)
{
emit debug("start simulation!");
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)FTMS_START_RESUME);
reply.append((quint8)FTMS_SUCCESS);
}
else if((char)newValue.at(0) == FTMS_REQUEST_CONTROL)
{
emit debug("control requested");
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)FTMS_REQUEST_CONTROL);
reply.append((quint8)FTMS_SUCCESS);
}
else
{
emit debug("not supported");
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)newValue.at(0));
reply.append((quint8)FTMS_NOT_SUPPORTED);
}
QLowEnergyCharacteristic characteristic
= serviceFIT->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9);
Q_ASSERT(characteristic.isValid());
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtual bike not connected");
return;
}
writeCharacteristic(serviceFIT, characteristic, reply);
break;
}
}
void virtualbike::writeCharacteristic(QLowEnergyService* service, QLowEnergyCharacteristic characteristic, QByteArray value)
{
try {
emit debug("virtualbike::writeCharacteristic " + service->serviceName() + " " + characteristic.name() + " " + value.toHex(' '));
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
} catch (...) {
emit debug("virtual bike error!");
}
}
void virtualbike::reconnect()
{
emit debug("virtualbike::reconnect");
leController->disconnectFromDevice();
if(!this->noHeartService)
serviceHR = leController->addService(serviceDataHR);
serviceFIT = leController->addService(serviceDataFIT);
if (serviceFIT)
leController->startAdvertising(QLowEnergyAdvertisingParameters(),
advertisingData, advertisingData);
}
void virtualbike::bikeProvider()
{
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtual bike not connected");
return;
}
else
{
emit debug("virtual bike connected");
}
QByteArray value;
value.append((char)0x64); // speed, inst. cadence, resistance lvl, instant power
value.append((char)0x02); // heart rate
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed() * 100);
value.append((char)(normalizeSpeed & 0xFF)); // speed
value.append((char)(normalizeSpeed >> 8) & 0xFF); // speed
value.append((char)(Bike->currentCadence() * 2)); // cadence
value.append((char)(0)); // cadence
value.append((char)Bike->currentResistance()); // resistance
value.append((char)(0)); // resistance
value.append((char)(Bike->watts() & 0xFF)); // watts
value.append((char)(Bike->watts() >> 8) & 0xFF); // watts
value.append(char(Bike->currentHeart())); // Actual value.
QLowEnergyCharacteristic characteristic
= serviceFIT->characteristic((QBluetoothUuid::CharacteristicType)0x2AD2);
Q_ASSERT(characteristic.isValid());
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtual bike not connected");
return;
}
writeCharacteristic(serviceFIT, characteristic, value);
//characteristic
// = service->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9); // Fitness Machine Control Point
//Q_ASSERT(characteristic.isValid());
//service->readCharacteristic(characteristic);
if(!this->noHeartService)
{
QByteArray valueHR;
valueHR.append(char(0)); // Flags that specify the format of the value.
valueHR.append(char(Bike->currentHeart())); // Actual value.
QLowEnergyCharacteristic characteristicHR
= serviceHR->characteristic(QBluetoothUuid::HeartRateMeasurement);
Q_ASSERT(characteristicHR.isValid());
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtual bike not connected");
return;
}
writeCharacteristic(serviceHR, characteristicHR, valueHR);
}
}
bool virtualbike::connected()
{
if(!leController)
return false;
return leController->state() == QLowEnergyController::ConnectedState;
}
void virtualbike::error(QLowEnergyController::Error newError)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
debug("virtualbike::controller:ERROR " + QString::fromLocal8Bit(metaEnum.valueToKey(newError)));
if(newError != QLowEnergyController::RemoteHostClosedError)
reconnect();
}

View File

@@ -1,58 +0,0 @@
#ifndef VIRTUALBIKE_H
#define VIRTUALBIKE_H
#include <QObject>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include "bike.h"
class virtualbike: public QObject
{
Q_OBJECT
public:
virtualbike(bike* t, bool noWriteResistance = false, bool noHeartService = false);
bool connected();
private:
QLowEnergyController* leController;
QLowEnergyService* serviceHR;
QLowEnergyService* serviceFIT;
QLowEnergyAdvertisingData advertisingData;
QLowEnergyServiceData serviceDataHR;
QLowEnergyServiceData serviceDataFIT;
QTimer bikeTimer;
bike* Bike;
bool noHeartService = false;
void writeCharacteristic(QLowEnergyService* service, QLowEnergyCharacteristic characteristic, QByteArray value);
signals:
void debug(QString string);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void bikeProvider();
void reconnect();
void error(QLowEnergyController::Error newError);
};
#endif // VIRTUALBIKE_H

View File

@@ -1,11 +1,16 @@
#include "virtualtreadmill.h"
#include <QtMath>
virtualtreadmill::virtualtreadmill(treadmill* t, bool noHeartService)
{
treadMill = t;
this->noHeartService = noHeartService;
volatile double currentSpeed = 0;
volatile double currentIncline = 0;
volatile uint8_t currentHeart = 0;
volatile double requestSpeed = -1;
volatile double requestIncline = -1;
volatile int8_t requestStart = -1;
volatile int8_t requestStop = -1;
virtualtreadmill::virtualtreadmill()
{
//! [Advertising Data]
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
@@ -55,33 +60,27 @@ virtualtreadmill::virtualtreadmill(treadmill* t, bool noHeartService)
serviceData.addCharacteristic(charData3);
//! [Service Data]
if(noHeartService == false)
{
QLowEnergyCharacteristicData charDataHR;
charDataHR.setUuid(QBluetoothUuid::HeartRateMeasurement);
charDataHR.setValue(QByteArray(2, 0));
charDataHR.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfigHR(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charDataHR.addDescriptor(clientConfigHR);
QLowEnergyCharacteristicData charDataHR;
charDataHR.setUuid(QBluetoothUuid::HeartRateMeasurement);
charDataHR.setValue(QByteArray(2, 0));
charDataHR.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfigHR(QBluetoothUuid::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charDataHR.addDescriptor(clientConfigHR);
serviceDataHR.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceDataHR.setUuid(QBluetoothUuid::HeartRate);
serviceDataHR.addCharacteristic(charDataHR);
}
QLowEnergyServiceData serviceDataHR;
serviceDataHR.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceDataHR.setUuid(QBluetoothUuid::HeartRate);
serviceDataHR.addCharacteristic(charDataHR);
//! [Start Advertising]
leController = QLowEnergyController::createPeripheral();
Q_ASSERT(leController);
service = leController->addService(serviceData);
if(noHeartService == false)
serviceHR = leController->addService(serviceDataHR);
serviceHR = leController->addService(serviceDataHR);
QObject::connect(service, SIGNAL(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)), this, SLOT(characteristicChanged(const QLowEnergyCharacteristic, const QByteArray)));
QLowEnergyAdvertisingParameters pars;
pars.setInterval(100, 100);
leController->startAdvertising(pars, advertisingData,
leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData,
advertisingData);
//! [Start Advertising]
@@ -94,7 +93,7 @@ virtualtreadmill::virtualtreadmill(treadmill* t, bool noHeartService)
void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
emit debug("characteristicChanged " + QString::number(characteristic.uuid().toUInt16()) + " " + newValue);
qDebug() << "characteristicChanged" << characteristic.uuid().toUInt16() << newValue;
char a;
char b;
@@ -109,9 +108,8 @@ void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
b = newValue.at(2);
uint16_t uspeed = a + (((uint16_t)b) << 8);
double requestSpeed = (double)uspeed / 100.0;
treadMill->changeSpeed(requestSpeed);
emit debug("new requested speed " + QString::number(requestSpeed));
requestSpeed = (double)uspeed / 100.0;
qDebug() << "new requested speed" << requestSpeed;
}
else if ((char)newValue.at(0)== 0x03) // Set Target Inclination
{
@@ -119,21 +117,20 @@ void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
b = newValue.at(2);
int16_t sincline = a + (((int16_t)b) << 8);
double requestIncline = (double)sincline / 10.0;
requestIncline = (double)sincline / 10.0;
if(requestIncline < 0)
requestIncline = 0;
treadMill->changeInclination(requestIncline);
emit debug("new requested incline " +QString::number(requestIncline));
qDebug() << "new requested incline" << requestIncline;
}
else if ((char)newValue.at(0)== 0x07) // Start request
{
treadMill->start();
emit debug("request to start");
requestStart = 1;
qDebug() << "request to start";
}
else if ((char)newValue.at(0)== 0x08) // Stop request
{
treadMill->stop();
emit debug("request to stop");
requestStop = 1;
qDebug() << "request to stop";
}
break;
}
@@ -141,12 +138,7 @@ void virtualtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
void virtualtreadmill::reconnect()
{
emit debug("virtualtreadmill reconnect");
service = leController->addService(serviceData);
if(noHeartService == false)
serviceHR = leController->addService(serviceDataHR);
if (service)
leController->startAdvertising(QLowEnergyAdvertisingParameters(),
advertisingData, advertisingData);
@@ -154,29 +146,23 @@ void virtualtreadmill::reconnect()
void virtualtreadmill::treadmillProvider()
{
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtualtreadmill connection error");
return;
}
QByteArray value;
value.append(0x08); // Inclination avaiable
value.append((char)0x01); // heart rate avaiable
value.append((char)0x00);
uint16_t normalizeSpeed = (uint16_t)qRound(treadMill->currentSpeed() * 100);
uint16_t normalizeSpeed = (uint16_t)qRound(currentSpeed * 100);
char a = (normalizeSpeed >> 8) & 0XFF;
char b = normalizeSpeed & 0XFF;
QByteArray speedBytes;
speedBytes.append(b);
speedBytes.append(a);
uint16_t normalizeIncline = (uint32_t)qRound(treadMill->currentInclination() * 10);
uint16_t normalizeIncline = (uint32_t)qRound(currentIncline * 10);
a = (normalizeIncline >> 8) & 0XFF;
b = normalizeIncline & 0XFF;
QByteArray inclineBytes;
inclineBytes.append(b);
inclineBytes.append(a);
double ramp = qRadiansToDegrees(qAtan(treadMill->currentInclination()/100));
double ramp = qRadiansToDegrees(qAtan(currentIncline/100));
int16_t normalizeRamp = (int32_t)qRound(ramp * 10);
a = (normalizeRamp >> 8) & 0XFF;
b = normalizeRamp & 0XFF;
@@ -190,51 +176,63 @@ void virtualtreadmill::treadmillProvider()
value.append(rampBytes); //ramp angle
value.append(treadMill->currentHeart()); // current heart rate
QLowEnergyCharacteristic characteristic
= service->characteristic((QBluetoothUuid::CharacteristicType)0x2ACD); //TreadmillDataCharacteristicUuid
Q_ASSERT(characteristic.isValid());
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtualtreadmill connection error");
return;
}
try {
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
} catch (...) {
emit debug("virtualtreadmill error!");
}
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
//characteristic
// = service->characteristic((QBluetoothUuid::CharacteristicType)0x2AD9); // Fitness Machine Control Point
//Q_ASSERT(characteristic.isValid());
//service->readCharacteristic(characteristic);
if(noHeartService == false)
QByteArray valueHR;
valueHR.append(char(0)); // Flags that specify the format of the value.
valueHR.append(char(currentHeart)); // Actual value.
QLowEnergyCharacteristic characteristicHR
= serviceHR->characteristic(QBluetoothUuid::HeartRateMeasurement);
Q_ASSERT(characteristicHR.isValid());
serviceHR->writeCharacteristic(characteristicHR, valueHR); // Potentially causes notification.
QFile* log;
QDomDocument docStatus;
QDomElement docRoot;
QDomElement docTreadmill;
QDomElement docHeart;
log = new QFile("status.xml");
if(!log->open(QIODevice::WriteOnly | QIODevice::Text))
{
QByteArray valueHR;
valueHR.append(char(0)); // Flags that specify the format of the value.
valueHR.append(char(treadMill->currentHeart())); // Actual value.
QLowEnergyCharacteristic characteristicHR
= serviceHR->characteristic(QBluetoothUuid::HeartRateMeasurement);
Q_ASSERT(characteristicHR.isValid());
if(leController->state() != QLowEnergyController::ConnectedState)
{
emit debug("virtualtreadmill connection error");
return;
}
try {
serviceHR->writeCharacteristic(characteristicHR, valueHR); // Potentially causes notification.
} catch (...) {
emit debug("virtualtreadmill error!");
}
qDebug() << "Open status.xml for writing failed";
}
docRoot = docStatus.createElement("Gym");
docStatus.appendChild(docRoot);
docTreadmill = docStatus.createElement("Treadmill");
docTreadmill.setAttribute("Speed", QString::number(currentSpeed));
docTreadmill.setAttribute("Incline", QString::number(currentIncline));
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->close();
}
bool virtualtreadmill::connected()
uint16_t virtualtreadmill::watts()
{
if(!leController)
return false;
return leController->state() == QLowEnergyController::ConnectedState;
// calc Watts ref. https://alancouzens.com/blog/Run_Power.html
uint16_t watts=0;
if(currentSpeed > 0)
{
double weight=75.0; // TODO: config need
double pace=60/currentSpeed;
double VO2R=210.0/pace;
double VO2A=(VO2R*weight)/1000.0;
double hwatts=75*VO2A;
double vwatts=((9.8*weight) * (currentIncline/100));
watts=hwatts+vwatts;
}
return watts;
}

View File

@@ -22,14 +22,13 @@
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include "treadmill.h"
#include <QtXml>
class virtualtreadmill: public QObject
class virtualtreadmill: QObject
{
Q_OBJECT
public:
virtualtreadmill(treadmill* t, bool noHeartService);
bool connected();
virtualtreadmill();
private:
QLowEnergyController* leController;
@@ -37,14 +36,8 @@ private:
QLowEnergyService* serviceHR;
QLowEnergyAdvertisingData advertisingData;
QLowEnergyServiceData serviceData;
QLowEnergyServiceData serviceDataHR;
QTimer treadmillTimer;
treadmill* treadMill;
bool noHeartService = false;
signals:
void debug(QString string);
QTimer treadmillTimer;
uint16_t watts();
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rows>
<row duration="00:02:00" speed="5" inclination="0" forcespeed="1"/>
<row duration="00:03:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:05:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7.7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7.7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7.7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
<row duration="00:01:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:02:00" speed="7" inclination="1" forcespeed="1"/>
<row duration="00:06:00" speed="6" inclination="0" forcespeed="1"/>
<row duration="00:01:00" speed="5" inclination="0" forcespeed="1"/>
</rows>

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rows>
<row duration="00:01:00" speed="9.85447" inclination="-1.82658" forcespeed="1"/>
<row duration="00:01:00" speed="10.2993" inclination="-2.91281" forcespeed="1"/>
<row duration="00:01:00" speed="10.2958" inclination="-2.15623" forcespeed="1"/>
<row duration="00:01:00" speed="10.2988" inclination="-0.174779" forcespeed="1"/>
<row duration="00:01:00" speed="10.3375" inclination="-2.49576" forcespeed="1"/>
<row duration="00:01:00" speed="11.0169" inclination="-1.41601" forcespeed="1"/>
<row duration="00:01:00" speed="11.8215" inclination="-2.0302" forcespeed="1"/>
<row duration="00:01:00" speed="11.0147" inclination="-1.19839" forcespeed="1"/>
<row duration="00:01:00" speed="3.66404" inclination="3.11131" forcespeed="1"/>
<row duration="00:01:00" speed="9.80634" inclination="5.75139" forcespeed="1"/>
<row duration="00:01:00" speed="8.71754" inclination="9.91104" forcespeed="1"/>
<row duration="00:01:00" speed="8.99724" inclination="9.40288" forcespeed="1"/>
<row duration="00:01:00" speed="10.11" inclination="4.51039" forcespeed="1"/>
<row duration="00:01:00" speed="10.1726" inclination="4.01078" forcespeed="1"/>
<row duration="00:01:00" speed="9.96776" inclination="6.01941" forcespeed="1"/>
<row duration="00:01:00" speed="9.21979" inclination="9.24099" forcespeed="1"/>
<row duration="00:01:00" speed="9.00303" inclination="9.39684" forcespeed="1"/>
<row duration="00:01:00" speed="7.59236" inclination="7.6656" forcespeed="1"/>
<row duration="00:01:00" speed="9.24165" inclination="5.12895" forcespeed="1"/>
<row duration="00:01:00" speed="6.95364" inclination="9.23258" forcespeed="1"/>
<row duration="00:01:00" speed="10.4115" inclination="6.45437" forcespeed="1"/>
<row duration="00:01:00" speed="9.95344" inclination="5.9678" forcespeed="1"/>
<row duration="00:01:00" speed="10.3448" inclination="2.72599" forcespeed="1"/>
<row duration="00:01:00" speed="10.485" inclination="0.286123" forcespeed="1"/>
<row duration="00:01:00" speed="9.42228" inclination="-5.4127" forcespeed="1"/>
<row duration="00:01:00" speed="11.5566" inclination="-6.5417" forcespeed="1"/>
<row duration="00:01:00" speed="10.3381" inclination="-3.48228" forcespeed="1"/>
<row duration="00:01:00" speed="12.2822" inclination="-2.34486" forcespeed="1"/>
<row duration="00:01:00" speed="10.4139" inclination="4.72445" forcespeed="1"/>
<row duration="00:01:00" speed="8.69461" inclination="-5.38266" forcespeed="1"/>
<row duration="00:01:00" speed="10.1889" inclination="-3.06215" forcespeed="1"/>
<row duration="00:01:00" speed="9.98059" inclination="2.46477" forcespeed="1"/>
<row duration="00:01:00" speed="10.9802" inclination="3.87972" forcespeed="1"/>
<row duration="00:01:00" speed="9.05151" inclination="-0.0662914" forcespeed="1"/>
<row duration="00:01:00" speed="3.99165" inclination="-19.9917" forcespeed="1"/>
<row duration="00:01:00" speed="8.7631" inclination="-12.0505" forcespeed="1"/>
<row duration="00:01:00" speed="6.64707" inclination="-16.2478" forcespeed="1"/>
<row duration="00:01:00" speed="6.51384" inclination="-14.8299" forcespeed="1"/>
<row duration="00:01:00" speed="8.57592" inclination="-10.8443" forcespeed="1"/>
<row duration="00:01:00" speed="8.401" inclination="-8.92751" forcespeed="1"/>
<row duration="00:01:00" speed="11.2526" inclination="-2.45276" forcespeed="1"/>
<row duration="00:01:00" speed="11.6882" inclination="-1.12934" forcespeed="1"/>
<row duration="00:01:00" speed="13.9318" inclination="-0.387599" forcespeed="1"/>
<row duration="00:01:00" speed="10.6752" inclination="-0.112417" forcespeed="1"/>
<row duration="00:01:00" speed="10.2653" inclination="-2.33797" forcespeed="1"/>
<row duration="00:01:00" speed="10.7095" inclination="-2.18497" forcespeed="1"/>
<row duration="00:01:00" speed="9.3889" inclination="2.10888" forcespeed="1"/>
<row duration="00:01:00" speed="11.1298" inclination="-3.01893" forcespeed="1"/>
<row duration="00:01:00" speed="11.9722" inclination="-1.90441" forcespeed="1"/>
<row duration="00:01:00" speed="12.0696" inclination="-1.73991" forcespeed="1"/>
<row duration="00:01:00" speed="12.9631" inclination="-1.80512" forcespeed="1"/>
<row duration="00:01:00" speed="12.336" inclination="-1.89688" forcespeed="1"/>
<row duration="00:01:00" speed="11.5617" inclination="-0.467066" forcespeed="1"/>
<row duration="00:01:00" speed="11.3423" inclination="-1.63987" forcespeed="1"/>
<row duration="00:01:00" speed="11.5684" inclination="0" forcespeed="1"/>
<row duration="00:01:00" speed="10.971" inclination="0.656273" forcespeed="1"/>
<row duration="00:01:00" speed="11.2002" inclination="-0.642841" forcespeed="1"/>
<row duration="00:01:00" speed="11.1976" inclination="-0.910918" forcespeed="1"/>
<row duration="00:01:00" speed="10.4373" inclination="-0.977264" forcespeed="1"/>
<row duration="00:01:00" speed="10.2949" inclination="-0.582811" forcespeed="1"/>
<row duration="00:01:00" speed="10.9175" inclination="0.0549612" forcespeed="1"/>
<row duration="00:01:00" speed="9.11082" inclination="2.30495" forcespeed="1"/>
<row duration="00:01:00" speed="10.4257" inclination="1.20855" forcespeed="1"/>
<row duration="00:01:00" speed="10.9692" inclination="1.42217" forcespeed="1"/>
<row duration="00:01:00" speed="10.5789" inclination="1.24777" forcespeed="1"/>
<row duration="00:01:00" speed="10.5046" inclination="0.971002" forcespeed="1"/>
<row duration="00:01:00" speed="10.5973" inclination="0.339712" forcespeed="1"/>
<row duration="00:01:00" speed="10.4905" inclination="1.14389" forcespeed="1"/>
<row duration="00:01:00" speed="11.7968" inclination="1.5767" forcespeed="1"/>
<row duration="00:01:00" speed="10.9239" inclination="1.70268" forcespeed="1"/>
<row duration="00:01:00" speed="12.6751" inclination="2.46152" forcespeed="1"/>
</rows>