Compare commits

...

7 Commits

Author SHA1 Message Date
Roberto Viola
48f53dfb68 Update Daum.h 2023-01-30 10:11:33 +01:00
Roberto Viola
ec5b32f7a2 Merge branch 'master' into daumbike 2023-01-30 10:10:02 +01:00
Roberto Viola
72277ea0b6 fixing build for android and linux 2023-01-17 15:40:18 +01:00
Roberto Viola
c0df5d284a fixing build for ios and android 2023-01-17 14:33:28 +01:00
Roberto Viola
0dcfd24052 build fix 2023-01-17 14:23:03 +01:00
Roberto Viola
3d710b1e9d first implementation 2023-01-17 14:13:26 +01:00
Roberto Viola
cae92b5546 importing modules 2023-01-16 16:58:55 +01:00
11 changed files with 1228 additions and 14 deletions

View File

@@ -236,7 +236,7 @@ jobs:
ref: "release-1.12.1"
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev libqt5serialport5-dev
- name: Install Qt
uses: jurplel/install-qt-action@v2

502
src/Daum.cpp Normal file
View File

@@ -0,0 +1,502 @@
/*
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com),
* 2018 Florian Nairz (nairz.florian@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Daum.h"
#include <QDate>
#include <QRegularExpression>
#include <QTime>
Daum::Daum(QObject *parent, QString device, QString profile)
: QThread(parent), timer_(nullptr), serialDeviceName_(device), serial_dev_(nullptr), deviceAddress_(-1),
maxDeviceLoad_(800), serialWriteDelay_(0), playSound_(profile.contains("sound", Qt::CaseInsensitive)),
paused_(false), devicePower_(0), deviceHeartRate_(0), deviceCadence_(0), deviceSpeed_(0), load_(kDefaultLoad),
loadToWrite_(kDefaultLoad), forceUpdate_(profile.contains("force", Qt::CaseInsensitive)), profile_(profile) {}
int Daum::start() {
QThread::start();
return isRunning() ? 0 : 1;
}
int Daum::restart() {
QMutexLocker locker(&pvars);
paused_ = true;
return 0;
}
int Daum::pause() {
QMutexLocker locker(&pvars);
paused_ = true;
return 0;
}
int Daum::stop() {
exit(-1);
return 0;
}
bool Daum::discover(QString dev) {
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
if (!openPort(dev)) {
return false;
}
QSerialPort &s(*serial_dev_);
QByteArray data;
data.append(0x11);
s.write(data);
if (!s.waitForBytesWritten(1000)) {
return false;
}
if (!s.waitForReadyRead(1000)) {
return false;
}
data = s.read(2);
if ((int)data[0] != 0x11) {
return false;
}
data = s.readAll();
closePort();
#endif
return true;
}
void Daum::setLoad(double load) {
const unsigned int minDeviceLoad = 25;
unsigned int local_load = (unsigned int)load;
QMutexLocker locker(&pvars);
if (local_load > maxDeviceLoad_) {
local_load = maxDeviceLoad_;
}
if (local_load < minDeviceLoad) {
local_load = minDeviceLoad;
}
qDebug() << "setLoad(): " << local_load;
loadToWrite_ = local_load;
}
double Daum::getPower() const {
QMutexLocker locker(&pvars);
return devicePower_;
}
double Daum::getSpeed() const {
QMutexLocker locker(&pvars);
return deviceSpeed_;
}
double Daum::getCadence() const {
QMutexLocker locker(&pvars);
return deviceCadence_;
}
double Daum::getHeartRate() const {
QMutexLocker locker(&pvars);
return deviceHeartRate_;
}
bool Daum::openPort(QString dev) {
QMutexLocker locker(&pvars);
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
if (serial_dev_ == nullptr) {
serial_dev_ = new QSerialPort();
}
if (serial_dev_->isOpen()) {
serial_dev_->close();
}
serial_dev_->setPortName(dev);
serial_dev_->setBaudRate(QSerialPort::Baud9600);
serial_dev_->setStopBits(QSerialPort::OneStop);
serial_dev_->setDataBits(QSerialPort::Data8);
serial_dev_->setFlowControl(QSerialPort::NoFlowControl);
serial_dev_->setParity(QSerialPort::NoParity);
if (!serial_dev_->open(QSerialPort::ReadWrite)) {
return false;
}
#endif
return true;
}
bool Daum::closePort() {
QMutexLocker locker(&pvars);
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
delete serial_dev_;
serial_dev_ = nullptr;
#endif
return true;
}
void Daum::run() {
// closePort();
if (!openPort(serialDeviceName_)) {
exit(-1);
}
{
QMutexLocker locker(&pvars);
if (timer_ == nullptr) {
timer_ = new QTimer();
connect(this, SIGNAL(finished()), timer_, SLOT(stop()), Qt::DirectConnection);
connect(timer_, SIGNAL(timeout()), this, SLOT(requestRealtimeData()), Qt::DirectConnection);
}
// discard prev. read data
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
serial_dev_->readAll();
#endif
}
initializeConnection();
// setup polling
{
QMutexLocker locker(&pvars);
timer_->setInterval(kQueryIntervalMS);
timer_->start();
}
StartProgram(0);
// enter event loop and wait for a call to quit() or exit()
exec();
{
QMutexLocker locker(&pvars);
timer_->stop();
}
}
void Daum::initializeConnection() {
serialWriteDelay_ = 0;
char addr = (char)GetAddress();
{
QMutexLocker locker(&pvars);
deviceAddress_ = addr;
}
if (addr < 0) {
qWarning() << "unable to detect device address";
exit(-1);
}
QThread::msleep(100);
// reset device
if (!ResetDevice()) {
qWarning() << "reset device failed";
}
QThread::msleep(100);
// unused so far
qDebug() << "CheckCockpit() returned " << CheckCockpit();
QThread::msleep(100);
// check version info for know devices
int dat = GetDeviceVersion();
if (configureForCockpitType(dat)) {
qDebug() << "Daum cockpit type: " << Qt::hex << dat;
qDebug() << " Using communication delay:" << serialWriteDelay_ << "msec";
qDebug() << " Playing sound:" << playSound_;
} else {
qWarning() << "unable to identify daum cockpit type" << Qt::hex << dat;
exit(-1);
}
if (!SetDate()) {
qWarning() << "set date failed";
}
if (!SetTime()) {
qWarning() << "set time failed";
}
if (!SetProgram(0)) {
qWarning() << "setting program failed";
}
if (!StartProgram(0)) {
qWarning() << "starting program failed";
}
PlaySound();
}
bool Daum::configureForCockpitType(int cockpitType) {
serialWriteDelay_ = 0;
if (configureFromProfile()) {
// the profile string contains a valid configuration
return true;
}
switch (cockpitType) {
case COCKPIT_CARDIO:
case COCKPIT_FITNESS:
case COCKPIT_VITA_DE_LUXE:
case COCKPIT_UNKNOWN:
case COCKPIT_THERAPIE:
return true;
case COCKPIT_8008:
case COCKPIT_8080:
case COCKPIT_8008_TRS:
case COCKPIT_8008_TRS_PRO:
serialWriteDelay_ = 50;
playSound_ = true;
return true;
}
return false;
}
bool Daum::configureFromProfile() {
QRegularExpression re("(\\d+)");
QRegularExpressionMatch match = re.match(profile_);
bool ok = false;
if (!match.hasMatch() || match.lastCapturedIndex() == 0) {
// no delay found in the profile
// if the string sound was found, use delay 0 and enable sound
return playSound_;
}
uint delay = match.captured(1).toUInt(&ok);
if (ok) {
// use the matched number found as the command delay
serialWriteDelay_ = delay;
}
return true;
}
void Daum::requestRealtimeData() {
char addr = -1;
QByteArray data;
// Discard any existing data
{
QMutexLocker locker(&pvars);
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
serial_dev_->readAll();
#endif
addr = deviceAddress_;
}
qDebug() << "querying device info";
data.clear();
data.append((char)0x40);
data.append(addr);
data = WriteDataAndGetAnswer(data, 19);
if (data.length() < 19) {
return;
}
// local cache of telemetry data
int pwr = (unsigned char)data[5];
int rpm = (unsigned char)data[6];
int speed = data[7];
int pulse = (unsigned char)data[14];
// sanity check
if (pwr >= 5 && pwr <= 160) {
int pedalling = data[4]; // either 0/1 or w/ offset of 128
pwr = pwr * 5 * (pedalling != 0 ? 1 : 0);
} else {
pwr = 0;
}
if (rpm < 0 || rpm > 199) {
rpm = 0;
}
if (speed < 0 || speed > 99) {
speed = 0;
}
if (pulse < 0 || pulse > 199) {
pulse = 0;
}
// assign
{
QMutexLocker locker(&pvars);
devicePower_ = pwr;
deviceCadence_ = rpm;
deviceSpeed_ = speed;
deviceHeartRate_ = pulse;
}
// write load to device
bool p = isPaused();
unsigned int load = kDefaultLoad;
{
QMutexLocker locker(&pvars);
load = load_;
pwr = loadToWrite_;
}
if (!p && (forceUpdate_ || load != pwr)) {
data.clear();
data.append((char)0x51);
data.append(deviceAddress_);
data.append(MapLoadToByte(pwr));
qInfo() << "Writing power to device: " << pwr << "W";
QByteArray res = WriteDataAndGetAnswer(data, 3);
qDebug() << "set power to " << (int)data[2] * 5 << "W";
if (res != data && res[2] != data[2] && pwr > 400) {
// reduce power limit because some devices are limited too 400W instead of 800W
QMutexLocker locker(&pvars);
maxDeviceLoad_ = 400;
}
// update class cache
{
QMutexLocker locker(&pvars);
load_ = pwr;
}
}
}
bool Daum::ResetDevice() {
QByteArray dat;
dat.append((char)0x12).append(deviceAddress_);
return WriteDataAndGetAnswer(dat, 3).length() == 2; // device tells pedalling state too
}
int Daum::GetAddress() {
QByteArray dat;
dat.append((char)0x11);
dat = WriteDataAndGetAnswer(dat, 2);
if (dat.length() == 2 && (int)dat[0] == 0x11) {
return (int)dat[1];
}
return -1;
}
int Daum::CheckCockpit() {
QByteArray dat;
dat.append((char)0x10);
dat.append(deviceAddress_);
dat = WriteDataAndGetAnswer(dat, 3);
if (dat.length() == 3 && (int)dat[0] == 0x10 && (char)dat[1] == deviceAddress_) {
return (int)dat[2];
}
return -1;
}
int Daum::GetDeviceVersion() {
QByteArray dat;
dat.append((char)0x73);
dat.append(deviceAddress_);
dat = WriteDataAndGetAnswer(dat, 11);
if (dat.length() == 11 && (int)dat[0] == 0x73 && (char)dat[1] == deviceAddress_) {
return (int)dat[10];
}
return -1;
}
bool Daum::SetProgram(unsigned int prog) {
QByteArray dat;
if (prog > 79) {
prog = 79;
} // clamp to max
dat.append((char)0x23).append(deviceAddress_).append((char)prog);
return WriteDataAndGetAnswer(dat, dat.length() + 1).length() == 4; // device tells pedalling state too
}
bool Daum::StartProgram(unsigned int prog) {
Q_UNUSED(prog);
QByteArray dat;
dat.append((char)0x21).append(deviceAddress_);
return WriteDataAndGetAnswer(dat, dat.length() + 1).length() == 3; // device tells pedalling state too
}
bool Daum::StopProgram(unsigned int prog) {
Q_UNUSED(prog);
QByteArray dat;
dat.append((char)0x22).append(deviceAddress_);
return WriteDataAndGetAnswer(dat, dat.length() + 1).length() == 3; // device tells pedalling state too
}
bool Daum::SetDate() {
QDate d = QDate::currentDate();
QByteArray dat;
char year = d.year() - 2000;
if (year < 0 || year > 99) {
year = 0;
}
dat.append((char)0x64).append(deviceAddress_).append((char)d.day()).append((char)d.month()).append(year);
return WriteDataAndGetAnswer(dat, 2).length() == 2;
}
bool Daum::SetTime() {
QTime tim = QTime::currentTime();
QByteArray dat;
dat.append((char)0x62)
.append(deviceAddress_)
.append((char)tim.second())
.append((char)tim.minute())
.append((char)tim.hour());
return WriteDataAndGetAnswer(dat, 2).length() == 2;
}
void Daum::PlaySound() {
if (playSound_) {
// might be buggy in device
QByteArray dat;
dat.append((char)0xd3).append((char)0x27).append((char)0x10);
WriteDataAndGetAnswer(dat, 2);
}
}
char Daum::MapLoadToByte(unsigned int load) const {
char load_map = load / 5;
return load_map;
}
bool Daum::isPaused() const {
QMutexLocker locker(&pvars);
return paused_;
}
QByteArray Daum::WriteDataAndGetAnswer(QByteArray const &dat, int response_bytes) {
QMutexLocker locker(&pvars);
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QSerialPort &s(*serial_dev_);
QByteArray ret;
if (!s.isOpen()) {
return ret;
}
QThread::msleep(serialWriteDelay_);
s.write(dat);
if (!s.waitForBytesWritten(1000)) {
qWarning() << "failed to write data to daum cockpit";
exit(-1);
}
if (response_bytes > 0) {
int retries = 20;
do {
if (!s.waitForReadyRead(1000)) {
return ret;
}
ret.append(s.read(response_bytes - ret.length()));
} while (--retries > 0 && ret.length() < response_bytes);
if (retries <= 0) {
qWarning() << "failed to read desired (" << response_bytes << ") data from device. Read: " << ret.length();
ret.clear();
}
}
s.readAll(); // discard additional data
#endif
return ret;
}

133
src/Daum.h Normal file
View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com),
* 2018 Florian Nairz (nairz.florian@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _GC_Daum_h
#define _GC_Daum_h 1
#include <QDebug>
#include <QMutex>
#include <QString>
#include <QThread>
#include <QTimer>
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
#include <QSerialPort>
#endif
/*
* This class add support for some Daum devices that are connected
* to the PC via serial port.
* This work is based on the documents found on the daum-electronic site:
* http://www.daum-electronic.de/de/support/supp02.html
* and the implementation details are inspired by the Fortius class.
*/
class Daum : public QThread {
Q_OBJECT
public:
// default load
const int kDefaultLoad = 100;
const int kQueryIntervalMS = 1000;
Daum(QObject *parent, QString device, QString profile);
int start();
int restart();
int pause();
int stop();
int quit();
bool discover(QString dev);
void setLoad(double load);
double getPower() const;
double getSpeed() const;
double getCadence() const;
double getHeartRate() const;
private:
void run();
bool openPort(QString dev);
bool closePort();
void initializeConnection();
bool configureForCockpitType(int cockpitType);
bool configureFromProfile();
bool ResetDevice();
bool StartProgram(unsigned int prog);
bool StopProgram(unsigned int prog);
int GetAddress();
int CheckCockpit();
int GetDeviceVersion();
bool SetProgram(unsigned int prog);
bool SetDate();
bool SetTime();
void PlaySound();
QByteArray WriteDataAndGetAnswer(QByteArray const &dat, int response_bytes);
char MapLoadToByte(unsigned int load) const;
bool isPaused() const;
// a lock for our private vars
mutable QMutex pvars;
QTimer *timer_;
QString serialDeviceName_;
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC) || defined(Q_OS_LINUX)
QSerialPort *serial_dev_;
#else
QObject *serial_dev_;
#endif
char deviceAddress_;
unsigned int maxDeviceLoad_;
unsigned int serialWriteDelay_;
bool playSound_;
// state
bool paused_;
// inbound
volatile int devicePower_;
volatile double deviceHeartRate_;
volatile double deviceCadence_;
volatile double deviceSpeed_;
// outbound
volatile int load_, loadToWrite_;
const bool forceUpdate_;
const QString profile_;
enum CockpitType {
COCKPIT_CARDIO = 10,
COCKPIT_FITNESS = 20,
COCKPIT_8008_TRS = 0x2a,
COCKPIT_VITA_DE_LUXE = 30,
COCKPIT_8008 = 40,
COCKPIT_8080 = 50,
COCKPIT_UNKNOWN = 55,
COCKPIT_THERAPIE = 60,
COCKPIT_8008_TRS_PRO = 64
};
private slots:
void requestRealtimeData();
};
#endif // _GC_Daum_h

View File

@@ -437,6 +437,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
QString computrainerSerialPort =
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
QString daumBikeSerialPort =
settings.value(QZSettings::daumbike_serialport, QZSettings::default_daumbike_serialport).toString();
bool manufacturerDeviceFound = false;
bool ss2k_peloton = settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool();
bool pafers_treadmill_bh_iboxster_plus =
@@ -628,6 +630,21 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->startTemplateManagers(proformWifiBike);
#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
} else if (!daumBikeSerialPort.isEmpty() && !daumBike) {
this->stopDiscovery();
daumBike = new daumbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
emit deviceConnected(b);
connect(daumBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(daumBike, &daumbike::debug, this, &bluetooth::debug);
daumBike->deviceDiscovered(b);
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->startTemplateManagers(daumBike);
#endif
#ifndef Q_OS_IOS
} else if (!computrainerSerialPort.isEmpty() && !computrainerBike) {
this->stopDiscovery();
@@ -1090,7 +1107,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("S77")) ||
b.name().toUpper().startsWith(QStringLiteral("T318_")) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("DK")) && b.name().length() >= 11) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("T218_")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("T218_")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TRX3500")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("JFTMPARAGON")) ||
b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) || // FTMS
@@ -2544,6 +2561,13 @@ void bluetooth::restart() {
delete inspireBike;
inspireBike = nullptr;
}
#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
if (daumBike) {
delete daumBike;
daumBike = nullptr;
}
#endif
#ifndef Q_OS_IOS
if (computrainerBike) {
@@ -2807,6 +2831,10 @@ bluetoothdevice *bluetooth::device() {
return fitPlusBike;
} else if (skandikaWiriBike) {
return skandikaWiriBike;
#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
} else if (daumBike) {
return daumBike;
#endif
#ifndef Q_OS_IOS
} else if (computrainerBike) {
return computrainerBike;

View File

@@ -17,8 +17,8 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qloggingcategory.h>
#include "qzsettings.h"
#include "discoveryoptions.h"
#include "qzsettings.h"
#include "activiotreadmill.h"
#include "bhfitnesselliptical.h"
@@ -31,6 +31,9 @@
#endif
#include "concept2skierg.h"
#include "cscbike.h"
#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
#include "daumbike.h"
#endif
#include "domyosbike.h"
#include "domyoselliptical.h"
#include "domyosrower.h"
@@ -124,7 +127,7 @@ class bluetooth : public QObject, public SignalHandler {
explicit bluetooth(bool logs, const QString &deviceName = QLatin1String(""), bool noWriteResistance = false,
bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false,
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
bool createTemplateManagers=true, bool startDiscovery=true);
bool createTemplateManagers = true, bool startDiscovery = true);
~bluetooth();
bluetoothdevice *device();
bluetoothdevice *externalInclination() { return eliteRizer; }
@@ -134,10 +137,9 @@ class bluetooth : public QObject, public SignalHandler {
TemplateInfoSenderBuilder *getUserTemplateManager() const { return userTemplateManager; }
TemplateInfoSenderBuilder *getInnerTemplateManager() const { return innerTemplateManager; }
private:
private:
bool useDiscovery = false;
bool createTemplateManagers =false;
bool createTemplateManagers = false;
TemplateInfoSenderBuilder *userTemplateManager = nullptr;
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
QFile *debugCommsLog = nullptr;
@@ -148,6 +150,9 @@ private:
fitshowtreadmill *fitshowTreadmill = nullptr;
#ifndef Q_OS_IOS
computrainerbike *computrainerBike = nullptr;
#endif
#if !defined(Q_OS_IOS) && !defined(Q_OS_ANDROID)
daumbike *daumBike = nullptr;
#endif
concept2skierg *concept2Skierg = nullptr;
domyostreadmill *domyos = nullptr;
@@ -277,7 +282,7 @@ private:
void setLastBluetoothDevice(const QBluetoothDeviceInfo &b);
void startTemplateManagers(bluetoothdevice *b);
void stopTemplateManagers();
signals:
signals:
void deviceConnected(QBluetoothDeviceInfo b);
void deviceFound(QString name);
void searchingStop();
@@ -286,7 +291,7 @@ signals:
public slots:
void restart();
void debug(const QString &string);
void heartRate(uint8_t heart);
void heartRate(uint8_t heart);
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))

403
src/daumbike.cpp Normal file
View File

@@ -0,0 +1,403 @@
#include "daumbike.h"
#include "ios/lockscreen.h"
#include "keepawakehelper.h"
#include "virtualbike.h"
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <QtXml>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
daumbike::daumbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);
target_watts.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &daumbike::update);
refresh->start(50ms);
QString daumbikeSerialPort =
settings.value(QZSettings::daumbike_serialport, QZSettings::default_daumbike_serialport).toString();
myDaumBike = new Daum(this, daumbikeSerialPort, "");
myDaumBike->start();
ergModeSupported = true; // IMPORTANT, only for this bike
initRequest = true;
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !virtualBike
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual bike interface..."));
virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,& daumbike::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &daumbike::changeInclination);
}
}
firstStateChanged = 1;
// ********************************************************************************************************
}
resistance_t daumbike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
QSettings settings;
double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
for (resistance_t i = 1; i < max_resistance; i++) {
if (((wattsFromResistance(i) * watt_gain) + watt_offset) <= power &&
((wattsFromResistance(i + 1) * watt_gain) + watt_offset) >= power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest")
<< ((wattsFromResistance(i) * watt_gain) + watt_offset)
<< ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) << power;
return i;
}
}
if (power < ((wattsFromResistance(1) * watt_gain) + watt_offset))
return 1;
else
return max_resistance;
}
uint16_t daumbike::wattsFromResistance(resistance_t resistance) {
if (currentCadence().value() == 0)
return 0;
switch (resistance) {
case 0:
case 1:
// -13.5 + 0.999x + 0.00993x²
return (-13.5 + (0.999 * currentCadence().value()) + (0.00993 * pow(currentCadence().value(), 2)));
case 2:
// -17.7 + 1.2x + 0.0116x²
return (-17.7 + (1.2 * currentCadence().value()) + (0.0116 * pow(currentCadence().value(), 2)));
case 3:
// -17.5 + 1.24x + 0.014x²
return (-17.5 + (1.24 * currentCadence().value()) + (0.014 * pow(currentCadence().value(), 2)));
case 4:
// -20.9 + 1.43x + 0.016x²
return (-20.9 + (1.43 * currentCadence().value()) + (0.016 * pow(currentCadence().value(), 2)));
case 5:
// -27.9 + 1.75x+0.0172x²
return (-27.9 + (1.75 * currentCadence().value()) + (0.0172 * pow(currentCadence().value(), 2)));
case 6:
// -26.7 + 1.9x + 0.0201x²
return (-26.7 + (1.9 * currentCadence().value()) + (0.0201 * pow(currentCadence().value(), 2)));
case 7:
// -33.5 + 2.23x + 0.0225x²
return (-33.5 + (2.23 * currentCadence().value()) + (0.0225 * pow(currentCadence().value(), 2)));
case 8:
// -36.5+2.5x+0.0262x²
return (-36.5 + (2.5 * currentCadence().value()) + (0.0262 * pow(currentCadence().value(), 2)));
case 9:
// -38+2.62x+0.0305x²
return (-38.0 + (2.62 * currentCadence().value()) + (0.0305 * pow(currentCadence().value(), 2)));
case 10:
// -41.2+2.85x+0.0327x²
return (-41.2 + (2.85 * currentCadence().value()) + (0.0327 * pow(currentCadence().value(), 2)));
case 11:
// -43.4+3.01x+0.0359x²
return (-43.4 + (3.01 * currentCadence().value()) + (0.0359 * pow(currentCadence().value(), 2)));
case 12:
// -46.8+3.23x+0.0364x²
return (-46.8 + (3.23 * currentCadence().value()) + (0.0364 * pow(currentCadence().value(), 2)));
case 13:
// -49+3.39x+0.0371x²
return (-49.0 + (3.39 * currentCadence().value()) + (0.0371 * pow(currentCadence().value(), 2)));
case 14:
// -53.4+3.55x+0.0383x²
return (-53.4 + (3.55 * currentCadence().value()) + (0.0383 * pow(currentCadence().value(), 2)));
case 15:
// -49.9+3.37x+0.0429x²
return (-49.9 + (3.37 * currentCadence().value()) + (0.0429 * pow(currentCadence().value(), 2)));
case 16:
default:
// -47.1+3.25x+0.0464x²
return (-47.1 + (3.25 * currentCadence().value()) + (0.0464 * pow(currentCadence().value(), 2)));
}
}
// must be double because it's an inclination
void daumbike::forceResistance(double requestResistance) { qDebug() << "forceResistance" << requestResistance; }
void daumbike::innerWriteResistance() {
QSettings settings;
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
if (requestResistance != -1) {
if (requestResistance > max_resistance) {
requestResistance = max_resistance;
} else if (requestResistance < min_resistance) {
requestResistance = min_resistance;
} else if (requestResistance == 0) {
requestResistance = 1;
}
if (requestResistance != currentResistance().value()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) &&
(requestPower == 0 || requestPower == -1)) {
forceResistance(requestResistance);
}
}
requestResistance = -1;
}
if (requestPower > 0) {
qDebug() << "change inclination due to request power = " << requestPower;
}
if (requestInclination != -100) {
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
forceResistance(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
// i'm using the gears in the inclination
requestInclination = -100;
}
}
void daumbike::update() {
if (initRequest) {
initRequest = false;
btinit();
emit connectedAndDiscovered();
} else {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
Speed = myDaumBike->getSpeed();
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
emit debug("Current Distance: " + QString::number(Distance.value()));
Cadence = myDaumBike->getCadence();
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
m_watt = myDaumBike->getPower();
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
Inclination = 0;
// emit debug(QStringLiteral("Current Inclination: ") + QString::number(Gradient));
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
/*
Resistance = resistance;
m_pelotonResistance = (100 / 32) * Resistance.value();
emit resistanceRead(Resistance.value()); */
if (!disable_hr_frommachinery) {
Heart = myDaumBike->getHeartRate();
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (disable_hr_frommachinery && heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
debug("Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate));
#endif
#endif
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
/*
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); */
update_metrics(false, watts());
// updating the treadmill console every second
if (sec1Update++ == (500 / refresh->interval())) {
sec1Update = 0;
// updateDisplay(elapsed);
}
innerWriteResistance();
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
// btinit();
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
bool daumbike::inclinationAvailableByHardware() {
QSettings settings;
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
if (proform_studio || proform_tdf_10)
return true;
else
return false;
}
resistance_t daumbike::pelotonToBikeResistance(int pelotonResistance) {
if (pelotonResistance <= 10) {
return 1;
}
if (pelotonResistance <= 20) {
return 2;
}
if (pelotonResistance <= 25) {
return 3;
}
if (pelotonResistance <= 30) {
return 4;
}
if (pelotonResistance <= 35) {
return 5;
}
if (pelotonResistance <= 40) {
return 6;
}
if (pelotonResistance <= 45) {
return 7;
}
if (pelotonResistance <= 50) {
return 8;
}
if (pelotonResistance <= 55) {
return 9;
}
if (pelotonResistance <= 60) {
return 10;
}
if (pelotonResistance <= 65) {
return 11;
}
if (pelotonResistance <= 70) {
return 12;
}
if (pelotonResistance <= 75) {
return 13;
}
if (pelotonResistance <= 80) {
return 14;
}
if (pelotonResistance <= 85) {
return 15;
}
if (pelotonResistance <= 100) {
return 16;
}
return Resistance.value();
}
void daumbike::btinit() { initDone = true; }
void daumbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
}
bool daumbike::connected() { return true; }
void *daumbike::VirtualBike() { return virtualBike; }
void *daumbike::VirtualDevice() { return VirtualBike(); }
uint16_t daumbike::watts() { return m_watt.value(); }

94
src/daumbike.h Normal file
View File

@@ -0,0 +1,94 @@
#ifndef DAUMBIKE_H
#define DAUMBIKE_H
#include <QAbstractOAuth2>
#include <QObject>
#include <QSettings>
#include <QTimer>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QString>
#include "Daum.h"
#include "bike.h"
#include "virtualbike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class daumbike : public bike {
Q_OBJECT
public:
daumbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance);
resistance_t resistanceFromPowerRequest(uint16_t power);
resistance_t maxResistance() { return max_resistance; }
bool inclinationAvailableByHardware();
bool connected();
void *VirtualBike();
void *VirtualDevice();
private:
resistance_t max_resistance = 100;
resistance_t min_resistance = -20;
uint16_t wattsFromResistance(resistance_t resistance);
double GetDistanceFromPacket(QByteArray packet);
QTime GetElapsedFromPacket(QByteArray packet);
void btinit();
void startDiscover();
void sendPoll();
uint16_t watts();
void forceResistance(double requestResistance);
void innerWriteResistance();
QTimer *refresh;
virtualbike *virtualBike = nullptr;
uint8_t counterPoll = 0;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t sec1Update = 0;
QString lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
metric target_watts;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
Daum *myDaumBike = nullptr;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void update();
};
#endif // DAUMBIKE_H

View File

@@ -3,6 +3,10 @@ QT += bluetooth widgets xml positioning quick networkauth websockets texttospeec
QTPLUGIN += qavfmediaplayer
QT+= charts
win32:QT += serialport
macx:QT += serialport
unix:QT += serialport
qtHaveModule(httpserver) {
QT += httpserver
DEFINES += Q_HTTPSERVER
@@ -63,6 +67,8 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD
# include(../qtzeroconf/qtzeroconf.pri)
SOURCES += \
$$PWD/Daum.cpp \
$$PWD/daumbike.cpp \
Computrainer.cpp \
PathController.cpp \
characteristicnotifier2a53.cpp \
@@ -253,6 +259,8 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
INCLUDEPATH += fit-sdk/
HEADERS += \
$$PWD/Daum.h \
$$PWD/daumbike.h \
$$PWD/discoveryoptions.h \
Computrainer.h \
PathController.h \

View File

@@ -582,11 +582,14 @@ const QString QZSettings::gears_restore_value = QStringLiteral("gears_restore_va
const QString QZSettings::gears_current_value = QStringLiteral("gears_current_value");
const QString QZSettings::tile_pace_last500m_enabled = QStringLiteral("tile_pace_last500m_enabled");
const QString QZSettings::tile_pace_last500m_order = QStringLiteral("tile_pace_last500m_order");
const QString QZSettings::daumbike_serialport = QStringLiteral("daumbike_serialport");
const QString QZSettings::default_daumbike_serialport = QStringLiteral("");
const QString QZSettings::treadmill_difficulty_gain_or_offset = QStringLiteral("treadmill_difficulty_gain_or_offset");
const QString QZSettings::pafers_treadmill_bh_iboxster_plus = QStringLiteral("pafers_treadmill_bh_iboxster_plus");
const QString QZSettings::proform_cycle_trainer_400 = QStringLiteral("proform_cycle_trainer_400");
const uint32_t allSettingsCount = 481;
const uint32_t allSettingsCount = 482;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
{QZSettings::bluetooth_no_reconnection, QZSettings::default_bluetooth_no_reconnection},
@@ -1069,6 +1072,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::gears_current_value, QZSettings::gears_current_value},
{QZSettings::tile_pace_last500m_enabled, QZSettings::default_tile_pace_last500m_enabled},
{QZSettings::tile_pace_last500m_order, QZSettings::default_tile_pace_last500m_order},
{QZSettings::daumbike_serialport, QZSettings::default_daumbike_serialport},
{QZSettings::treadmill_difficulty_gain_or_offset, QZSettings::default_treadmill_difficulty_gain_or_offset},
{QZSettings::pafers_treadmill_bh_iboxster_plus, QZSettings::default_pafers_treadmill_bh_iboxster_plus},
{QZSettings::proform_cycle_trainer_400, QZSettings::default_proform_cycle_trainer_400},

View File

@@ -1660,6 +1660,9 @@ class QZSettings {
static const QString tile_pace_last500m_order;
static constexpr int default_tile_pace_last500m_order = 49;
static const QString daumbike_serialport;
static const QString default_daumbike_serialport;
static const QString treadmill_difficulty_gain_or_offset;
static constexpr bool default_treadmill_difficulty_gain_or_offset = false;

View File

@@ -653,6 +653,9 @@ import Qt.labs.settings 1.0
// from version 2.12.52
property bool proform_cycle_trainer_400: false
// from version 2.12.56
property string daumbike_serialport: ""
}
function paddingZeros(text, limit) {
@@ -2708,6 +2711,37 @@ import Qt.labs.settings 1.0
}
}
AccordionElement {
id: daumBikeAccordion
title: qsTr("Daum Bike Options")
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Yellow)
color: Material.backgroundColor
RowLayout {
spacing: 10
Label {
id: labeldaumBikeSerialPort
text: qsTr("Serial Port:")
Layout.fillWidth: true
}
TextField {
id: daumbikeSerialPortTextField
text: settings.daumbike_serialport
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
//inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: settings.daumbike_serialport = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
id: okdaumBikeSerialPortButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: settings.daumbike_serialport = daumbikeSerialPortTextField.text
}
}
}
AccordionElement {
id: m3iBikeAccordion
@@ -4280,7 +4314,7 @@ import Qt.labs.settings 1.0
textColor: Material.color(Material.Yellow)
color: Material.backgroundColor
accordionContent: ColumnLayout {
spacing: 0
spacing: 0
SwitchDelegate {
id: nordictrackS25iDelegate
text: qsTr("Nordictrack S25i")
@@ -4322,7 +4356,7 @@ import Qt.labs.settings 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.nordictrack_t65s_treadmill = checked
}
}
SwitchDelegate {
id: nordictrackT65S_83Delegate
@@ -5224,7 +5258,7 @@ import Qt.labs.settings 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.toorx_bike = checked
}
}
SwitchDelegate {
id: toorxFTMSTreadmillDelegate
@@ -6420,7 +6454,7 @@ import Qt.labs.settings 1.0
text: "Refresh Devices List"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: refresh_bluetooth_devices_clicked();
}
}
SwitchDelegate {
id: ss2kPelotonDelegate