mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
.. move the fluff that has collected into the src directory to somewhere more appropriate.
445 lines
12 KiB
C++
445 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 *devConf) : QThread(parent)
|
|
{
|
|
Status=0;
|
|
// server hostname and TCP port#
|
|
if (devConf) {
|
|
deviceHostname = devConf->portSpec.section(':',0,0).toAscii(); // after the colon
|
|
devicePort = (int)QString(devConf->portSpec).section(':',1,1).toInt(); // after the colon
|
|
antIDs = devConf->deviceProfile.split(",");
|
|
}
|
|
lastReadWatts = 0;
|
|
lastReadCadence = 0;
|
|
lastReadSpeed = 0;
|
|
}
|
|
|
|
QuarqdClient::~QuarqdClient()
|
|
{
|
|
}
|
|
|
|
void QuarqdClient::run()
|
|
{
|
|
qDebug() << "Starting thread...";
|
|
int status; // control commands from controller
|
|
|
|
Status = ANT_RUNNING;
|
|
QString strBuf;
|
|
|
|
openPort();
|
|
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.setMsecs(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.setMsecs(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
|
|
telemetry.setWheelRpm(value);
|
|
lastReadSpeed = elapsedTime.elapsed();
|
|
}
|
|
telemetry.setMsecs(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.setMsecs(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()
|
|
{
|
|
// 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 && sentSpeed == false)
|
|
{
|
|
sentSpeed = true;
|
|
tcpSocket->write(strSpeed); //Speed
|
|
} else if(start.elapsed() >= 30000 && sentDual == false)
|
|
{
|
|
sentDual = true;
|
|
tcpSocket->write(strDual); //Dual
|
|
} 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);
|
|
}
|
|
}
|
|
}
|