2016-10-31 22:59:08 +01:00
|
|
|
#include <cstring>
|
|
|
|
#include <QtEndian>
|
2016-11-05 17:32:15 +01:00
|
|
|
#include "assert.h"
|
2016-10-31 22:59:08 +01:00
|
|
|
#include "fitparser.h"
|
|
|
|
|
|
|
|
|
|
|
|
const quint32 FIT_MAGIC = 0x5449462E; // .FIT
|
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
#define RECORD_MESSAGE 20
|
|
|
|
#define TIMESTAMP_FIELD 253
|
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
FITParser::FITParser(QList<TrackData> &tracks, QList<RouteData> &routes,
|
|
|
|
QList<Waypoint> &waypoints) : Parser(tracks, routes, waypoints)
|
|
|
|
{
|
|
|
|
memset(_defs, 0, sizeof(_defs));
|
2016-11-11 21:37:35 +01:00
|
|
|
|
|
|
|
_device = 0;
|
|
|
|
_endian = 0;
|
|
|
|
_timestamp = 0;
|
|
|
|
_len = 0;
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
void FITParser::clearDefinitions()
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
2016-11-02 17:33:54 +01:00
|
|
|
for (int i = 0; i < 16; i++) {
|
2016-11-03 08:02:28 +01:00
|
|
|
if (_defs[i].fields)
|
2016-10-31 22:59:08 +01:00
|
|
|
delete[] _defs[i].fields;
|
2016-11-03 08:02:28 +01:00
|
|
|
if (_defs[i].devFields)
|
|
|
|
delete[] _defs[i].devFields;
|
2016-11-02 17:33:54 +01:00
|
|
|
}
|
2016-11-03 08:02:28 +01:00
|
|
|
|
|
|
|
memset(_defs, 0, sizeof(_defs));
|
2016-11-02 17:33:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void FITParser::warning(const char *text) const
|
|
|
|
{
|
|
|
|
const QFile *file = static_cast<QFile *>(_device);
|
|
|
|
fprintf(stderr, "%s:%d: %s\n", qPrintable(file->fileName()),
|
|
|
|
_len, text);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::readData(char *data, size_t size)
|
|
|
|
{
|
|
|
|
qint64 n;
|
|
|
|
|
|
|
|
n = _device->read(data, size);
|
|
|
|
if (n < 0) {
|
|
|
|
_errorString = "I/O error";
|
|
|
|
return false;
|
|
|
|
} else if ((size_t)n < size) {
|
|
|
|
_errorString = "Premature end of data";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<class T> bool FITParser::readValue(T &val)
|
|
|
|
{
|
|
|
|
T data;
|
|
|
|
|
|
|
|
if (!readData((char*)&data, sizeof(T)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
_len -= sizeof(T);
|
|
|
|
|
|
|
|
if (sizeof(T) > 1) {
|
|
|
|
if (_endian)
|
|
|
|
val = qFromBigEndian(data);
|
|
|
|
else
|
|
|
|
val = qFromLittleEndian(data);
|
|
|
|
} else
|
|
|
|
val = data;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::skipValue(size_t size)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
quint8 val;
|
|
|
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
|
|
if (!readValue(val))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::parseDefinitionMessage(quint8 header)
|
|
|
|
{
|
|
|
|
int local_id = header & 0x0f;
|
|
|
|
MessageDefinition* def = &_defs[local_id];
|
|
|
|
quint8 i;
|
|
|
|
|
|
|
|
|
2016-11-03 01:09:41 +01:00
|
|
|
if (def->fields) {
|
2016-10-31 22:59:08 +01:00
|
|
|
delete[] def->fields;
|
2016-11-03 01:09:41 +01:00
|
|
|
def->fields = 0;
|
|
|
|
}
|
2016-11-03 08:02:28 +01:00
|
|
|
if (def->devFields) {
|
|
|
|
delete[] def->devFields;
|
|
|
|
def->devFields = 0;
|
|
|
|
}
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
// reserved/unused
|
|
|
|
if (!readValue(i))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// endianness
|
|
|
|
if (!readValue(def->endian))
|
|
|
|
return false;
|
|
|
|
if (def->endian > 1) {
|
|
|
|
_errorString = "Bad endian field";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
_endian = def->endian;
|
|
|
|
|
|
|
|
// global message number
|
2016-11-03 07:37:17 +01:00
|
|
|
if (!readValue(def->globalId))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// number of records
|
2016-11-03 07:37:17 +01:00
|
|
|
if (!readValue(def->numFields))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// definition records
|
2016-11-03 07:37:17 +01:00
|
|
|
def->fields = new Field[def->numFields];
|
|
|
|
for (i = 0; i < def->numFields; i++) {
|
2016-11-05 17:32:15 +01:00
|
|
|
STATIC_ASSERT(sizeof(def->fields[i]) == 3);
|
2016-10-31 22:59:08 +01:00
|
|
|
if (!readData((char*)&(def->fields[i]), sizeof(def->fields[i])))
|
|
|
|
return false;
|
|
|
|
_len -= sizeof(def->fields[i]);
|
|
|
|
}
|
|
|
|
|
2016-11-03 08:02:28 +01:00
|
|
|
// developer definition records
|
|
|
|
if (header & 0x20) {
|
|
|
|
if (!readValue(def->numDevFields))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
def->devFields = new Field[def->numDevFields];
|
|
|
|
for (i = 0; i < def->numDevFields; i++) {
|
2016-11-05 17:32:15 +01:00
|
|
|
STATIC_ASSERT(sizeof(def->devFields[i]) == 3);
|
2016-11-03 08:02:28 +01:00
|
|
|
if (!readData((char*)&(def->devFields[i]),
|
|
|
|
sizeof(def->devFields[i])))
|
|
|
|
return false;
|
|
|
|
_len -= sizeof(def->devFields[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
bool FITParser::readField(Field *f, quint32 &val)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
quint8 v8 = (quint8)-1;
|
|
|
|
quint16 v16 = (quint16)-1;
|
|
|
|
bool ret;
|
|
|
|
|
|
|
|
val = (quint32)-1;
|
|
|
|
|
|
|
|
switch (f->type) {
|
|
|
|
case 1: // sint8
|
|
|
|
case 2: // uint8
|
|
|
|
if (f->size == 1) {
|
|
|
|
ret = readValue(v8);
|
|
|
|
val = v8;
|
|
|
|
} else
|
|
|
|
ret = skipValue(f->size);
|
|
|
|
break;
|
|
|
|
case 0x83: // sint16
|
|
|
|
case 0x84: // uint16
|
|
|
|
if (f->size == 2) {
|
|
|
|
ret = readValue(v16);
|
|
|
|
val = v16;
|
|
|
|
} else
|
|
|
|
ret = skipValue(f->size);
|
|
|
|
break;
|
|
|
|
case 0x85: // sint32
|
|
|
|
case 0x86: // uint32
|
|
|
|
if (f->size == 4)
|
|
|
|
ret = readValue(val);
|
|
|
|
else
|
|
|
|
ret = skipValue(f->size);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = skipValue(f->size);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::parseData(MessageDefinition *def, quint8 offset)
|
|
|
|
{
|
|
|
|
Field *field;
|
|
|
|
quint32 timestamp = _timestamp + offset;
|
|
|
|
quint32 val;
|
|
|
|
Trackpoint trackpoint;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
2016-11-03 08:02:28 +01:00
|
|
|
if (!def->fields && !def->devFields) {
|
2016-11-02 17:33:54 +01:00
|
|
|
_errorString = "Undefined data message";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
_endian = def->endian;
|
|
|
|
|
2016-11-03 07:37:17 +01:00
|
|
|
for (i = 0; i < def->numFields; i++) {
|
2016-10-31 22:59:08 +01:00
|
|
|
field = &def->fields[i];
|
|
|
|
if (!readField(field, val))
|
|
|
|
return false;
|
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
if (field->id == TIMESTAMP_FIELD)
|
2016-10-31 22:59:08 +01:00
|
|
|
_timestamp = timestamp = val;
|
2016-11-03 07:37:17 +01:00
|
|
|
else if (def->globalId == RECORD_MESSAGE) {
|
2016-10-31 22:59:08 +01:00
|
|
|
switch (field->id) {
|
|
|
|
case 0:
|
|
|
|
if (val != 0x7fffffff)
|
|
|
|
trackpoint.rcoordinates().setLat(
|
2016-11-01 00:14:44 +01:00
|
|
|
((qint32)val / (double)0x7fffffff) * 180);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
if (val != 0x7fffffff)
|
|
|
|
trackpoint.rcoordinates().setLon(
|
2016-11-01 00:14:44 +01:00
|
|
|
((qint32)val / (double)0x7fffffff) * 180);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
if (val != 0xffff)
|
|
|
|
trackpoint.setElevation((val / 5.0) - 500);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
if (val != 0xff)
|
|
|
|
trackpoint.setHeartRate(val);
|
|
|
|
break;
|
2016-11-06 03:28:08 +01:00
|
|
|
case 4:
|
|
|
|
if (val != 0xff)
|
|
|
|
trackpoint.setCadence(val);
|
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
case 6:
|
|
|
|
if (val != 0xffff)
|
|
|
|
trackpoint.setSpeed(val / 1000.0f);
|
2016-11-06 03:28:08 +01:00
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
if (val != 0xffff)
|
|
|
|
trackpoint.setPower(val);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 13:
|
|
|
|
if (val != 0x7f)
|
2016-11-01 08:24:05 +01:00
|
|
|
trackpoint.setTemperature((qint8)val);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-03 08:02:28 +01:00
|
|
|
for (i = 0; i < def->numDevFields; i++) {
|
|
|
|
field = &def->devFields[i];
|
|
|
|
if (!readField(field, val))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-03 07:37:17 +01:00
|
|
|
if (def->globalId == RECORD_MESSAGE) {
|
2016-11-02 17:33:54 +01:00
|
|
|
if (trackpoint.coordinates().isValid()) {
|
|
|
|
trackpoint.setTimestamp(QDateTime::fromTime_t(timestamp
|
|
|
|
+ 631065600));
|
|
|
|
_tracks.last().append(trackpoint);
|
|
|
|
} else {
|
|
|
|
if (trackpoint.coordinates().isNull())
|
|
|
|
warning("Missing coordinates");
|
|
|
|
else {
|
|
|
|
_errorString = "Invalid coordinates";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::parseDataMessage(quint8 header)
|
|
|
|
{
|
2016-11-02 17:33:54 +01:00
|
|
|
int local_id = header & 0xf;
|
2016-10-31 22:59:08 +01:00
|
|
|
MessageDefinition* def = &_defs[local_id];
|
|
|
|
return parseData(def, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::parseCompressedMessage(quint8 header)
|
|
|
|
{
|
|
|
|
int local_id = (header >> 5) & 3;
|
|
|
|
MessageDefinition* def = &_defs[local_id];
|
|
|
|
return parseData(def, header & 0x1f);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::parseRecord()
|
|
|
|
{
|
|
|
|
quint8 header;
|
|
|
|
|
|
|
|
if (!readValue(header))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (header & 0x80)
|
|
|
|
return parseCompressedMessage(header);
|
|
|
|
else if (header & 0x40)
|
|
|
|
return parseDefinitionMessage(header);
|
|
|
|
else
|
|
|
|
return parseDataMessage(header);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::parseHeader()
|
|
|
|
{
|
|
|
|
FileHeader hdr;
|
|
|
|
quint16 crc;
|
|
|
|
qint64 len;
|
|
|
|
|
2016-11-05 17:32:15 +01:00
|
|
|
STATIC_ASSERT(sizeof(hdr) == 12);
|
2016-10-31 22:59:08 +01:00
|
|
|
len = _device->read((char*)&hdr, sizeof(hdr));
|
|
|
|
if (len < 0) {
|
|
|
|
_errorString = "I/O error";
|
|
|
|
return false;
|
|
|
|
} else if ((size_t)len < sizeof(hdr)
|
|
|
|
|| hdr.magic != qToLittleEndian(FIT_MAGIC)) {
|
|
|
|
_errorString = "Not a FIT file";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-11-03 07:37:17 +01:00
|
|
|
_len = qFromLittleEndian(hdr.dataSize);
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2016-11-03 07:37:17 +01:00
|
|
|
if (hdr.headerSize > sizeof(hdr))
|
2016-10-31 22:59:08 +01:00
|
|
|
if (!readData((char *)&crc, sizeof(crc)))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FITParser::loadFile(QFile *file)
|
|
|
|
{
|
2016-11-11 21:37:35 +01:00
|
|
|
bool ret = true;
|
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
_device = file;
|
|
|
|
_endian = 0;
|
|
|
|
_timestamp = 0;
|
|
|
|
|
|
|
|
if (!parseHeader())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
_tracks.append(TrackData());
|
|
|
|
|
|
|
|
while (_len)
|
2016-11-02 17:33:54 +01:00
|
|
|
if ((ret = parseRecord()) == false)
|
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
clearDefinitions();
|
|
|
|
|
|
|
|
return ret;
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|