mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 00:28:42 +00:00
.. Don't include -255 in average .. Don't show -255 in ride navigator .. Don't show a value on summary if not preset
251 lines
8.7 KiB
C++
251 lines
8.7 KiB
C++
/*
|
|
* Copyright (c) 2010 Ilja Booij (ibooij@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 "RideFile.h"
|
|
#include <QtGlobal>
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QDateTime>
|
|
|
|
struct TacxCafFileReader : public RideFileReader {
|
|
virtual RideFile *openRideFile(QFile &file, QStringList &errors, QList<RideFile*>* = 0) const;
|
|
virtual bool hasWrite() const { return false; }
|
|
};
|
|
|
|
static int tacxCafFileReaderRegistered =
|
|
RideFileFactory::instance().registerReader(
|
|
"caf", "Tacx Fortius", new TacxCafFileReader());
|
|
|
|
/** a simple struct which describes the header of a block */
|
|
typedef struct header {
|
|
/** fingerprint of the block, see the list of block fingerprints */
|
|
qint16 fingerprint;
|
|
/** nr of records in the block */
|
|
qint32 recordCount;
|
|
/** size of each record */
|
|
qint32 recordSize;
|
|
} header_t;
|
|
|
|
/**
|
|
Read header block, containing general information about file, among which the
|
|
fingerprint of the file. Return the version of the file, 100 or 110.
|
|
*/
|
|
static qint16 readHeaderBlock(const QByteArray& block, QStringList& errors);
|
|
|
|
/**
|
|
Read all data blocks and return a RideFile.
|
|
*/
|
|
static RideFile* readBlocks(const QByteArray& blocks, qint16 version, QStringList& errors);
|
|
|
|
/**
|
|
read the header of one block
|
|
*/
|
|
static header_t readBlockHeader(const QByteArray& blockHeader);
|
|
|
|
/**
|
|
Read general information about the ride
|
|
*/
|
|
static bool readRideInformationBlock(RideFile* rideFile, const QByteArray& block);
|
|
|
|
/**
|
|
Read the actual data points
|
|
*/
|
|
static bool readRideData(RideFile *rideFile, const QByteArray& block, const int nrOfRecords, const qint16 version);
|
|
|
|
static qint8 readByteFromByteArray(const QByteArray& block);
|
|
static qint16 readShortFromByteArray(const QByteArray& block);
|
|
static quint16 readUnsignedShortFromByteArray(const QByteArray& block);
|
|
static qint32 readIntFromByteArray(const QByteArray& block);
|
|
static float readFloatFromByteArray(const QByteArray& block);
|
|
|
|
static const int TACX_CAF_FILE_FINGERPRINT = 3000;
|
|
static const int TACX_HEADER_BLOCK_SIZE = 8;
|
|
static const int TACX_BLOCK_INFO_HEADER_SIZE = 12;
|
|
static const int TACX_RIDE_DATA_BLOCK_SIZE = 10;
|
|
|
|
// Block fingerprints
|
|
static const int TACX_RIDE_INFORMATION_BLOCK = 3010;
|
|
static const int TACX_RIDE_DATA_BLOCK = 3020;
|
|
|
|
static const QString TACX_FORTIUS_DEVICE_TYPE = "Tacx Fortius";
|
|
|
|
RideFile *TacxCafFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const {
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
errors << ("Could not open ride file: \""
|
|
+ file.fileName() + "\"");
|
|
return NULL;
|
|
}
|
|
|
|
QByteArray bytes = file.readAll();
|
|
file.close();
|
|
|
|
qint16 version = readHeaderBlock(bytes.left(TACX_HEADER_BLOCK_SIZE), errors);
|
|
if (version == 0)
|
|
return NULL;
|
|
|
|
return readBlocks(bytes.mid(TACX_HEADER_BLOCK_SIZE), version, errors);
|
|
}
|
|
|
|
qint16 readHeaderBlock(const QByteArray& block, QStringList& errors) {
|
|
|
|
if (block.size() != 8) {
|
|
errors << "Block size is not 8, is this a valid Tacx run file?";
|
|
return 0;
|
|
}
|
|
|
|
qint16 fingerprint = readShortFromByteArray(block);
|
|
|
|
if (fingerprint != TACX_CAF_FILE_FINGERPRINT) {
|
|
errors << ("This is not a Tacx run file");
|
|
return 0;
|
|
}
|
|
qint16 version = readShortFromByteArray(block.mid(2));
|
|
return version;
|
|
}
|
|
|
|
|
|
RideFile* readBlocks(const QByteArray& blocks, const qint16 version, QStringList& /*errors*/) {
|
|
RideFile* rideFile = new RideFile();
|
|
|
|
rideFile->setDeviceType(TACX_FORTIUS_DEVICE_TYPE);
|
|
rideFile->setFileFormat("Tacx Fortius (caf)");
|
|
|
|
|
|
QByteArray remainingBytes = blocks;
|
|
while(!remainingBytes.isEmpty()) {
|
|
const QByteArray blockHeaderBytes = remainingBytes.left(12);
|
|
header_t blockHeader = readBlockHeader(blockHeaderBytes);
|
|
|
|
switch(blockHeader.fingerprint) {
|
|
case TACX_RIDE_INFORMATION_BLOCK:
|
|
readRideInformationBlock(rideFile, remainingBytes.mid(12));
|
|
break;
|
|
case TACX_RIDE_DATA_BLOCK:
|
|
readRideData(rideFile, remainingBytes.mid(12), blockHeader.recordCount, version);
|
|
break;
|
|
}
|
|
|
|
int skipBytes = 12 + blockHeader.recordCount * blockHeader.recordSize;
|
|
|
|
remainingBytes = remainingBytes.mid(skipBytes);
|
|
}
|
|
|
|
return rideFile;
|
|
}
|
|
|
|
header_t readBlockHeader(const QByteArray& blockHeader) {
|
|
Q_ASSERT(blockHeader.size() >= 12);
|
|
header_t header;
|
|
header.fingerprint = readShortFromByteArray(blockHeader);
|
|
header.recordCount = readIntFromByteArray(blockHeader.mid(4, 4));
|
|
header.recordSize = readIntFromByteArray(blockHeader.right(4));
|
|
|
|
return header;
|
|
}
|
|
|
|
bool readRideInformationBlock(RideFile* rideFile, const QByteArray& block) {
|
|
// year = block[0-2]
|
|
qint16 year = readShortFromByteArray(block);
|
|
qint16 month = readShortFromByteArray(block.mid(2));
|
|
qint16 day = readShortFromByteArray(block.mid(6));
|
|
qint16 hour = readShortFromByteArray(block.mid(8));
|
|
qint16 minute = readShortFromByteArray(block.mid(10));
|
|
qint16 second = readShortFromByteArray(block.mid(12));
|
|
|
|
rideFile->setStartTime(QDateTime(QDate(year, month, day), QTime(hour, minute, second)));
|
|
rideFile->setRecIntSecs(readFloatFromByteArray(block.mid(16)));
|
|
|
|
return true;
|
|
}
|
|
|
|
struct RideFilePoint readSinglePoint(const QByteArray& record, const double& timeInSeconds, const double& recordingIntervalInSeconds,
|
|
const float& startDistance = 0.0f, const float& lastDistance = 0.0f) {
|
|
float distance = readFloatFromByteArray(record);
|
|
float relativeDistance = distance - startDistance;
|
|
|
|
quint8 heartRate = readByteFromByteArray(record.mid(4));
|
|
quint8 cadence = readByteFromByteArray(record.mid(5));
|
|
quint16 powerX10 = readUnsignedShortFromByteArray(record.mid(6));
|
|
double power = powerX10 / 10.0;
|
|
|
|
double speed = (relativeDistance - lastDistance) / recordingIntervalInSeconds;
|
|
|
|
struct RideFilePoint point(timeInSeconds, cadence, heartRate, relativeDistance / 1000.0, speed * 3.6, 0.0, power, 0.0, 0.0, 0.0, 0.0, 0.0, RideFile::NoTemp, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0);
|
|
|
|
return point;
|
|
}
|
|
|
|
bool readRideData(RideFile *rideFile, const QByteArray& block, const int nrOfRecords, const qint16 version) {
|
|
const int dataRecordSize = (version == 100) ? TACX_RIDE_DATA_BLOCK_SIZE : TACX_RIDE_DATA_BLOCK_SIZE + 8;
|
|
|
|
double seconds = rideFile->recIntSecs();
|
|
|
|
struct RideFilePoint firstDataPoint = readSinglePoint(block, seconds, rideFile->recIntSecs());
|
|
float startDistance = firstDataPoint.km * 1000.0;
|
|
float lastDistance = 0.0f;
|
|
for(int i = 0; i < nrOfRecords; i++, seconds += rideFile->recIntSecs()) {
|
|
const QByteArray& record = block.mid(i * dataRecordSize);
|
|
struct RideFilePoint nextDataPoint = readSinglePoint(record, seconds, rideFile->recIntSecs(), startDistance, lastDistance);
|
|
lastDistance = nextDataPoint.km * 1000.0;
|
|
|
|
rideFile->appendPoint(nextDataPoint.secs, nextDataPoint.cad,
|
|
nextDataPoint.hr, nextDataPoint.km,
|
|
nextDataPoint.kph, nextDataPoint.nm,
|
|
nextDataPoint.watts, nextDataPoint.alt,
|
|
nextDataPoint.lon, nextDataPoint.lat,
|
|
nextDataPoint.headwind, 0.0, RideFile::NoTemp, 0.0,
|
|
0.0,0.0,0.0,0.0,0.0,0.0,nextDataPoint.interval);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
qint8 readByteFromByteArray(const QByteArray& block) {
|
|
Q_ASSERT(block.size() >= 1);
|
|
qint8 value = 0;
|
|
memcpy(&value, block, 1);
|
|
return value;
|
|
}
|
|
|
|
quint16 readUnsignedShortFromByteArray(const QByteArray& block) {
|
|
Q_ASSERT(block.size() >= 2);
|
|
quint16 value = 0;
|
|
memcpy(&value, block, 2);
|
|
return value;
|
|
}
|
|
|
|
qint16 readShortFromByteArray(const QByteArray& block) {
|
|
Q_ASSERT(block.size() >= 2);
|
|
qint16 value = 0;
|
|
memcpy(&value, block, 2);
|
|
return value;
|
|
}
|
|
|
|
qint32 readIntFromByteArray(const QByteArray& block) {
|
|
Q_ASSERT(block.size() >= 4);
|
|
qint32 value = 0;
|
|
memcpy(&value, block, 4);
|
|
return value;
|
|
}
|
|
|
|
float readFloatFromByteArray(const QByteArray& block) {
|
|
Q_ASSERT(block.size() >= 4);
|
|
float value = 0;
|
|
memcpy(&value, block, 4);
|
|
return value;
|
|
}
|