Files
GoldenCheetah/src/QuarqdClient.cpp
Dag Gruneau e3ac6c799c quarqd - cadence, wheel rotation, error checking
Fixed a number of issues with data from quarqd inf and nan values where
inserted as valid data points and thus destoying all plotting in the
realtime window and in later analysis.

The unit was used to distinguish between the entities, thus rpm was
erroneously used as a cadence, rpm is used as the unit for wheel
rotation and for cadence.  This made the cadence useless together with a
PowerTap hub which reports both cadence and wheel rotation.

No error checking was performed on the received data, bad data is
ignored now.
2010-02-12 06:07:30 -08:00

452 lines
12 KiB
C++

/*
* Copyright (c) 2009 Justin F. Knotzke (jknotzke@shampoo.ca)
*
* 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 <QtNetwork>
#include <QMessageBox>
#include <QTime>
#include <QProgressDialog>
#include <QtDebug>
#include "QuarqdClient.h"
#include "RealtimeData.h"
// Strings as received from quarqd
// tested with a Garmin ANT+ stick
//
static QString powerStr = "<Power ";
static QString cadenceStr = "<Cadence ";
static QString speedStr = "<Speed ";
static QString heartRateStr = "<HeartRate ";
static QString sensorStaleStr = "<SensorStale ";
static QString sensorFoundStr = "<SensorFound ";
static QString sensorDropStr = "<SensorDrop ";
static QString sensorLostStr = "<SensorLost ";
/* Control status */
#define ANT_RUNNING 0x01
#define ANT_PAUSED 0x02
QuarqdClient::QuarqdClient(QObject *parent, DeviceConfiguration *config) : QThread(parent)
{
Status=0;
// server hostname and TCP port#
deviceHostname = config->portSpec.section(':',0,0).toAscii(); // after the colon
devicePort = (int)QString(config->portSpec).section(':',1,1).toInt(); // after the colon
antIDs = config->deviceProfile.split(",");
lastReadWatts = 0;
lastReadCadence = 0;
lastReadSpeed = 0;
}
QuarqdClient::~QuarqdClient()
{
}
void QuarqdClient::run()
{
qDebug() << "Starting thread...";
int status; // control commands from controller
bool isPortOpen = false;
Status = ANT_RUNNING;
QString strBuf;
openPort();
isPortOpen = true;
elapsedTime.start();
while(1)
{
if(tcpSocket->isValid())
{
if(!tcpSocket->waitForReadyRead(-1))
{
return;
}
QByteArray array = tcpSocket->readAll();
strBuf = array;
parseElement(strBuf); // updates local telemetry
}
//----------------------------------------------------------------------
// LISTEN TO CONTROLLER FOR COMMANDS
//----------------------------------------------------------------------
pvars.lock();
status = this->Status;
pvars.unlock();
/* time to shut up shop */
if (!(status&ANT_RUNNING)) {
// time to stop!
quit(0);
return;
}
}
}
void
QuarqdClient::parseElement(QString &strBuf) // updates QuarqdClient::telemetry
{
QStringList qList = strBuf.split("\n");
// qDebug("%s",strBuf.toAscii().data());
//Loop for all the elements.
for(int i=0; i<qList.size(); i++)
{
QString str = qList.at(i);
bool ok;
double value;
int start, end;
QString mid;
if(str.startsWith(powerStr))
{
start = str.indexOf("watts='");
start += 7;
end = str.indexOf("'", start);
mid = str.mid(start, end - start);
value = mid.toDouble(&ok);
if (ok && mid != "nan" && mid != "inf") {
telemetry.setWatts(value);
telemetry.setTime(getTimeStamp(str));
lastReadWatts = elapsedTime.elapsed();
}
} else if(str.startsWith(cadenceStr))
{
start = str.indexOf("RPM='");
start += 5;
end = str.indexOf("'", start);
mid = str.mid(start, end - start);
value = mid.toDouble(&ok);
if (ok && mid != "nan" && mid != "inf") {
telemetry.setCadence(value);
telemetry.setTime(getTimeStamp(str));
lastReadCadence = elapsedTime.elapsed();
}
} else if(str.startsWith(speedStr))
{
// This is not the speed it is the revolution of the wheel
// at least when it comes from PowerTap.
start = str.indexOf("RPM='");
start += 5;
end = str.indexOf("'", start);
mid = str.mid(start, end - start);
value = mid.toDouble(&ok);
if (ok && mid != "nan" && mid != "inf") {
if (value > 0) {
// TODO: let wheel size be a configurable, default now to 2101 mm
telemetry.setSpeed((value*2101/1000*60)/1000); // meter/minute -> meter/hour -> km/hour
lastReadSpeed = elapsedTime.elapsed();
}
telemetry.setTime(getTimeStamp(str));
}
} else if(str.startsWith(heartRateStr))
{
start = str.indexOf("BPM");
start += 5;
end = str.indexOf("'", start);
mid = str.mid(start, end - start);
value = mid.toDouble(&ok);
if (ok && mid != "nan" && mid != "inf") {
telemetry.setHr(value);
telemetry.setTime(getTimeStamp(str));
}
} else if(str.startsWith(sensorDropStr) || str.startsWith(sensorStaleStr) || str.startsWith(sensorLostStr))//Try and save
{
int start = str.indexOf("id");
start += 4;
int end = str.indexOf("'", start);
reinitChannel(str.mid(start, end - start));
}
if(elapsedTime.elapsed() - lastReadCadence > 5000)
telemetry.setCadence(0);
if(elapsedTime.elapsed() - lastReadWatts > 5000)
telemetry.setWatts(0);
if(elapsedTime.elapsed() - lastReadSpeed > 5000)
telemetry.setSpeed(0);
}
}
long QuarqdClient::getTimeStamp(QString &str)
{
//Get the timestamp
int start = str.indexOf("timestamp='");
start += 11;
int end = str.indexOf(".", start);
// qDebug() << str.mid(start, end - start);
return str.mid(start, end - start).toLong();
}
int
QuarqdClient::openPort()
{
tcpSocket = new QTcpSocket();
tcpSocket->connectToHost(deviceHostname, devicePort);
connect(tcpSocket, SIGNAL(connected()), this, SLOT(initChannel()));
return 0;
}
int
QuarqdClient::closePort()
{
tcpSocket->close();
delete tcpSocket;
return 0;
}
//
// All stuff below here is to support startup / shutdown commands and
// interaction with the ANTplusController class between the QuarqdClient
// and the GUI
//
int
QuarqdClient::start()
{
QThread::start();
return 0;
}
int
QuarqdClient::restart()
{
int status;
// get current status
pvars.lock();
status = this->Status;
pvars.unlock();
// what state are we in anyway?
if (status&ANT_RUNNING && status&ANT_PAUSED) {
status &= ~ANT_PAUSED;
pvars.lock();
this->Status = status;
pvars.unlock();
return 0; // ok its running again!
}
return 2;
}
int
QuarqdClient::pause()
{
int status;
// get current status
pvars.lock();
status = this->Status;
pvars.unlock();
if (status&ANT_PAUSED) return 2; // already paused you muppet!
else if (!(status&ANT_RUNNING)) return 4; // not running anyway, fool!
else {
// ok we're running and not paused so lets pause
status |= ANT_PAUSED;
pvars.lock();
this->Status = status;
pvars.unlock();
return 0;
}
}
int
QuarqdClient::stop()
{
int status;
// get current status
pvars.lock();
status = this->Status;
pvars.unlock();
// what state are we in anyway?
pvars.lock();
Status = 0; // Terminate it!
pvars.unlock();
return 0;
}
int
QuarqdClient::quit(int code)
{
// event code goes here!
closePort();
exit(code);
return 0; // never gets here obviously but shuts up the compiler!
}
bool
QuarqdClient::discover(DeviceConfiguration *config, QProgressDialog *progress)
{
QString strBuf;
QStringList strList;
sentDual = false;
sentSpeed = false;
sentHR = false;
sentCad = false;
sentPWR = false;
openPort();
QByteArray strPwr("X-set-channel: 0p"); //Power
QByteArray strHR("X-set-channel: 0h"); //Heart Rate
QByteArray strSpeed("X-set-channel: 0s"); //Speed
QByteArray strCad("X-set-channel: 0c"); //Cadence
QByteArray strDual("X-set-channel: 0d"); //Dual (Speed/Cadence)
// tell quarqd to start scanning....
if (tcpSocket->isOpen()) {
tcpSocket->write(strPwr); //Power
}
QTime start;
start.start();
progress->setMaximum(50000);
while(start.elapsed() <= 50000) //Scan for 50 seconds.
{
if (progress->wasCanceled())
{
tcpSocket->close();
return false;
}
progress->setValue(start.elapsed());
if(start.elapsed() >= 40000 && sentDual == false)
{
sentDual = true;
tcpSocket->write(strDual); //Dual
} else if(start.elapsed() >= 30000 && sentSpeed == false)
{
sentSpeed = true;
tcpSocket->write(strSpeed); //Speed
} else if(start.elapsed() >= 20000 && sentHR == false)
{
sentHR = true;
tcpSocket->write(strHR); //HR
} else if(start.elapsed() >= 10000 && sentCad == false)
{
sentCad = true;
tcpSocket->write(strCad); //Cadence
} else if(start.elapsed() >= 0 && sentPWR == false)
{
sentPWR = true;
tcpSocket->write(strPwr);
}
if (tcpSocket->bytesAvailable() > 0) {
QByteArray array = tcpSocket->readAll();
strBuf = array;
// qDebug() << strBuf;
QStringList qList = strBuf.split("\n");
//Loop for all the elements.
for(int i=0; i<qList.size(); i++)
{
progress->setValue(start.elapsed());
QString str = qList.at(i);
// qDebug() << str;
if(str.contains("id"))
{
int start = str.indexOf("id");
start += 4;
int end = str.indexOf("'", start);
QString id = str.mid(start, end - start);
if(!strList.contains(id))
{
if(id != "0p" && id != "0h" && id != "0s" && id != "0c" && id != "0d")
strList.append(id);
}
}
}
}
}
progress->setValue(50000);//We are done.
//Now return a comma delimited string.
for(int i=0; i < strList.size(); i++)
{
config->deviceProfile.append(strList.at(i));
if(i < strList.size() -1)
config->deviceProfile.append(',');
}
return config;
}
RealtimeData
QuarqdClient::getRealtimeData()
{
return telemetry;
}
void QuarqdClient::reinitChannel(QString _channel)
{
if(!tcpSocket->isValid())
return;
qDebug() << "Reinit: " << _channel;
QByteArray channel("X-set-channel: ");
channel.append(_channel);
tcpSocket->write(channel);
}
void
QuarqdClient::initChannel()
{
qDebug() << "Init channel..";
if(!tcpSocket->isValid())
return;
QByteArray setChannel("X-set-channel: ");
QByteArray channel;
for(int i=0; i < antIDs.size(); i++)
{
if(tcpSocket->isValid())
{
channel.clear();
channel = setChannel;
channel.append(antIDs.at(i));
tcpSocket->write(channel);
tcpSocket->flush();
qDebug() << channel;
sleep(2);
}
}
}