From 07e086ce44859dc33d52804faf81301bc8d27826 Mon Sep 17 00:00:00 2001 From: Ilja Booij Date: Sun, 26 Sep 2010 18:48:44 +0200 Subject: [PATCH] Support Tacx CAF Ride File Format Initial version of Tacx Caf file importer. TacxCafRideFile.cpp added to qmake file. Fixed parsing of heart rate value, Heart rate and cadence should have used quint8 instead of qint8, because they're unsigned. Fixed #143. --- src/TacxCafRideFile.cpp | 222 ++++++++++++++++++++++++++++++++++++++++ src/src.pro | 1 + 2 files changed, 223 insertions(+) create mode 100644 src/TacxCafRideFile.cpp diff --git a/src/TacxCafRideFile.cpp b/src/TacxCafRideFile.cpp new file mode 100644 index 000000000..6128477b0 --- /dev/null +++ b/src/TacxCafRideFile.cpp @@ -0,0 +1,222 @@ +/* + * 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 +#include +#include +#include +#include + +struct TacxCafFileReader : public RideFileReader { + virtual RideFile *openRideFile(QFile &file, QStringList &errors) const; +}; + +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 + */ +static bool readHeaderBlock(const QByteArray& block, QStringList& errors); + +/** + Read all data blocks and return a RideFile. + */ +static RideFile* readBlocks(const QByteArray& blocks, 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); + +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) const { + if (!file.open(QFile::ReadOnly)) { + errors << ("Could not open ride file: \"" + + file.fileName() + "\""); + return NULL; + } + + QByteArray bytes = file.readAll(); + file.close(); + + if (!readHeaderBlock(bytes.left(TACX_HEADER_BLOCK_SIZE), errors)) + return NULL; + + return readBlocks(bytes.mid(TACX_HEADER_BLOCK_SIZE), errors); +} + +bool readHeaderBlock(const QByteArray& block, QStringList& errors) { + assert(block.size() == 8); + + short fingerprint = readShortFromByteArray(block); + + if (fingerprint != TACX_CAF_FILE_FINGERPRINT) { + errors << ("This is not a Tacx run file"); + return false; + } + return true; +} + + +RideFile* readBlocks(const QByteArray& blocks, QStringList& /*errors*/) { + RideFile* rideFile = new RideFile(); + + rideFile->setDeviceType(TACX_FORTIUS_DEVICE_TYPE); + + 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); + 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; +} + +bool readRideData(RideFile *rideFile, const QByteArray& block, const int nrOfRecords) { + double seconds = rideFile->recIntSecs(); + float lastDistance = 0.0f; + for(int i = 0; i < nrOfRecords; i++, seconds += rideFile->recIntSecs()) { + const QByteArray& record = block.mid(i * TACX_RIDE_DATA_BLOCK_SIZE); + + float distance = readFloatFromByteArray(record); + 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 = (distance - lastDistance) / rideFile->recIntSecs(); + + rideFile->appendPoint(seconds, cadence, heartRate, distance / 1000.0, speed * 3.6, 0.0, power, 0.0, 0.0, 0.0, 0.0, 0); + lastDistance = distance; + } + + 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; +} diff --git a/src/src.pro b/src/src.pro index e9ee03849..81855dd26 100644 --- a/src/src.pro +++ b/src/src.pro @@ -284,6 +284,7 @@ SOURCES += \ SrdRideFile.cpp \ SrmRideFile.cpp \ StressCalculator.cpp \ + TacxCafRideFile.cpp \ TcxParser.cpp \ TcxRideFile.cpp \ TxtRideFile.cpp \