mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 00:49:55 +00:00
Add support for Quarq ANT+ log import.
The Quarq ANT+ log contains a hex dump of raw ANT+ messages. This importer uses the closed-source program "qollector_interpret" to convert the ANT+ log file into an XML format, then parses that into a RideFile. qollector_interpret binaries for several platforms may be downloaded from http://opensource.quarq.us/qollector_interpret If the qollector_interpret program is not available, the menu selection for importing Quarq ANT+ will not appear, nor will .qla files be imported.
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
#include "PowerHist.h"
|
||||
#include "RideItem.h"
|
||||
#include "RideFile.h"
|
||||
#include "QuarqRideFile.h"
|
||||
#include "RideMetric.h"
|
||||
#include "Settings.h"
|
||||
#include "TimeUtils.h"
|
||||
@@ -606,6 +607,9 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
SLOT (importPolar()));
|
||||
rideMenu->addAction(tr("&Import from WKO..."), this,
|
||||
SLOT (importWKO()));
|
||||
if (quarqInterpreterInstalled())
|
||||
rideMenu->addAction(tr("&Import from Quarq ANT+ log..."), this,
|
||||
SLOT (importQuarq()));
|
||||
rideMenu->addAction(tr("Find &best intervals..."), this,
|
||||
SLOT(findBestIntervals()), tr ("Ctrl+B"));
|
||||
rideMenu->addAction(tr("Split &ride..."), this,
|
||||
@@ -1137,6 +1141,63 @@ MainWindow::importPolar()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::importQuarq()
|
||||
{
|
||||
QVariant lastDirVar = settings->value(GC_SETTINGS_LAST_IMPORT_PATH);
|
||||
QString lastDir = (lastDirVar != QVariant())
|
||||
? lastDirVar.toString() : QDir::homePath();
|
||||
QStringList fileNames = QFileDialog::getOpenFileNames(
|
||||
this, tr("Import Ant"), lastDir,
|
||||
tr("Quarq ANT+ Format (*.qla)"));
|
||||
if (!fileNames.isEmpty()) {
|
||||
lastDir = QFileInfo(fileNames.front()).absolutePath();
|
||||
settings->setValue(GC_SETTINGS_LAST_IMPORT_PATH, lastDir);
|
||||
}
|
||||
QStringList fileNamesCopy = fileNames;
|
||||
QStringListIterator i(fileNames);
|
||||
while (i.hasNext()) {
|
||||
QString fileName = i.next();
|
||||
QFile file(fileName);
|
||||
QStringList errors;
|
||||
|
||||
boost::scoped_ptr<RideFile> ride(
|
||||
RideFileFactory::instance().openRideFile(file, errors));
|
||||
|
||||
if (!ride || !errors.empty()) {
|
||||
QString all = (ride
|
||||
? tr("Non-fatal problem(s) opening %1:")
|
||||
: tr("Fatal problem(s) opening %1:")).arg(fileName);
|
||||
QStringListIterator i(errors);
|
||||
while (i.hasNext())
|
||||
all += "\n" + i.next();
|
||||
if (ride)
|
||||
QMessageBox::warning(this, tr("Open Warning"), all);
|
||||
else {
|
||||
QMessageBox::critical(this, tr("Open Error"), all);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QChar zero = QLatin1Char('0');
|
||||
QString name = QString("%1_%2_%3_%4_%5_%6.qla")
|
||||
.arg(ride->startTime().date().year(), 4, 10, zero)
|
||||
.arg(ride->startTime().date().month(), 2, 10, zero)
|
||||
.arg(ride->startTime().date().day(), 2, 10, zero)
|
||||
.arg(ride->startTime().time().hour(), 2, 10, zero)
|
||||
.arg(ride->startTime().time().minute(), 2, 10, zero)
|
||||
.arg(ride->startTime().time().second(), 2, 10, zero);
|
||||
|
||||
if (!file.copy(home.absolutePath() + "/" + name)) {
|
||||
QMessageBox::critical(this, tr("Copy Error"),
|
||||
tr("Couldn't copy %1").arg(fileName+" to "+home.absolutePath()+"/"+name));
|
||||
return;
|
||||
}
|
||||
|
||||
addRide(name);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::findBestIntervals()
|
||||
{
|
||||
|
||||
@@ -68,6 +68,7 @@ class MainWindow : public QMainWindow
|
||||
void importTCX();
|
||||
void importWKO();
|
||||
void importPolar();
|
||||
void importQuarq();
|
||||
void findBestIntervals();
|
||||
void splitRide();
|
||||
void deleteRide();
|
||||
|
||||
128
src/QuarqParser.cpp
Normal file
128
src/QuarqParser.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Rages (mark@quarq.us)
|
||||
*
|
||||
* 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 <QString>
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
#include "QuarqParser.h"
|
||||
|
||||
QuarqParser::QuarqParser (RideFile* rideFile)
|
||||
: rideFile(rideFile),
|
||||
version(""),
|
||||
km(0),
|
||||
watts(0),
|
||||
cad(0),
|
||||
hr(0),
|
||||
initial_seconds(-1.0),
|
||||
seconds_from_start(0),
|
||||
kph(0),
|
||||
nm(0)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
// implement sample-and-hold resampling
|
||||
|
||||
#define SAMPLE_INTERVAL 1.0 // seconds
|
||||
|
||||
void
|
||||
QuarqParser::incrementTime( const double new_time )
|
||||
{
|
||||
if (initial_seconds < 0.0) {
|
||||
initial_seconds = new_time;
|
||||
}
|
||||
|
||||
float time_diff = new_time - initial_seconds;
|
||||
|
||||
while (time_diff > seconds_from_start) {
|
||||
|
||||
rideFile->appendPoint(seconds_from_start, cad, hr, km,
|
||||
kph, nm, watts, 0, 9, 0);
|
||||
|
||||
seconds_from_start += SAMPLE_INTERVAL;
|
||||
}
|
||||
time.setTime_t(new_time);
|
||||
}
|
||||
|
||||
bool
|
||||
QuarqParser::startElement( const QString&, const QString&,
|
||||
const QString& qName,
|
||||
const QXmlAttributes& qAttributes)
|
||||
{
|
||||
buf.clear();
|
||||
|
||||
if (qName == "Qollector") {
|
||||
version = qAttributes.value("version");
|
||||
|
||||
// reset the timer for a new <Qollector> tag
|
||||
seconds_from_start = 0.0;
|
||||
initial_seconds = -1;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#define CheckQuarqXml(name,unit,dest) do { \
|
||||
if (qName== #name) { \
|
||||
QString name = qAttributes.value( #unit ); \
|
||||
QString timestamp = qAttributes.value("timestamp"); \
|
||||
\
|
||||
if ((! name.isEmpty()) && (!timestamp.isEmpty()) && \
|
||||
( name.toLower() != "nan")) { \
|
||||
dest = name.toDouble(); \
|
||||
incrementTime(timestamp.toDouble()); \
|
||||
} \
|
||||
return TRUE; \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
CheckQuarqXml(Cadence, RPM, cad );
|
||||
CheckQuarqXml(Power, Watts, watts );
|
||||
CheckQuarqXml(HeartRate, BPM, hr );
|
||||
// clearly bogus, equating RPM to kph.
|
||||
// Unless you have an 18 foot wheel, by chance
|
||||
CheckQuarqXml(Speed, RPM, kph );
|
||||
|
||||
#undef CheckQuarqXml
|
||||
|
||||
// default case
|
||||
|
||||
// only print the first time and unknown happens
|
||||
if (!unknown_keys[qName]++)
|
||||
std::cerr << "Unknown Element " << qPrintable(qName) << std::endl;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool
|
||||
QuarqParser::endElement( const QString&, const QString&, const QString& qName)
|
||||
{
|
||||
|
||||
// flush one last data point
|
||||
if (qName == "Qollector") {
|
||||
rideFile->appendPoint(seconds_from_start, cad, hr, km,
|
||||
kph, nm, watts, 0, 0, 0);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool
|
||||
QuarqParser::characters( const QString& str )
|
||||
{
|
||||
buf += str;
|
||||
return TRUE;
|
||||
}
|
||||
68
src/QuarqParser.h
Normal file
68
src/QuarqParser.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Rages (mark@quarq.us)
|
||||
*
|
||||
* 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 _QuarqParser_h
|
||||
#define _QuarqParser_h
|
||||
|
||||
#include "RideFile.h"
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include <QDateTime>
|
||||
#include <QProcess>
|
||||
#include <QXmlDefaultHandler>
|
||||
|
||||
class QuarqParser : public QXmlDefaultHandler
|
||||
{
|
||||
public:
|
||||
QuarqParser(RideFile* rideFile);
|
||||
|
||||
bool startElement( const QString&, const QString&, const QString&,
|
||||
const QXmlAttributes& );
|
||||
bool endElement( const QString&, const QString&, const QString& );
|
||||
|
||||
bool characters( const QString& );
|
||||
|
||||
private:
|
||||
|
||||
void incrementTime( const double new_time ) ;
|
||||
|
||||
RideFile* rideFile;
|
||||
|
||||
QString buf;
|
||||
|
||||
QString version;
|
||||
|
||||
QDateTime time;
|
||||
double km;
|
||||
|
||||
double watts;
|
||||
double cad;
|
||||
double hr;
|
||||
|
||||
double initial_seconds;
|
||||
double seconds_from_start;
|
||||
|
||||
double kph;
|
||||
double nm;
|
||||
|
||||
QHash<QString, int> unknown_keys;
|
||||
|
||||
};
|
||||
|
||||
#endif // _QuarqParser_h
|
||||
|
||||
153
src/QuarqRideFile.cpp
Normal file
153
src/QuarqRideFile.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Rages (mark@quarq.us)
|
||||
*
|
||||
* 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 "QuarqRideFile.h"
|
||||
#include "QuarqParser.h"
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
|
||||
static QString installed_path = "";
|
||||
|
||||
|
||||
QProcess *getInterpreterProcess( QString path ) {
|
||||
|
||||
QProcess *antProcess;
|
||||
|
||||
antProcess = new QProcess( );
|
||||
antProcess->start( path );
|
||||
|
||||
if (!antProcess->waitForStarted()) {
|
||||
delete antProcess;
|
||||
antProcess = NULL;
|
||||
}
|
||||
|
||||
return antProcess;
|
||||
}
|
||||
|
||||
/*
|
||||
Thanks to ANT+ nondisclosure agreements, the Quarq ANT+
|
||||
interpretation code lives in a closed source binary. It takes the
|
||||
log on stdin and writes XML to stdout.
|
||||
|
||||
If the binary is not available, no Quarq ANT+ capability is shown
|
||||
in the menu, nor are any ANT+ files opened.
|
||||
|
||||
QProcess note:
|
||||
|
||||
It turns out that the interpreter must actually be opened and
|
||||
executed before we can be sure it's there. Checking the return
|
||||
value of start() isn't sufficient. On my Linux system, start()
|
||||
returns true upon opening the OS X build of qollector_interpret.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
bool quarqInterpreterInstalled( void ) {
|
||||
|
||||
static bool checkedInstallation = false;
|
||||
static bool installed;
|
||||
|
||||
if (!checkedInstallation) {
|
||||
|
||||
QString interpreterPath="/usr/local/bin/qollector_interpret/build-";
|
||||
QStringList executables;
|
||||
executables << "linux-i386/qollector_interpret";
|
||||
executables << "osx-ppc-i386/qollector_interpret";
|
||||
executables << "win32/qollector_interpret.exe";
|
||||
|
||||
for ( QStringList::Iterator ex = executables.begin(); ex != executables.end(); ++ex ) {
|
||||
|
||||
QProcess *antProcess = getInterpreterProcess( interpreterPath + *ex );
|
||||
|
||||
if (NULL == antProcess) {
|
||||
installed = false;
|
||||
} else {
|
||||
|
||||
antProcess->closeWriteChannel();
|
||||
antProcess->waitForFinished(-1);
|
||||
|
||||
installed=((QProcess::NormalExit == antProcess->exitStatus()) &&
|
||||
(0 == antProcess->exitCode()));
|
||||
|
||||
delete antProcess;
|
||||
|
||||
if (installed) {
|
||||
installed_path = interpreterPath + *ex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!installed)
|
||||
std::cerr << "Cannot open qollector_interpret program, available from http://opensource.quarq.us/qollector_interpret." << std::endl;
|
||||
|
||||
checkedInstallation = true;
|
||||
}
|
||||
|
||||
return installed;
|
||||
}
|
||||
|
||||
static int antFileReaderRegistered =
|
||||
quarqInterpreterInstalled() ? RideFileFactory::instance().registerReader("qla", new QuarqFileReader()) : 0;
|
||||
|
||||
RideFile *QuarqFileReader::openRideFile(QFile &file, QStringList &errors) const
|
||||
{
|
||||
(void) errors;
|
||||
RideFile *rideFile = new RideFile();
|
||||
rideFile->setDeviceType("Quarq Qollector");
|
||||
|
||||
QuarqParser handler(rideFile);
|
||||
|
||||
QProcess *antProcess = getInterpreterProcess( installed_path );
|
||||
|
||||
assert(antProcess);
|
||||
|
||||
QXmlInputSource source (antProcess);
|
||||
QXmlSimpleReader reader;
|
||||
reader.setContentHandler (&handler);
|
||||
|
||||
// this could done be a loop to "save memory."
|
||||
file.open(QIODevice::ReadOnly);
|
||||
antProcess->write(file.readAll());
|
||||
antProcess->closeWriteChannel();
|
||||
antProcess->waitForFinished(-1);
|
||||
|
||||
assert(QProcess::NormalExit == antProcess->exitStatus());
|
||||
assert(0 == antProcess->exitCode());
|
||||
|
||||
reader.parse(source);
|
||||
|
||||
reader.parseContinue();
|
||||
|
||||
QRegExp rideTime("^.*/(\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_"
|
||||
"(\\d\\d)_(\\d\\d)_(\\d\\d)\\.qla$");
|
||||
if (rideTime.indexIn(file.fileName()) >= 0) {
|
||||
QDateTime datetime(QDate(rideTime.cap(1).toInt(),
|
||||
rideTime.cap(2).toInt(),
|
||||
rideTime.cap(3).toInt()),
|
||||
QTime(rideTime.cap(4).toInt(),
|
||||
rideTime.cap(5).toInt(),
|
||||
rideTime.cap(6).toInt()));
|
||||
rideFile->setStartTime(datetime);
|
||||
}
|
||||
|
||||
delete antProcess;
|
||||
|
||||
return rideFile;
|
||||
}
|
||||
|
||||
32
src/QuarqRideFile.h
Normal file
32
src/QuarqRideFile.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Rages (mark@quarq.us)
|
||||
*
|
||||
* 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 _QuarqRideFile_h
|
||||
#define _QuarqRideFile_h
|
||||
|
||||
#include "RideFile.h"
|
||||
#include <QProcess>
|
||||
|
||||
bool quarqInterpreterInstalled( void );
|
||||
|
||||
struct QuarqFileReader : public RideFileReader {
|
||||
virtual RideFile *openRideFile(QFile &file, QStringList &errors) const;
|
||||
};
|
||||
|
||||
#endif // _QuarqRideFile_h
|
||||
|
||||
@@ -59,6 +59,8 @@ HEADERS += \
|
||||
SrmRideFile.h \
|
||||
TcxParser.h \
|
||||
TcxRideFile.h \
|
||||
QuarqParser.h \
|
||||
QuarqRideFile.h \
|
||||
TimeUtils.h \
|
||||
ConfigDialog.h \
|
||||
DatePickerDialog.h \
|
||||
@@ -104,6 +106,8 @@ SOURCES += \
|
||||
SrmRideFile.cpp \
|
||||
TcxParser.cpp \
|
||||
TcxRideFile.cpp \
|
||||
QuarqParser.cpp \
|
||||
QuarqRideFile.cpp \
|
||||
TimeUtils.cpp \
|
||||
BasicRideMetrics.cpp \
|
||||
BikeScore.cpp \
|
||||
|
||||
Reference in New Issue
Block a user