Compare commits

..

25 Commits

Author SHA1 Message Date
Roberto Viola
19c3a90bf4 storage permission on android for the logs
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 15:52:19 +01:00
Roberto Viola
705baaa37c odometer on echelonconnectsport fixed
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 15:37:06 +01:00
Roberto Viola
e302e90066 logs on android restored
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 15:36:38 +01:00
Roberto Viola
9d9800d4e6 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2020-11-30 14:04:09 +01:00
Roberto Viola
5c00a959f4 echelonconnectsport service UUID fixed
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 14:03:39 +01:00
Roberto Viola
54501760d3 cache dropped 2020-11-30 13:49:59 +01:00
Roberto Viola
2f4b76014f android compatibility issue fixed
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 11:44:23 +01:00
Roberto Viola
7de4bac932 fixed elevation gain on domyostreadmill
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 11:15:10 +01:00
Roberto Viola
d992959792 android build commented 2020-11-30 10:25:14 +01:00
Roberto Viola
1ce77629ff echelonconnectsport added (NOT TESTED)
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 10:16:06 +01:00
Roberto Viola
908c1536f6 signal icon added to QML
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 08:09:49 +01:00
Roberto Viola
b8948c6d8f bluetoothdeviceinfo move to bluetoothdevice to get signal strength
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-30 07:32:39 +01:00
Roberto Viola
1ab448f7cc ugly workaround for zwift virtualtreadmill on raspberry 2020-11-29 17:13:11 +01:00
Roberto Viola
38ea3f5c80 fixed cadence on UI 2020-11-29 17:09:30 +01:00
Roberto Viola
a87e818d9a virtual treadmill created when the domyostreadmill has finished its init
Signed-off-by: Roberto Viola <roberto.viola83@gmail.com>
2020-11-28 17:47:17 +01:00
Roberto Viola
d852bd44fe added root check 2020-11-28 17:14:55 +01:00
Roberto Viola
5f7d7e01b8 speed and inclination not synced in the state file fixed
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-27 16:01:52 +01:00
Roberto Viola
548fa9d8d6 android qml: show relevant icons only
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-27 15:56:10 +01:00
Roberto Viola
3a725d71b5 linux compilation fixed
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-25 15:17:28 +01:00
Roberto Viola
a304963dc5 decimal point fixed on the state file
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-25 09:45:22 +01:00
Roberto Viola
bf9fb4537b restore from previous values on domyostreadmill
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-25 09:37:15 +01:00
Roberto Viola
838fe8c96e xml state file written for domyostreadmill (untested)
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-25 06:08:59 +01:00
Roberto Viola
77b204d9fd SIGINT handled
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-25 05:55:04 +01:00
Roberto Viola
8a6e8e9c9d youtube video added 2020-11-23 14:41:18 +01:00
Roberto Viola
123df9db6b saving speed and inclination for future session on domyostreadmill
Signed-off-by: Roberto Viola <roberto.viola@systemceramics.com>
2020-11-23 10:40:25 +01:00
30 changed files with 1080 additions and 117 deletions

View File

@@ -19,19 +19,19 @@ jobs:
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Cache Qt Linux Desktop
id: cache-qt-linux-desktop
uses: actions/cache@v1
with:
path: '${{ github.workspace }}/output/linux-desktop/'
key: ${{ runner.os }}-QtCache-Linux-Desktop
# - name: Cache Qt Linux Desktop
# id: cache-qt-linux-desktop
# uses: actions/cache@v1
# with:
# path: '${{ github.workspace }}/output/linux-desktop/'
# key: ${{ runner.os }}-QtCache-Linux-Desktop
- name: Cache Qt Linux Android
id: cache-qt-android
uses: actions/cache@v1
with:
path: '${{ github.workspace }}/output/android/'
key: ${{ runner.os }}-QtCache-Android
# - name: Cache Qt Linux Android
# id: cache-qt-android
# uses: actions/cache@v1
# with:
# path: '${{ github.workspace }}/output/android/'
# key: ${{ runner.os }}-QtCache-Android
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
@@ -45,7 +45,7 @@ jobs:
target: 'desktop'
modules: 'qtcharts debug_info'
dir: '${{ github.workspace }}/output/linux-desktop/'
cached: ${{ steps.cache-qt-linux-desktop.outputs.cache-hit }}
# cached: ${{ steps.cache-qt-linux-desktop.outputs.cache-hit }}
- name: Compile Linux Desktop
run: cd src; qmake; make -j4
@@ -56,39 +56,39 @@ jobs:
name: linux-desktop-binary
path: src/qdomyos-zwift
- uses: actions/checkout@v2
with:
repository: nttld/setup-ndk
path: setup-ndk
# - uses: actions/checkout@v2
# with:
# repository: nttld/setup-ndk
# path: setup-ndk
# The packages.json in nttld/setup-ndk has already been updated,
# https://github.com/nttld/setup-ndk/commit/831db5b02a0f0cab80614619efe461a3dcc140e6
# but `dist/*` has not been rebuilt yet. Build it.
# https://github.com/nttld/setup-ndk/tree/main/dist
- name: Locally rebuilt setup-ndk
run: |
npm -prefix ./setup-ndk install
npm -prefix ./setup-ndk run all
# - name: Locally rebuilt setup-ndk
# run: |
# npm -prefix ./setup-ndk install
# npm -prefix ./setup-ndk run all
# Install using locally rebuilt setup-ndk
- name: Setup Android NDK r21d
uses: ./setup-ndk
# - name: Setup Android NDK r21d
# uses: ./setup-ndk
#- uses: nttld/setup-ndk@v1
with:
ndk-version: r21d
# with:
# ndk-version: r21d
# waiting github.com/jurplel/install-qt-action/issues/63
- name: Install Qt Android
uses: jurplel/install-qt-action@v2
with:
version: '5.12.9'
host: 'linux'
target: 'android'
arch: 'android_armv7'
modules: 'qtcharts debug_info'
dir: '${{ github.workspace }}/output/android/'
cached: ${{ steps.cache-qt-android.outputs.cache-hit }}
# - name: Install Qt Android
# uses: jurplel/install-qt-action@v2
# with:
# version: '5.12.9'
# host: 'linux'
# target: 'android'
# arch: 'android_armv7'
# modules: 'qtcharts debug_info'
# dir: '${{ github.workspace }}/output/android/'
# cached: ${{ steps.cache-qt-android.outputs.cache-hit }}
- name: Compile Android
run: cd src; qmake; make -j4
# - name: Compile Android
# run: cd src; qmake; make -j4
# - name: Install Qt MacOS
# uses: jurplel/install-qt-action@v2

View File

@@ -5,6 +5,8 @@ Zwift bridge for Treadmills and Bike!
![UI](docs/treadmill-bridge-schema.png)
[![Video](https://img.youtube.com/vi/GgG3dMhmo2Y/0.jpg)](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
![UI](docs/ui.png)
![UI](docs/realtime-chart.png)

View File

@@ -37,6 +37,7 @@ HomeForm{
width: 175
height: 125
visible: visibleItem
Component.onCompleted: console.log("completed " + objectName)
Rectangle {

View File

@@ -28,19 +28,42 @@ Page {
width: 50
height: 100
color: Material.backgroundColor
Image {
Column {
id: column
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
id: treadmill_connection
width: 48
height: 48
source: "icons/icons/bluetooth-icon.png"
enabled: rootItem.device
smooth: true
}
ColorOverlay {
anchors.fill: treadmill_connection
source: treadmill_connection
color: treadmill_connection.enabled ? "#00000000" : "#B0D3d3d3"
width: parent.width
height: 100
spacing: 0
padding: 0
Rectangle {
width: 50
height: 100
color: Material.backgroundColor
Image {
anchors.verticalCenter: parent.verticalCenter
id: treadmill_connection
width: 48
height: 48
source: "icons/icons/bluetooth-icon.png"
enabled: rootItem.device
smooth: true
}
ColorOverlay {
anchors.fill: treadmill_connection
source: treadmill_connection
color: treadmill_connection.enabled ? "#00000000" : "#B0D3d3d3"
}
}
Image {
anchors.horizontalCenter: parent.horizontalCenter
id: treadmill_signal
width: 24
height: 24
source: rootItem.signal
smooth: true
}
}
}

View File

@@ -3,8 +3,9 @@
#include <QDateTime>
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
#include <QtXml>
bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool noHeartService, uint32_t pollDeviceTime, bool noConsole, bool testResistance) : QObject(nullptr)
bluetooth::bluetooth(bool logs, QString deviceName, bool noWriteResistance, bool noHeartService, uint32_t pollDeviceTime, bool noConsole, bool testResistance)
{
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
filterDevice = deviceName;
@@ -69,11 +70,26 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
discoveryAgent->stop();
domyos = new domyostreadmill(this->pollDeviceTime, noConsole, noHeartService);
stateFileRead();
emit(deviceConnected());
connect(domyos, SIGNAL(disconnected()), this, SLOT(restart()));
connect(domyos, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
connect(domyos, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
connect(domyos, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
domyos->deviceDiscovered(device);
}
else if(device.name().startsWith("ECH-SPORT") && filter)
{
discoveryAgent->stop();
echelonConnectSport = new echelonconnectsport(noWriteResistance, noHeartService);
//stateFileRead();
emit(deviceConnected());
connect(echelonConnectSport, SIGNAL(disconnected()), this, SLOT(restart()));
connect(echelonConnectSport, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
//connect(echelonConnectSport, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
//connect(echelonConnectSport, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
echelonConnectSport->deviceDiscovered(device);
}
else if((device.name().startsWith("TRX ROUTE KEY")) && filter)
{
discoveryAgent->stop();
@@ -116,6 +132,11 @@ void bluetooth::restart()
delete trxappgateusb;
trxappgateusb = 0;
}
if(echelonConnectSport)
{
delete echelonConnectSport;
echelonConnectSport = 0;
}
discoveryAgent->start();
}
@@ -129,5 +150,104 @@ bluetoothdevice* bluetooth::device()
return toorx;
else if(trxappgateusb)
return trxappgateusb;
else if(echelonConnectSport)
return echelonConnectSport;
return nullptr;
}
bool bluetooth::handleSignal(int signal)
{
if(signal == SIGNALS::SIG_INT)
{
qDebug() << "SIGINT";
QFile::remove("status.xml");
exit(0);
}
// Let the signal propagate as though we had not been there
return false;
}
void bluetooth::stateFileRead()
{
if(!device()) return;
QFile* log;
QDomDocument xmlBOM;
log = new QFile("status.xml");
if(!log->open(QIODevice::ReadOnly | QIODevice::Text))
{
qDebug() << "Open status.xml for writing failed";
return;
}
xmlBOM.setContent(log);
QDomElement root=xmlBOM.documentElement();
// Get root names and attributes
QString Type=root.tagName();
QString lastUpdated = root.attribute("Updated", QDateTime::currentDateTime().toString());
QDomElement machine=root.firstChild().toElement();
// Loop while there is a child
while(!machine.isNull())
{
// Check if the child tag name is COMPONENT
if (machine.tagName()=="Treadmill")
{
// Read and display the component ID
double speed = machine.attribute("Speed", "0.0").toDouble();
double inclination = machine.attribute("Incline", "0.0").toDouble();
((domyostreadmill*)device())->setLastSpeed(speed);
((domyostreadmill*)device())->setLastInclination(inclination);
}
// Next component
machine = machine.nextSibling().toElement();
}
log->close();
}
void bluetooth::stateFileUpdate()
{
if(!device()) return;
if(device()->deviceType() != bluetoothdevice::TREADMILL) return;
QFile* log;
QDomDocument docStatus;
QDomElement docRoot;
QDomElement docTreadmill;
QDomElement docHeart;
log = new QFile("status.xml");
if(!log->open(QIODevice::WriteOnly | QIODevice::Text))
{
qDebug() << "Open status.xml for writing failed";
return;
}
docRoot = docStatus.createElement("Gym");
docStatus.appendChild(docRoot);
docTreadmill = docStatus.createElement("Treadmill");
docTreadmill.setAttribute("Speed", QString::number(device()->currentSpeed(), 'f', 1));
docTreadmill.setAttribute("Incline", QString::number(((treadmill*)device())->currentInclination(), 'f', 1));
docRoot.appendChild(docTreadmill);
//docHeart = docStatus.createElement("Heart");
//docHeart.setAttribute("Rate", QString::number(currentHeart));
//docRoot.appendChild(docHeart);
docRoot.setAttribute("Updated", QDateTime::currentDateTime().toString());
QTextStream stream(log);
stream << docStatus.toString();
log->flush();
log->close();
}
void bluetooth::speedChanged(double speed)
{
Q_UNUSED(speed);
stateFileUpdate();
}
void bluetooth::inclinationChanged(double inclination)
{
Q_UNUSED(inclination);
stateFileUpdate();
}

View File

@@ -20,9 +20,11 @@
#include "domyosbike.h"
#include "trxappgateusbtreadmill.h"
#include "toorxtreadmill.h"
#include "echelonconnectsport.h"
#include "bluetoothdevice.h"
#include "signalhandler.h"
class bluetooth : public QObject
class bluetooth : public QObject, public SignalHandler
{
Q_OBJECT
public:
@@ -36,6 +38,7 @@ private:
domyosbike* domyosBike = 0;
toorxtreadmill* toorx = 0;
trxappgateusbtreadmill* trxappgateusb = 0;
echelonconnectsport* echelonConnectSport = 0;
QString filterDevice = "";
bool testResistance = false;
bool noWriteResistance = false;
@@ -44,6 +47,10 @@ private:
bool logs = true;
uint32_t pollDeviceTime = 200;
bool handleSignal(int signal);
void stateFileUpdate();
void stateFileRead();
signals:
void deviceConnected();
void deviceFound(QString name);
@@ -54,6 +61,8 @@ public slots:
private slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void speedChanged(double);
void inclinationChanged(double);
signals:

View File

@@ -11,7 +11,7 @@ void bluetoothdevice::start(){ requestStart = 1; }
void bluetoothdevice::stop(){ requestStop = 1; }
unsigned char bluetoothdevice::currentHeart(){ return Heart; }
double bluetoothdevice::currentSpeed(){ return Speed; }
QTime bluetoothdevice::currentPace(){ return QTime(0, (int)(1.0 / (Speed / 60.0)), (((double)(1.0 / (Speed / 60.0)) - ((double)((int)(1.0 / (Speed / 60.0))))) * 60.0), 0 ); }
QTime bluetoothdevice::currentPace(){ if(Speed == 0) return QTime(0,0,0,0); else return QTime(0, (int)(1.0 / (Speed / 60.0)), (((double)(1.0 / (Speed / 60.0)) - ((double)((int)(1.0 / (Speed / 60.0))))) * 60.0), 0 ); }
double bluetoothdevice::odometer(){ return Distance; }
double bluetoothdevice::calories(){ return KCal; }
uint8_t bluetoothdevice::fanSpeed() { return FanSpeed; };

View File

@@ -2,6 +2,7 @@
#define BLUETOOTHDEVICE_H
#include <QObject>
#include <QBluetoothDeviceInfo>
class bluetoothdevice : public QObject
{
@@ -18,6 +19,7 @@ public:
virtual void* VirtualDevice();
uint16_t watts(double weight=75.0);
virtual bool changeFanSpeed(uint8_t speed);
QBluetoothDeviceInfo bluetoothDevice;
enum BLUETOOTH_TYPE {
UNKNOWN = 0,

View File

@@ -129,7 +129,7 @@ void domyosbike::update()
//else
// btinit_telink(false);
}
else if(btbike.isValid() &&
else if(bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
@@ -455,7 +455,7 @@ void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("Domyos-Bike") && !device.name().startsWith("DomyosBridge"))
{
btbike = device;
bluetoothDevice = device;
if(device.address().toString().startsWith("57"))
{
@@ -468,7 +468,7 @@ void domyosbike::deviceDiscovered(const QBluetoothDeviceInfo &device)
bike_type = CHANG_YOW;
}
m_control = QLowEnergyController::createCentral(btbike, this);
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
connect(m_control, SIGNAL(discoveryFinished()),

View File

@@ -53,7 +53,6 @@ private:
QTimer* refresh;
virtualbike* virtualBike = 0;
QBluetoothDeviceInfo btbike;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;

View File

@@ -5,6 +5,9 @@
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
static double lastSpeed = 0.0;
static double lastInclination = 0;
// set speed and incline to 0
uint8_t initData1[] = { 0xf0, 0xc8, 0x01, 0xb9 };
uint8_t initData2[] = { 0xf0, 0xc9, 0xb9 };
@@ -49,7 +52,6 @@ QBluetoothUuid _gattCommunicationChannelServiceId((QString)"49535343-fe7d-4ae5-8
QBluetoothUuid _gattWriteCharacteristicId((QString)"49535343-8841-43f4-a8d4-ecbe34729bb3");
QBluetoothUuid _gattNotifyCharacteristicId((QString)"49535343-1e4d-4bd9-ba61-23c647249616");
QBluetoothDeviceInfo bttreadmill;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;
@@ -58,10 +60,17 @@ QLowEnergyCharacteristic gattNotifyCharacteristic;
bool initDone = false;
bool initRequest = false;
domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService)
domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed, double forceInitInclination)
{
this->noConsole = noConsole;
this->noHeartService = noHeartService;
if(forceInitSpeed > 0)
lastSpeed = forceInitSpeed;
if(forceInitInclination > 0)
lastInclination = forceInitInclination;
refresh = new QTimer(this);
initDone = false;
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
@@ -136,8 +145,8 @@ void domyostreadmill::updateDisplay(uint16_t elapsed)
display[26] += display[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed) );
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed) );
writeCharacteristic(display, 20, "updateDisplay elapsed=" + QString::number(elapsed), false, false );
writeCharacteristic(&display[20], sizeof (display) - 20, "updateDisplay elapsed=" + QString::number(elapsed), false, true );
}
void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestIncline)
@@ -161,8 +170,8 @@ void domyostreadmill::forceSpeedOrIncline(double requestSpeed, double requestInc
//qDebug() << "writeIncline crc" << QString::number(writeIncline[26], 16);
writeCharacteristic(writeIncline, 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline));
writeCharacteristic(&writeIncline[20], sizeof (writeIncline) - 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline));
writeCharacteristic(writeIncline, 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline), false, false);
writeCharacteristic(&writeIncline[20], sizeof (writeIncline) - 20, "forceSpeedOrIncline speed=" + QString::number(requestSpeed) + " incline=" + QString::number(requestIncline), false, true);
}
bool domyostreadmill::changeFanSpeed(uint8_t speed)
@@ -178,7 +187,7 @@ bool domyostreadmill::changeFanSpeed(uint8_t speed)
fanSpeed[3] += fanSpeed[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(fanSpeed, 4, "changeFanSpeed speed=" + QString::number(speed));
writeCharacteristic(fanSpeed, 4, "changeFanSpeed speed=" + QString::number(speed), false, true);
return true;
}
@@ -199,15 +208,36 @@ void domyostreadmill::update()
if(initRequest)
{
initRequest = false;
btinit(false);
btinit((lastSpeed > 0 ? true : false));
}
else if(bttreadmill.isValid() &&
else if(bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
gattNotifyCharacteristic.isValid() &&
initDone)
{
// ******************************************* virtual treadmill init *************************************
static uint8_t firstInit = 0;
if(!firstInit)
{
debug("creating virtual treadmill interface...");
virtualTreadMill = new virtualtreadmill(this, noHeartService);
#ifdef Q_OS_LINUX
#ifndef Q_OS_ANDROID
// on raspberry, the very first time you run the bridge it doesn't work. let's try in this way
delete virtualTreadMill;
virtualTreadMill = new virtualtreadmill(this, noHeartService);
#endif
#endif
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&domyostreadmill::debug);
}
firstInit = 1;
// ********************************************************************************************************
debug("Domyos Treadmill RSSI " + QString::number(bluetoothDevice.rssi()));
QDateTime current = QDateTime::currentDateTime();
if(currentSpeed() > 0.0 && !first)
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
@@ -263,6 +293,8 @@ void domyostreadmill::update()
if(requestStart != -1)
{
debug("starting...");
if(lastSpeed == 0.0)
lastSpeed = 0.5;
btinit(true);
requestStart = -1;
emit tapeStarted();
@@ -287,7 +319,7 @@ void domyostreadmill::update()
}
}
elevationAcc += (currentSpeed() / 3600.0) * 1000 * (currentInclination() / 100) * (refresh->interval() / 1000);
elevationAcc += (currentSpeed() / 3600.0) * 1000.0 * (currentInclination() / 100.0) * ((double)refresh->interval() / 1000.0);
}
first = false;
@@ -390,11 +422,26 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
if(m_control->error() != QLowEnergyController::NoError)
qDebug() << "QLowEnergyController ERROR!!" << m_control->errorString();
Speed = speed;
Inclination = incline;
if(Speed != speed)
{
Speed = speed;
emit speedChanged(speed);
}
if(Inclination != incline)
{
Inclination = incline;
emit inclinationChanged(incline);
}
KCal = kcal;
Distance = distance;
if(speed > 0)
{
lastSpeed = speed;
lastInclination = incline;
}
lastTime = QDateTime::currentDateTime();
first = false;
}
@@ -422,7 +469,12 @@ double domyostreadmill::GetDistanceFromPacket(QByteArray packet)
double domyostreadmill::GetInclinationFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(2) << 8) | packet.at(3);
double data = ((double)convertedData - 1000.0f) / 10.0f;
double data;
if(convertedData > 10000)
data = ((double)convertedData - 65512.0f) / 10.0f;
else
data = ((double)convertedData - 1000.0f) / 10.0f;
if (data < 0) return 0;
return data;
}
@@ -436,16 +488,20 @@ void domyostreadmill::btinit(bool startTape)
writeCharacteristic(initDataStart3, sizeof(initDataStart3), "init", false, true);
writeCharacteristic(initDataStart4, sizeof(initDataStart4), "init", false, true);
writeCharacteristic(initDataStart5, sizeof(initDataStart5), "init", false, true);
writeCharacteristic(initDataStart6, sizeof(initDataStart6), "init", false, false);
writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
//writeCharacteristic(initDataStart6, sizeof(initDataStart6), "init", false, false);
//writeCharacteristic(initDataStart7, sizeof(initDataStart7), "init", false, true);
forceSpeedOrIncline(lastSpeed, lastInclination);
writeCharacteristic(initDataStart8, sizeof(initDataStart8), "init", false, false);
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
writeCharacteristic(initDataStart9, sizeof(initDataStart9), "init", false, true);
if(startTape)
{
writeCharacteristic(initDataStart10, sizeof(initDataStart10), "init", false, false);
writeCharacteristic(initDataStart11, sizeof(initDataStart11), "init", false, true);
writeCharacteristic(initDataStart12, sizeof(initDataStart12), "init", false, false);
writeCharacteristic(initDataStart13, sizeof(initDataStart13), "init", false, true);
forceSpeedOrIncline(lastSpeed, lastInclination);
}
initDone = true;
@@ -474,17 +530,6 @@ void domyostreadmill::stateChanged(QLowEnergyService::ServiceState state)
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
// ******************************************* virtual treadmill init *************************************
static uint8_t first = 0;
if(!first)
{
debug("creating virtual treadmill interface...");
virtualTreadMill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadMill,&virtualtreadmill::debug ,this,&domyostreadmill::debug);
}
first = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
@@ -532,8 +577,8 @@ void domyostreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("Domyos") && !device.name().startsWith("DomyosBridge"))
{
bttreadmill = device;
m_control = QLowEnergyController::createCentral(bttreadmill, this);
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
connect(m_control, SIGNAL(discoveryFinished()),
@@ -586,3 +631,13 @@ double domyostreadmill::odometer()
{
return DistanceCalculated;
}
void domyostreadmill::setLastSpeed(double speed)
{
lastSpeed = speed;
}
void domyostreadmill::setLastInclination(double inclination)
{
lastInclination = inclination;
}

View File

@@ -31,11 +31,14 @@ class domyostreadmill : public treadmill
{
Q_OBJECT
public:
domyostreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false);
domyostreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false, double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
bool connected();
bool changeFanSpeed(uint8_t speed);
double odometer();
void setLastSpeed(double speed);
void setLastInclination(double inclination);
void* VirtualTreadMill();
void* VirtualDevice();
@@ -61,6 +64,8 @@ private:
signals:
void disconnected();
void debug(QString string);
void speedChanged(double speed);
void inclinationChanged(double inclination);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);

361
src/echelonconnectsport.cpp Normal file
View File

@@ -0,0 +1,361 @@
#include "echelonconnectsport.h"
#include "virtualbike.h"
#include <QFile>
#include <QDateTime>
#include <QMetaEnum>
#include <QBluetoothLocalDevice>
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService)
{
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
initDone = false;
connect(refresh, SIGNAL(timeout()), this, SLOT(update()));
refresh->start(200);
}
void echelonconnectsport::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool wait_for_response)
{
QEventLoop loop;
if(wait_for_response)
{
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
&loop, SLOT(quit()));
}
else
{
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(QLowEnergyCharacteristic,QByteArray)),
&loop, SLOT(quit()));
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, QByteArray::fromRawData((const char*)data, data_len));
if(!disable_log)
debug(" >> " + QByteArray((const char*)data, data_len).toHex(' ') + " // " + info);
loop.exec();
}
void echelonconnectsport::sendPoll()
{
static uint8_t counter = 1;
uint8_t noOpData[] = { 0xf0, 0xa0, 0x01, 0x00, 0x00 };
noOpData[3] = counter;
for(uint8_t i=0; i<sizeof(noOpData)-1; i++)
{
noOpData[4] += noOpData[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(noOpData, sizeof(noOpData), "noOp", true);
counter++;
if(!counter)
counter = 1;
}
void echelonconnectsport::update()
{
static QDateTime lastTime;
static bool first = true;
static uint8_t sec1 = 0;
if(m_control->state() == QLowEnergyController::UnconnectedState)
{
emit disconnected();
return;
}
if(initRequest)
{
initRequest = false;
btinit();
}
else if(bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() &&
gattNotify2Characteristic.isValid() &&
initDone)
{
QDateTime current = QDateTime::currentDateTime();
if(currentSpeed() > 0.0 && !first)
elapsed += (((double)lastTime.msecsTo(current)) / ((double)1000.0));
lastTime = current;
// updating the treadmill console every second
if(sec1++ == (500 / refresh->interval()))
{
sec1 = 0;
//updateDisplay(elapsed);
}
sendPoll();
if(requestResistance != -1)
{
if(requestResistance > 15) requestResistance = 15;
else if(requestResistance == 0) requestResistance = 1;
if(requestResistance != currentResistance())
{
debug("writing resistance " + QString::number(requestResistance));
//forceResistance(requestResistance);
}
requestResistance = -1;
}
if(requestStart != -1)
{
debug("starting...");
btinit();
requestStart = -1;
emit bikeStarted();
}
if(requestStop != -1)
{
debug("stopping...");
//writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
first = false;
}
void echelonconnectsport::serviceDiscovered(const QBluetoothUuid &gatt)
{
debug("serviceDiscovered " + gatt.toString());
}
static QByteArray lastPacket;
void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
//qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
static QDateTime lastRefresh = QDateTime::currentDateTime();
debug(" << " + newValue.toHex(' '));
if (lastPacket.length() && lastPacket == newValue)
return;
lastPacket = newValue;
// resistance value is in another frame
if(newValue.length() == 5 && (uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd2)
{
Resistance = newValue.at(3);
debug("Current resistance: " + QString::number(Resistance));
return;
}
if (newValue.length() != 13)
return;
/*if ((uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd1)
return;*/
double distance = GetDistanceFromPacket(newValue);
Cadence = newValue.at(10);
CrankRevs += ((double)(lastRefresh.msecsTo(QDateTime::currentDateTime())) * ((double)Cadence / 60000.0) );
LastCrankEventTime += (uint16_t)((lastRefresh.msecsTo(QDateTime::currentDateTime())) * 1.024);
lastRefresh = QDateTime::currentDateTime();
debug("Current Local elapsed: " + GetElapsedFromPacket(newValue).toString());
debug("Current cadence: " + QString::number(Cadence));
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 = 0;
KCal = 0;
Distance = distance;
}
QTime echelonconnectsport::GetElapsedFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(3) << 8) | packet.at(4);
QTime t(0,convertedData / 60, convertedData % 60);
return t;
}
double echelonconnectsport::GetDistanceFromPacket(QByteArray packet)
{
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
double data = ((double)convertedData) / 10.0f;
return data;
}
void echelonconnectsport::btinit()
{
uint8_t initData1[] = { 0xf0, 0xa1, 0x00, 0x91 };
uint8_t initData2[] = { 0xf0, 0xa3, 0x00, 0x93 };
uint8_t initData3[] = { 0xf0, 0xb0, 0x01, 0x01, 0xa2 };
// in the snoof log it repeats this frame 4 times, i will have to analyze the response to understand if 4 times are enough
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData2, sizeof(initData2), "init", false, true);
writeCharacteristic(initData1, sizeof(initData1), "init", false, true);
writeCharacteristic(initData1, sizeof(initData3), "init", false, true);
initDone = true;
}
void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state)
{
QBluetoothUuid _gattWriteCharacteristicId((QString)"0bf669f2-45f2-11e7-9598-0800200c9a66");
QBluetoothUuid _gattNotify1CharacteristicId((QString)"0bf669f3-45f2-11e7-9598-0800200c9a66");
QBluetoothUuid _gattNotify2CharacteristicId((QString)"0bf669f4-45f2-11e7-9598-0800200c9a66");
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
debug("BTLE stateChanged " + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if(state == QLowEnergyService::ServiceDiscovered)
{
//qDebug() << gattCommunicationChannelService->characteristics();
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
Q_ASSERT(gattNotify2Characteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)),
this, SLOT(characteristicChanged(QLowEnergyCharacteristic,QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)),
this, SLOT(characteristicWritten(const QLowEnergyCharacteristic, const QByteArray)));
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)),
this, SLOT(errorService(QLowEnergyService::ServiceError)));
connect(gattCommunicationChannelService, SIGNAL(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)), this,
SLOT(descriptorWritten(const QLowEnergyDescriptor, const QByteArray)));
// ******************************************* virtual bike init *************************************
static uint8_t first = 0;
if(!first)
{
debug("creating virtual bike interface...");
virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
}
first = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
gattCommunicationChannelService->writeDescriptor(gattNotify2Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void echelonconnectsport::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue)
{
debug("descriptorWritten " + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
}
void echelonconnectsport::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue)
{
Q_UNUSED(characteristic);
debug("characteristicWritten " + newValue.toHex(' '));
}
void echelonconnectsport::serviceScanDone(void)
{
debug("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((QString)"0bf669f1-45f2-11e7-9598-0800200c9a66");
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(stateChanged(QLowEnergyService::ServiceState)));
gattCommunicationChannelService->discoverDetails();
}
void echelonconnectsport::errorService(QLowEnergyService::ServiceError err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
debug("echelonconnectsport::errorService" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
}
void echelonconnectsport::error(QLowEnergyController::Error err)
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
debug("echelonconnectsport::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString());
m_control->disconnect();
}
void echelonconnectsport::deviceDiscovered(const QBluetoothDeviceInfo &device)
{
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("ECH-SPORT"))
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
connect(m_control, SIGNAL(discoveryFinished()),
this, SLOT(serviceScanDone()));
connect(m_control, SIGNAL(error(QLowEnergyController::Error)),
this, SLOT(error(QLowEnergyController::Error)));
connect(m_control, static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
debug("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
debug("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
debug("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool echelonconnectsport::connected()
{
if(!m_control)
return false;
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void* echelonconnectsport::VirtualBike()
{
return virtualBike;
}
void* echelonconnectsport::VirtualDevice()
{
return VirtualBike();
}

86
src/echelonconnectsport.h Normal file
View File

@@ -0,0 +1,86 @@
#ifndef ECHELONCONNECTSPORT_H
#define ECHELONCONNECTSPORT_H
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QtCore/qmutex.h>
#include <QObject>
#include <QString>
#include "virtualbike.h"
#include "bike.h"
class echelonconnectsport : public bike
{
Q_OBJECT
public:
echelonconnectsport(bool noWriteResistance, bool noHeartService);
bool connected();
void* VirtualBike();
void* VirtualDevice();
private:
double GetDistanceFromPacket(QByteArray packet);
QTime GetElapsedFromPacket(QByteArray packet);
void btinit();
void writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log=false, bool wait_for_response = false);
void startDiscover();
void sendPoll();
QTimer* refresh;
virtualbike* virtualBike = 0;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // ECHELONCONNECTSPORT_H

View File

@@ -15,26 +15,53 @@ DataObject::DataObject(QString name, QString icon, QString value, bool writable,
}
void DataObject::setValue(QString v) {m_value = v; emit valueChanged(m_value);}
void DataObject::setVisible(bool visible) {m_visible = visible; emit visibleChanged(m_visible);}
homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
{
this->bluetoothManager = bl;
this->engine = engine;
connect(bluetoothManager, SIGNAL(deviceFound(QString)), this, SLOT(deviceFound(QString)));
connect(bluetoothManager, SIGNAL(deviceConnected()), this, SLOT(deviceConnected()));
engine->rootContext()->setContextProperty("rootItem", (QObject *)this);
dataList = {
speed,
inclination,
cadence,
elevation,
calories,
odometer,
pace,
resistance,
watt,
heart,
fan
};
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &homeform::update);
timer->start(1000);
}
void homeform::deviceConnected()
{
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
dataList = {
speed,
inclination,
elevation,
calories,
odometer,
pace,
watt,
heart,
fan
};
}
else if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
{
dataList = {
speed,
cadence,
elevation,
calories,
odometer,
resistance,
watt,
heart,
fan
};
}
engine->rootContext()->setContextProperty("appModel", QVariant::fromValue(dataList));
@@ -48,14 +75,11 @@ homeform::homeform(QQmlApplicationEngine* engine, bluetooth* bl)
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)
{
if(!name.trimmed().length()) return;
m_info = name + " founded";
emit infoChanged(m_info);
}
@@ -159,6 +183,23 @@ void homeform::Stop()
bluetoothManager->device()->stop();
}
QString homeform::signal()
{
if(!bluetoothManager)
return "icons/icons/signal-1.png";
if(!bluetoothManager->device())
return "icons/icons/signal-1.png";
int16_t rssi = bluetoothManager->device()->bluetoothDevice.rssi();
if(rssi > -40)
return "icons/icons/signal-3.png";
else if(rssi > -60)
return "icons/icons/signal-2.png";
return "icons/icons/signal-1.png";
}
void homeform::update()
{
if(bluetoothManager->device())
@@ -168,6 +209,8 @@ void homeform::update()
double watts = 0;
double pace = 0;
emit signalChanged(signal());
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));
@@ -176,8 +219,15 @@ void homeform::update()
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
if(pace < 0) pace = 0;
if(bluetoothManager->device()->currentSpeed())
{
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
if(pace < 0) pace = 0;
}
else
{
pace = 0;
}
watts = ((treadmill*)bluetoothManager->device())->watts(/*weight->text().toFloat()*/); // TODO: add weight to settings
inclination = ((treadmill*)bluetoothManager->device())->currentInclination();
this->pace->setValue(((treadmill*)bluetoothManager->device())->currentPace().toString("m:ss"));
@@ -191,6 +241,7 @@ void homeform::update()
watts = ((bike*)bluetoothManager->device())->watts();
watt->setValue(QString::number(watts));
this->resistance->setValue(QString::number(resistance));
this->cadence->setValue(QString::number(((bike*)bluetoothManager->device())->currentCadence()));
}
/*
if(trainProgram)

View File

@@ -14,16 +14,19 @@ class DataObject : public QObject
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(bool writable READ writable NOTIFY writableChanged)
Q_PROPERTY(bool visibleItem READ visibleItem NOTIFY visibleChanged)
Q_PROPERTY(QString plusName READ plusName NOTIFY plusNameChanged)
Q_PROPERTY(QString minusName READ minusName NOTIFY minusNameChanged)
public:
DataObject(QString name, QString icon, QString value, bool writable, QString id);
void setValue(QString value);
void setVisible(bool visible);
QString name() {return m_name;}
QString icon() {return m_icon;}
QString value() {return m_value;}
bool writable() {return m_writable;}
bool visibleItem() {return m_visible;}
QString plusName() {return m_id + "_plus";}
QString minusName() {return m_id + "_minus";}
@@ -32,12 +35,14 @@ public:
QString m_icon;
QString m_value;
bool m_writable;
bool m_visible = true;
signals:
void valueChanged(QString value);
void nameChanged(QString value);
void iconChanged(QString value);
void writableChanged(bool value);
void visibleChanged(bool value);
void plusNameChanged(QString value);
void minusNameChanged(QString value);
};
@@ -48,15 +53,18 @@ class homeform: public QObject
Q_PROPERTY( bool device READ getDevice NOTIFY changeOfdevice)
Q_PROPERTY( bool zwift READ getZwift NOTIFY changeOfzwift)
Q_PROPERTY(QString info READ info NOTIFY infoChanged)
Q_PROPERTY(QString signal READ signal NOTIFY signalChanged)
public:
homeform(QQmlApplicationEngine* engine, bluetooth* bl);
QString info() {return m_info;}
QString signal();
private:
QList<QObject *> dataList;
QList<SessionLine> Session;
bluetooth* bluetoothManager;
bluetooth* bluetoothManager = 0;
QQmlApplicationEngine* engine;
QString m_info = "Connecting...";
@@ -84,10 +92,12 @@ private slots:
void Minus(QString);
void Plus(QString);
void deviceFound(QString name);
void deviceConnected();
signals:
void changeOfdevice();
void changeOfzwift();
void signalChanged(QString value);
void infoChanged(QString value);
};

View File

@@ -18,5 +18,9 @@
<file>icons/pace.png</file>
<file>icons/chart.png</file>
<file>icons/icon.png</file>
<file>icons/signal-0.png</file>
<file>icons/signal-1.png</file>
<file>icons/signal-2.png</file>
<file>icons/signal-3.png</file>
</qresource>
</RCC>

BIN
src/icons/signal-0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

BIN
src/icons/signal-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

BIN
src/icons/signal-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

BIN
src/icons/signal-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

View File

@@ -2,9 +2,11 @@
#include <QStyleFactory>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // getuid
#include <QStandardPaths>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtAndroid>
#include "virtualtreadmill.h"
#include "domyostreadmill.h"
#include "bluetooth.h"
@@ -143,18 +145,35 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
int main(int argc, char *argv[])
{
#ifdef Q_OS_LINUX
#ifndef Q_OS_ANDROID
if (getuid())
{
printf("Runme as root!\n");
return -1;
}
else printf("%s", "OK, you are root.\n");
#endif
#endif
#ifndef Q_OS_ANDROID
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
qInstallMessageHandler(myMessageOutput);
#endif
qInstallMessageHandler(myMessageOutput);
qDebug() << "version 1.0.1";
#ifndef Q_OS_ANDROID
if(onlyVirtualBike)
{
virtualbike* V = new virtualbike(new bike(), noWriteResistance, noHeartService);
Q_UNUSED(V)
return app->exec();
}
else if(onlyVirtualTreadmill)
{
virtualtreadmill* V = new virtualtreadmill(new treadmill(), noHeartService);
Q_UNUSED(V)
return app->exec();
}
#endif
@@ -170,6 +189,14 @@ int main(int argc, char *argv[])
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
auto result = QtAndroid::checkPermission(QString("android.permission.WRITE_EXTERNAL_STORAGE"));
if(result == QtAndroid::PermissionResult::Denied){
QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList({"android.permission.WRITE_EXTERNAL_STORAGE"}));
if(resultHash["android.permission.STORAGE"] == QtAndroid::PermissionResult::Denied)
qDebug() << "log unwritable!";
}
engine.load(url);
new homeform(&engine, bl);

View File

@@ -65,8 +65,15 @@ void MainWindow::update()
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
{
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
if(pace < 0) pace = 0;
if(bluetoothManager->device()->currentSpeed())
{
pace = 10000 / (((treadmill*)bluetoothManager->device())->currentPace().second() + (((treadmill*)bluetoothManager->device())->currentPace().minute() * 60));
if(pace < 0) pace = 0;
}
else
{
pace = 0;
}
watts = ((treadmill*)bluetoothManager->device())->watts(ui->weight->text().toFloat());
inclination = ((treadmill*)bluetoothManager->device())->currentInclination();
ui->pace->setText(((treadmill*)bluetoothManager->device())->currentPace().toString("m:ss"));
@@ -80,6 +87,7 @@ void MainWindow::update()
watts = ((bike*)bluetoothManager->device())->watts();
ui->watt->setText(QString::number(watts));
ui->resistance->setText(QString::number(resistance));
ui->cadence->setText(QString::number(((bike*)bluetoothManager->device())->currentCadence()));
}
if(trainProgram)

View File

@@ -20,10 +20,12 @@ SOURCES += \
bluetoothdevice.cpp \
charts.cpp \
domyostreadmill.cpp \
echelonconnectsport.cpp \
gpx.cpp \
homeform.cpp \
main.cpp \
sessionline.cpp \
signalhandler.cpp \
toorxtreadmill.cpp \
treadmill.cpp \
mainwindow.cpp \
@@ -44,8 +46,10 @@ HEADERS += \
bluetoothdevice.h \
charts.h \
domyostreadmill.h \
echelonconnectsport.h \
homeform.h \
sessionline.h \
signalhandler.h \
toorxtreadmill.h \
gpx.h \
treadmill.h \

169
src/signalhandler.cpp Normal file
View File

@@ -0,0 +1,169 @@
#include "signalhandler.h"
#include <assert.h>
#include <cstddef>
#if 1
#include <signal.h>
#else
#include <windows.h>
#endif //!__MINGW32_MAJOR_VERSION
// There can be only ONE SignalHandler per process
SignalHandler* g_handler(NULL);
#if 0
BOOL WINAPI WIN32_handleFunc(DWORD);
int WIN32_physicalToLogical(DWORD);
DWORD WIN32_logicalToPhysical(int);
std::set<int> g_registry;
#else //__MINGW32_MAJOR_VERSION
void POSIX_handleFunc(int);
int POSIX_physicalToLogical(int);
int POSIX_logicalToPhysical(int);
#endif //__MINGW32_MAJOR_VERSION
SignalHandler::SignalHandler(int mask) : _mask(mask)
{
assert(g_handler == NULL);
g_handler = this;
#if 0
SetConsoleCtrlHandler(WIN32_handleFunc, TRUE);
#endif //__MINGW32_MAJOR_VERSION
for (int i=0;i<numSignals;i++)
{
int logical = 0x1 << i;
if (_mask & logical)
{
#if 0
g_registry.insert(logical);
#else
int sig = POSIX_logicalToPhysical(logical);
bool failed = signal(sig, POSIX_handleFunc) == SIG_ERR;
assert(!failed);
(void)failed; // Silence the warning in non _DEBUG; TODO: something better
#endif //__MINGW32_MAJOR_VERSION
}
}
}
SignalHandler::~SignalHandler()
{
#if 0
SetConsoleCtrlHandler(WIN32_handleFunc, FALSE);
#else
for (int i=0;i<numSignals;i++)
{
int logical = 0x1 << i;
if (_mask & logical)
{
signal(POSIX_logicalToPhysical(logical), SIG_DFL);
}
}
#endif //__MINGW32_MAJOR_VERSION
}
#if 0
DWORD WIN32_logicalToPhysical(int signal)
{
switch (signal)
{
case SignalHandler::SIG_INT: return CTRL_C_EVENT;
case SignalHandler::SIG_TERM: return CTRL_BREAK_EVENT;
case SignalHandler::SIG_CLOSE: return CTRL_CLOSE_EVENT;
default:
return ~(unsigned int)0; // SIG_ERR = -1
}
}
#else
int POSIX_logicalToPhysical(int signal)
{
switch (signal)
{
case SignalHandler::SIG_INT: return SIGINT;
case SignalHandler::SIG_TERM: return SIGTERM;
// In case the client asks for a SIG_CLOSE handler, accept and
// bind it to a SIGTERM. Anyway the signal will never be raised
case SignalHandler::SIG_CLOSE: return SIGTERM;
//case SignalHandler::SIG_RELOAD: return SIGHUP;
default:
return -1; // SIG_ERR = -1
}
}
#endif //__MINGW32_MAJOR_VERSION
#if 0
int WIN32_physicalToLogical(DWORD signal)
{
switch (signal)
{
case CTRL_C_EVENT: return SignalHandler::SIG_INT;
case CTRL_BREAK_EVENT: return SignalHandler::SIG_TERM;
case CTRL_CLOSE_EVENT: return SignalHandler::SIG_CLOSE;
default:
return SignalHandler::SIG_UNHANDLED;
}
}
#else
int POSIX_physicalToLogical(int signal)
{
switch (signal)
{
case SIGINT: return SignalHandler::SIG_INT;
case SIGTERM: return SignalHandler::SIG_TERM;
//case SIGHUP: return SignalHandler::SIG_RELOAD;
default:
return SignalHandler::SIG_UNHANDLED;
}
}
#endif //__MINGW32_MAJOR_VERSION
#if 0
BOOL WINAPI WIN32_handleFunc(DWORD signal)
{
if (g_handler)
{
int signo = WIN32_physicalToLogical(signal);
// The std::set is thread-safe in const reading access and we never
// write to it after the program has started so we don't need to
// protect this search by a mutex
std::set<int>::const_iterator found = g_registry.find(signo);
if (signo != -1 && found != g_registry.end())
{
return g_handler->handleSignal(signo) ? TRUE : FALSE;
}
else
{
return FALSE;
}
}
else
{
return FALSE;
}
}
#else
void POSIX_handleFunc(int signal)
{
if (g_handler)
{
int signo = POSIX_physicalToLogical(signal);
g_handler->handleSignal(signo);
}
}
#endif //__MINGW32_MAJOR_VERSION

29
src/signalhandler.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef SIGNALHANDLER_H
#define SIGNALHANDLER_H
class SignalHandler
{
public:
SignalHandler(int mask = DEFAULT_SIGNALS);
virtual ~SignalHandler();
enum SIGNALS
{
SIG_UNHANDLED = 0, // Physical signal not supported by this class
SIG_NOOP = 1, // The application is requested to do a no-op (only a target that platform-specific signals map to when they can't be raised anyway)
SIG_INT = 2, // Control+C (should terminate but consider that it's a normal way to do so; can delay a bit)
SIG_TERM = 4, // Control+Break (should terminate now without regarding the consquences)
SIG_CLOSE = 8, // Container window closed (should perform normal termination, like Ctrl^C) [Windows only; on Linux it maps to SIG_TERM]
SIG_RELOAD = 16, // Reload the configuration [Linux only, physical signal is SIGHUP; on Windows it maps to SIG_NOOP]
DEFAULT_SIGNALS = SIG_INT | SIG_TERM | SIG_CLOSE,
};
static const int numSignals = 6;
virtual bool handleSignal(int signal) = 0;
private:
int _mask;
};
#endif // SIGNALHANDLER_H

View File

@@ -15,7 +15,7 @@ void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("TRX ROUTE KEY"))
{
bttreadmill = device;
bluetoothDevice = device;
// Create a discovery agent and connect to its signals
discoveryAgent = new QBluetoothServiceDiscoveryAgent(this);
@@ -31,7 +31,7 @@ void toorxtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
// In your local slot, read information about the found devices
void toorxtreadmill::serviceDiscovered(const QBluetoothServiceInfo &service)
{
if(service.device().address() == bttreadmill.address())
if(service.device().address() == bluetoothDevice.address())
{
debug("Found new service: " + service.serviceName()
+ '(' + service.serviceUuid().toString() + ')');

View File

@@ -46,7 +46,6 @@ private slots:
void update();
private:
QBluetoothDeviceInfo bttreadmill;
QBluetoothServiceDiscoveryAgent *discoveryAgent;
QBluetoothServiceInfo serialPortService;
QBluetoothSocket *socket = nullptr;

View File

@@ -58,7 +58,7 @@ void trxappgateusbtreadmill::update()
initRequest = false;
btinit(false);
}
else if(bttreadmill.isValid() &&
else if(bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
@@ -342,8 +342,8 @@ void trxappgateusbtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device
debug("Found new device: " + device.name() + " (" + device.address().toString() + ')');
if(device.name().startsWith("TOORX"))
{
bttreadmill = device;
m_control = QLowEnergyController::createCentral(bttreadmill, this);
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, SIGNAL(serviceDiscovered(const QBluetoothUuid &)),
this, SLOT(serviceDiscovered(const QBluetoothUuid &)));
connect(m_control, SIGNAL(discoveryFinished()),

View File

@@ -55,7 +55,6 @@ private:
QTimer* refresh;
virtualtreadmill* virtualTreadMill = 0;
QBluetoothDeviceInfo bttreadmill;
QLowEnergyController* m_control = 0;
QLowEnergyService* gattCommunicationChannelService = 0;
QLowEnergyCharacteristic gattWriteCharacteristic;