mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
7 Commits
crossQFile
...
daumbike
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48f53dfb68 | ||
|
|
ec5b32f7a2 | ||
|
|
72277ea0b6 | ||
|
|
c0df5d284a | ||
|
|
0dcfd24052 | ||
|
|
3d710b1e9d | ||
|
|
cae92b5546 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -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
502
src/Daum.cpp
Normal 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
133
src/Daum.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
403
src/daumbike.cpp
Normal 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
94
src/daumbike.h
Normal 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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user