mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 08:38:45 +00:00
455 lines
10 KiB
C++
455 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2015 Erik Botö (erik.boto@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 "MonarkConnection.h"
|
|
|
|
#include <QByteArray>
|
|
#include <QDebug>
|
|
|
|
MonarkConnection::MonarkConnection() :
|
|
m_serial(0),
|
|
m_pollInterval(1000),
|
|
m_timer(0),
|
|
m_load(0),
|
|
m_loadToWrite(0),
|
|
m_kp(0),
|
|
m_kpToWrite(0),
|
|
m_shouldWriteLoad(false),
|
|
m_shouldWriteKp(false),
|
|
m_type(MONARK_UNKNOWN),
|
|
m_mode(MONARK_MODE_WATT),
|
|
m_power(0),
|
|
m_cadence(0),
|
|
m_pulse(0)
|
|
{
|
|
}
|
|
|
|
void MonarkConnection::setSerialPort(const QString serialPortName)
|
|
{
|
|
if (! this->isRunning())
|
|
{
|
|
m_serialPortName = serialPortName;
|
|
} else {
|
|
qWarning() << "MonarkConnection: Cannot set serialPortName while running";
|
|
}
|
|
}
|
|
|
|
void MonarkConnection::setPollInterval(int interval)
|
|
{
|
|
if (interval != m_pollInterval)
|
|
{
|
|
m_pollInterval = interval;
|
|
m_timer->setInterval(m_pollInterval);
|
|
}
|
|
}
|
|
|
|
int MonarkConnection::pollInterval()
|
|
{
|
|
return m_pollInterval;
|
|
}
|
|
|
|
/**
|
|
* Private function that reads a complete reply and prepares if for
|
|
* processing by replacing \r with \0
|
|
*/
|
|
QString MonarkConnection::readAnswer(int timeoutMs)
|
|
{
|
|
QByteArray data;
|
|
|
|
do
|
|
{
|
|
if (m_serial->waitForReadyRead(timeoutMs))
|
|
{
|
|
data.append(m_serial->readAll());
|
|
} else {
|
|
data.append('\r');
|
|
}
|
|
} while (data.indexOf('\r') == -1);
|
|
|
|
data.replace("\r", "\0");
|
|
return QString(data);
|
|
}
|
|
|
|
/**
|
|
* QThread::run()
|
|
*
|
|
* Open the serial port and set it up, then starts polling.
|
|
*
|
|
*/
|
|
void MonarkConnection::run()
|
|
{
|
|
// Open and configure serial port
|
|
m_serial = new QSerialPort();
|
|
m_serial->setPortName(m_serialPortName);
|
|
|
|
m_timer = new QTimer();
|
|
QTimer *startupTimer = new QTimer();
|
|
|
|
if (!m_serial->open(QSerialPort::ReadWrite))
|
|
{
|
|
qDebug() << "Error opening serial";
|
|
this->exit(-1);
|
|
} else {
|
|
configurePort(m_serial);
|
|
|
|
// Discard any existing data
|
|
QByteArray data = m_serial->readAll();
|
|
|
|
// Set up polling
|
|
connect(m_timer, SIGNAL(timeout()), this, SLOT(requestAll()),Qt::DirectConnection);
|
|
|
|
// Set up initial model detection
|
|
connect(startupTimer, SIGNAL(timeout()), this, SLOT(identifyModel()),Qt::DirectConnection);
|
|
}
|
|
|
|
m_timer->setInterval(1000);
|
|
m_timer->start();
|
|
|
|
startupTimer->setSingleShot(true);
|
|
startupTimer->setInterval(0);
|
|
startupTimer->start();
|
|
|
|
exec();
|
|
}
|
|
|
|
void MonarkConnection::requestAll()
|
|
{
|
|
// If something else is blocking mutex, don't start another round of requests
|
|
if (! m_mutex.tryLock())
|
|
return;
|
|
|
|
// Discard any previous data, useful if e.g a restart of the bike caused us to get
|
|
// out of sync
|
|
QByteArray data = m_serial->readAll();
|
|
Q_UNUSED(data)
|
|
|
|
requestPower();
|
|
requestPulse();
|
|
requestCadence();
|
|
|
|
if ((m_loadToWrite != m_load) && m_mode == MONARK_MODE_WATT && canDoLoad())
|
|
{
|
|
QString cmd = QString("power %1\r").arg(m_loadToWrite);
|
|
m_serial->write(cmd.toStdString().c_str());
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
m_load = m_loadToWrite;
|
|
QByteArray data = m_serial->readAll();
|
|
}
|
|
|
|
if ((m_kpToWrite != m_kp) && m_mode == MONARK_MODE_KP && canDoKp())
|
|
{
|
|
QString cmd = QString("kp %1\r").arg(QString::number(m_kpToWrite, 'f', 1 ));
|
|
m_serial->write(cmd.toStdString().c_str());
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
m_kp = m_kpToWrite;
|
|
QByteArray data = m_serial->readAll();
|
|
}
|
|
|
|
if ((m_mode == MONARK_MODE_KP) && canDoLoad() && !canDoKp())
|
|
{
|
|
// Calculate what wattage to request to simulate selected kp
|
|
// watt = kp * cadence * 0.98
|
|
unsigned int load = (m_kpToWrite * m_cadence) * 0.98;
|
|
|
|
QString cmd = QString("power %1\r").arg(load);
|
|
m_serial->write(cmd.toStdString().c_str());
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
QByteArray data = m_serial->readAll();
|
|
}
|
|
|
|
m_mutex.unlock();
|
|
}
|
|
|
|
void MonarkConnection::requestPower()
|
|
{
|
|
// Discard any existing data
|
|
m_serial->readAll();
|
|
|
|
m_serial->write("power\r");
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
QString data = readAnswer(500);
|
|
m_readMutex.lock();
|
|
m_power = data.toInt();
|
|
m_readMutex.unlock();
|
|
emit power(m_power);
|
|
}
|
|
|
|
void MonarkConnection::requestPulse()
|
|
{
|
|
// Discard any existing data
|
|
m_serial->readAll();
|
|
|
|
m_serial->write("pulse\r");
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
QString data = readAnswer(500);
|
|
m_readMutex.lock();
|
|
m_pulse = data.toInt();
|
|
m_readMutex.unlock();
|
|
emit pulse(m_pulse);
|
|
}
|
|
|
|
void MonarkConnection::requestCadence()
|
|
{
|
|
// Discard any existing data
|
|
m_serial->readAll();
|
|
|
|
m_serial->write("pedal\r");
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
QString data = readAnswer(500);
|
|
m_readMutex.lock();
|
|
m_cadence = data.toInt();
|
|
m_readMutex.unlock();
|
|
emit cadence(m_cadence);
|
|
}
|
|
|
|
int MonarkConnection::readConfiguredLoad()
|
|
{
|
|
// Discard any existing data
|
|
m_serial->readAll();
|
|
|
|
m_serial->write("B\r");
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
QString data = readAnswer(500);
|
|
//data.remove(0,1);
|
|
qDebug() << "Current configure load: " << data.toInt();
|
|
return data.toInt();
|
|
}
|
|
|
|
void MonarkConnection::identifyModel()
|
|
{
|
|
// Discard any existing data
|
|
m_serial->readAll();
|
|
|
|
QString servo = "";
|
|
|
|
m_serial->write("id\r");
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
m_id = readAnswer(500);
|
|
|
|
if (m_id.toLower().startsWith("novo"))
|
|
{
|
|
m_serial->write("servo\r");
|
|
if (!m_serial->waitForBytesWritten(500))
|
|
{
|
|
// failure to write to device, bail out
|
|
this->exit(-1);
|
|
}
|
|
servo = readAnswer(500);
|
|
}
|
|
|
|
|
|
qDebug() << "Connected to bike: " << m_id;
|
|
qDebug() << "Servo: : " << servo;
|
|
|
|
if (m_id.toLower().startsWith("lc"))
|
|
{
|
|
m_type = MONARK_LC;
|
|
setLoad(100);
|
|
} else if (m_id.toLower().startsWith("novo") && servo != "manual") {
|
|
m_type = MONARK_LC_NOVO;
|
|
setLoad(100);
|
|
} else if (m_id.toLower().startsWith("mec")) {
|
|
m_type = MONARK_839E;
|
|
setLoad(100);
|
|
}
|
|
|
|
}
|
|
|
|
void MonarkConnection::setLoad(unsigned int load)
|
|
{
|
|
m_loadToWrite = load;
|
|
m_mode = MONARK_MODE_WATT;
|
|
}
|
|
|
|
void MonarkConnection::setKp(double kp)
|
|
{
|
|
if (kp < 0)
|
|
kp = 0;
|
|
|
|
if (kp > 7)
|
|
kp = 7;
|
|
|
|
m_kpToWrite = kp;
|
|
m_mode = MONARK_MODE_KP;
|
|
}
|
|
|
|
/*
|
|
* Configures a serialport for communicating with a Monark bike.
|
|
*/
|
|
void MonarkConnection::configurePort(QSerialPort *serialPort)
|
|
{
|
|
if (!serialPort)
|
|
{
|
|
qFatal("Trying to configure null port, start debugging.");
|
|
}
|
|
serialPort->setBaudRate(QSerialPort::Baud4800);
|
|
serialPort->setDataBits(QSerialPort::Data8);
|
|
serialPort->setStopBits(QSerialPort::OneStop);
|
|
serialPort->setFlowControl(QSerialPort::SoftwareControl);
|
|
serialPort->setParity(QSerialPort::NoParity);
|
|
|
|
// Send empty \r after configuring port, otherwise first command might not
|
|
// be interpreted correctly
|
|
serialPort->write("\r");
|
|
}
|
|
|
|
bool MonarkConnection::canDoLoad()
|
|
{
|
|
bool result = false;
|
|
|
|
switch (m_type)
|
|
{
|
|
case MONARK_LC: // fall through
|
|
case MONARK_LC_NOVO: // fall through
|
|
case MONARK_839E:
|
|
result = true;
|
|
break;
|
|
case MONARK_LT2: // fall through
|
|
default:
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool MonarkConnection::canDoKp()
|
|
{
|
|
bool result = false;
|
|
|
|
switch (m_type)
|
|
{
|
|
case MONARK_LC_NOVO:
|
|
result = true;
|
|
break;
|
|
case MONARK_LC: // fall through
|
|
case MONARK_LT2: // fall through
|
|
case MONARK_839E: // fall through
|
|
default:
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This functions takes a serial port and tries if it can find a Monark bike connected
|
|
* to it.
|
|
*/
|
|
bool MonarkConnection::discover(QString portName)
|
|
{
|
|
bool found = false;
|
|
QSerialPort sp;
|
|
|
|
sp.setPortName(portName);
|
|
|
|
if (sp.open(QSerialPort::ReadWrite))
|
|
{
|
|
configurePort(&sp);
|
|
|
|
// Discard any existing data
|
|
QByteArray data = sp.readAll();
|
|
|
|
// Read id from bike
|
|
sp.write("id\r");
|
|
sp.waitForBytesWritten(-1);
|
|
|
|
QByteArray id;
|
|
do
|
|
{
|
|
bool readyToRead = sp.waitForReadyRead(1000);
|
|
if (readyToRead)
|
|
{
|
|
id.append(sp.readAll());
|
|
} else {
|
|
id.append('\r');
|
|
}
|
|
} while ((id.indexOf('\r') == -1));
|
|
|
|
id.replace("\r", "\0");
|
|
|
|
// Should check for all bike ids known to use this protocol
|
|
if (QString(id).toLower().contains("lt") ||
|
|
QString(id).toLower().contains("lc") ||
|
|
QString(id).toLower().contains("mec") ||
|
|
QString(id).toLower().contains("novo")) {
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
sp.close();
|
|
|
|
return found;
|
|
}
|
|
|
|
quint32 MonarkConnection::power()
|
|
{
|
|
QMutexLocker mlock(&m_readMutex);
|
|
return m_power;
|
|
}
|
|
|
|
quint32 MonarkConnection::cadence()
|
|
{
|
|
QMutexLocker mlock(&m_readMutex);
|
|
return m_cadence;
|
|
}
|
|
|
|
quint32 MonarkConnection::pulse()
|
|
{
|
|
QMutexLocker mlock(&m_readMutex);
|
|
return m_pulse;
|
|
}
|
|
|
|
double MonarkConnection::kp()
|
|
{
|
|
return m_kp;
|
|
}
|