/* * 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 #include 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; }