2016-10-31 22:59:08 +01:00
|
|
|
#include <QtEndian>
|
2017-11-26 18:54:03 +01:00
|
|
|
#include "common/staticassert.h"
|
2016-10-31 22:59:08 +01:00
|
|
|
#include "fitparser.h"
|
|
|
|
|
|
|
|
|
2018-07-03 01:29:14 +02:00
|
|
|
#define FIT_MAGIC 0x5449462E // .FIT
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
#define RECORD_MESSAGE 20
|
2018-07-03 01:29:14 +02:00
|
|
|
#define EVENT_MESSAGE 21
|
2020-04-08 00:54:35 +02:00
|
|
|
#define COURSE_POINT 32
|
2016-11-02 17:33:54 +01:00
|
|
|
#define TIMESTAMP_FIELD 253
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
class Event {
|
|
|
|
public:
|
|
|
|
Event() : id(0), type(0), data(0) {}
|
|
|
|
|
2018-07-03 01:29:14 +02:00
|
|
|
quint8 id;
|
|
|
|
quint8 type;
|
|
|
|
quint32 data;
|
|
|
|
};
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
struct FileHeader {
|
|
|
|
quint8 headerSize;
|
|
|
|
quint8 protocolVersion;
|
|
|
|
quint16 profileVersion;
|
|
|
|
quint32 dataSize;
|
|
|
|
quint32 magic;
|
|
|
|
};
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
struct FITParser::Field {
|
|
|
|
quint8 id;
|
|
|
|
quint8 size;
|
|
|
|
quint8 type;
|
|
|
|
};
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
class FITParser::MessageDefinition {
|
|
|
|
public:
|
|
|
|
MessageDefinition() : endian(0), globalId(0), numFields(0), fields(0),
|
|
|
|
numDevFields(0), devFields(0) {}
|
|
|
|
~MessageDefinition() {delete[] fields; delete[] devFields;}
|
|
|
|
|
|
|
|
quint8 endian;
|
|
|
|
quint16 globalId;
|
|
|
|
quint8 numFields;
|
2018-07-03 20:52:13 +02:00
|
|
|
Field *fields;
|
2018-07-03 19:08:46 +02:00
|
|
|
quint8 numDevFields;
|
2018-07-03 20:52:13 +02:00
|
|
|
Field *devFields;
|
2018-07-03 19:08:46 +02:00
|
|
|
};
|
2016-11-03 08:02:28 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
class FITParser::CTX {
|
|
|
|
public:
|
2020-04-08 00:54:35 +02:00
|
|
|
CTX(QFile *file, QVector<Waypoint> &waypoints)
|
|
|
|
: file(file), waypoints(waypoints), len(0), endian(0), timestamp(0),
|
2020-11-10 20:07:46 +01:00
|
|
|
ratio(NAN) {}
|
2018-07-03 19:08:46 +02:00
|
|
|
|
|
|
|
QFile *file;
|
2020-04-08 00:54:35 +02:00
|
|
|
QVector<Waypoint> &waypoints;
|
2018-07-03 19:08:46 +02:00
|
|
|
quint32 len;
|
|
|
|
quint8 endian;
|
2020-11-10 20:07:46 +01:00
|
|
|
quint32 timestamp;
|
2018-07-03 20:52:13 +02:00
|
|
|
MessageDefinition defs[16];
|
2018-07-03 19:08:46 +02:00
|
|
|
qreal ratio;
|
|
|
|
Trackpoint trackpoint;
|
2019-02-11 23:28:08 +01:00
|
|
|
SegmentData segment;
|
2018-07-03 19:08:46 +02:00
|
|
|
};
|
2016-11-02 17:33:54 +01:00
|
|
|
|
2020-04-08 00:54:35 +02:00
|
|
|
static QMap<int, QString> coursePointDescInit()
|
|
|
|
{
|
|
|
|
QMap<int, QString> map;
|
|
|
|
|
|
|
|
map.insert(1, "Summit");
|
|
|
|
map.insert(2, "Valley");
|
|
|
|
map.insert(3, "Water");
|
|
|
|
map.insert(4, "Food");
|
|
|
|
map.insert(5, "Danger");
|
|
|
|
map.insert(6, "Left");
|
|
|
|
map.insert(7, "Right");
|
|
|
|
map.insert(8, "Straight");
|
|
|
|
map.insert(9, "First aid");
|
|
|
|
map.insert(10, "Fourth category");
|
|
|
|
map.insert(11, "Third category");
|
|
|
|
map.insert(12, "Second category");
|
|
|
|
map.insert(13, "First category");
|
|
|
|
map.insert(14, "Hors category");
|
|
|
|
map.insert(15, "Sprint");
|
|
|
|
map.insert(16, "Left fork");
|
|
|
|
map.insert(17, "Right fork");
|
|
|
|
map.insert(18, "Middle fork");
|
|
|
|
map.insert(19, "Slight left");
|
|
|
|
map.insert(20, "Sharp left");
|
|
|
|
map.insert(21, "Slight right");
|
|
|
|
map.insert(22, "Sharp right");
|
|
|
|
map.insert(23, "U-Turn");
|
|
|
|
map.insert(24, "Segment start");
|
|
|
|
map.insert(25, "Segment end");
|
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
|
|
|
static QMap<int, QString> coursePointDesc = coursePointDescInit();
|
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::readData(QFile *file, char *data, size_t size)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
qint64 n;
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
n = file->read(data, size);
|
2016-10-31 22:59:08 +01:00
|
|
|
if (n < 0) {
|
|
|
|
_errorString = "I/O error";
|
|
|
|
return false;
|
|
|
|
} else if ((size_t)n < size) {
|
|
|
|
_errorString = "Premature end of data";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
template<class T> bool FITParser::readValue(CTX &ctx, T &val)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
2020-04-09 10:17:30 +02:00
|
|
|
if (!readData(ctx.file, (char*)&val, sizeof(T)))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.len -= sizeof(T);
|
2020-04-09 10:17:30 +02:00
|
|
|
if (sizeof(T) > 1)
|
|
|
|
val = (ctx.endian) ? qFromBigEndian(val) : qFromLittleEndian(val);
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-19 10:44:12 +02:00
|
|
|
bool FITParser::skipValue(CTX &ctx, quint8 size)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.len -= size;
|
|
|
|
return ctx.file->seek(ctx.file->pos() + size);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::parseDefinitionMessage(CTX &ctx, quint8 header)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
int local_id = header & 0x0f;
|
2018-07-03 19:08:46 +02:00
|
|
|
MessageDefinition *def = &(ctx.defs[local_id]);
|
2016-10-31 22:59:08 +01:00
|
|
|
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
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readValue(ctx, i))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// endianness
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readValue(ctx, def->endian))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
if (def->endian > 1) {
|
|
|
|
_errorString = "Bad endian field";
|
|
|
|
return false;
|
|
|
|
}
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.endian = def->endian;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
// global message number
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readValue(ctx, def->globalId))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// number of records
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readValue(ctx, 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);
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readData(ctx.file, (char*)&(def->fields[i]),
|
|
|
|
sizeof(def->fields[i])))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.len -= sizeof(def->fields[i]);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2016-11-03 08:02:28 +01:00
|
|
|
// developer definition records
|
|
|
|
if (header & 0x20) {
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readValue(ctx, def->numDevFields))
|
2016-11-03 08:02:28 +01:00
|
|
|
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);
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readData(ctx.file, (char*)&(def->devFields[i]),
|
2016-11-03 08:02:28 +01:00
|
|
|
sizeof(def->devFields[i])))
|
|
|
|
return false;
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.len -= sizeof(def->devFields[i]);
|
2016-11-03 08:02:28 +01:00
|
|
|
}
|
2018-07-02 18:50:13 +02:00
|
|
|
} else
|
|
|
|
def->numDevFields = 0;
|
2016-11-03 08:02:28 +01:00
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-04-08 22:28:35 +02:00
|
|
|
bool FITParser::readField(CTX &ctx, Field *field, QVariant &val, bool &valid)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
bool ret;
|
|
|
|
|
2020-04-08 22:28:35 +02:00
|
|
|
#define VAL(type, inval) \
|
|
|
|
{type var; \
|
|
|
|
if (field->size == sizeof(var)) { \
|
|
|
|
ret = readValue(ctx, var); \
|
|
|
|
val = var; \
|
|
|
|
valid = (var != (inval)); \
|
|
|
|
} else { \
|
|
|
|
ret = skipValue(ctx, field->size); \
|
|
|
|
valid = false; \
|
|
|
|
}}
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
switch (field->type) {
|
2016-10-31 22:59:08 +01:00
|
|
|
case 1: // sint8
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(qint8, 0x7fU);
|
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
case 2: // uint8
|
2020-04-08 22:28:35 +02:00
|
|
|
case 0: // enum
|
|
|
|
VAL(quint8, 0xffU);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
2020-04-08 00:54:35 +02:00
|
|
|
case 7: // UTF8 nul terminated string
|
|
|
|
{QByteArray ba(ctx.file->read(field->size));
|
|
|
|
ctx.len -= field->size;
|
|
|
|
ret = (ba.size() == field->size);
|
2020-04-08 22:28:35 +02:00
|
|
|
val = ret ? ba : QString();
|
|
|
|
valid = !ba.isEmpty();}
|
2020-04-08 00:54:35 +02:00
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
case 0x83: // sint16
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(qint16, 0x7fffU);
|
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
case 0x84: // uint16
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(quint16, 0xffffU);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 0x85: // sint32
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(qint32, 0x7fffffffU);
|
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
case 0x86: // uint32
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(quint32, 0xffffffffU);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
default:
|
2018-07-03 19:08:46 +02:00
|
|
|
ret = skipValue(ctx, field->size);
|
2020-04-08 22:28:35 +02:00
|
|
|
valid = false;
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::parseData(CTX &ctx, const MessageDefinition *def)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
Field *field;
|
2020-04-08 00:54:35 +02:00
|
|
|
QVariant val;
|
2020-04-08 22:28:35 +02:00
|
|
|
bool valid;
|
|
|
|
Event event;
|
|
|
|
Waypoint waypoint;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.endian = def->endian;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
for (int i = 0; i < def->numFields; i++) {
|
2016-10-31 22:59:08 +01:00
|
|
|
field = &def->fields[i];
|
2020-04-08 22:28:35 +02:00
|
|
|
if (!readField(ctx, field, val, valid))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
2020-04-08 22:28:35 +02:00
|
|
|
if (!valid)
|
|
|
|
continue;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2016-11-02 17:33:54 +01:00
|
|
|
if (field->id == TIMESTAMP_FIELD)
|
2020-04-08 00:54:35 +02:00
|
|
|
ctx.timestamp = val.toUInt();
|
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:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.rcoordinates().setLat(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 1:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.rcoordinates().setLon(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 2:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setElevation((val.toUInt() / 5.0) - 500);
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setHeartRate(val.toUInt());
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
2016-11-06 03:28:08 +01:00
|
|
|
case 4:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setCadence(val.toUInt());
|
2016-11-06 03:28:08 +01:00
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
case 6:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setSpeed(val.toUInt() / 1000.0f);
|
2016-11-06 03:28:08 +01:00
|
|
|
break;
|
|
|
|
case 7:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setPower(val.toUInt());
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
|
|
|
case 13:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setTemperature(val.toInt());
|
2016-10-31 22:59:08 +01:00
|
|
|
break;
|
2019-09-29 11:24:20 +02:00
|
|
|
case 73:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setSpeed(val.toUInt() / 1000.0f);
|
2019-09-29 11:24:20 +02:00
|
|
|
break;
|
|
|
|
case 78:
|
2020-04-08 22:28:35 +02:00
|
|
|
ctx.trackpoint.setElevation((val.toUInt() / 5.0) - 500);
|
2019-09-29 11:24:20 +02:00
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
2018-07-03 01:29:14 +02:00
|
|
|
} else if (def->globalId == EVENT_MESSAGE) {
|
|
|
|
switch (field->id) {
|
|
|
|
case 0:
|
2020-04-08 00:54:35 +02:00
|
|
|
event.id = val.toUInt();
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
event.type = val.toUInt();
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
event.data = val.toUInt();
|
2018-07-03 01:29:14 +02:00
|
|
|
break;
|
2020-04-08 00:54:35 +02:00
|
|
|
}
|
|
|
|
} else if (def->globalId == COURSE_POINT) {
|
|
|
|
switch (field->id) {
|
2018-07-03 01:29:14 +02:00
|
|
|
case 1:
|
2020-04-08 22:28:35 +02:00
|
|
|
waypoint.setTimestamp(QDateTime::fromTime_t(val.toUInt()
|
2020-04-08 00:54:35 +02:00
|
|
|
+ 631065600));
|
|
|
|
break;
|
|
|
|
case 2:
|
2020-04-08 22:28:35 +02:00
|
|
|
waypoint.rcoordinates().setLat(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
2018-07-03 01:29:14 +02:00
|
|
|
break;
|
|
|
|
case 3:
|
2020-04-08 22:28:35 +02:00
|
|
|
waypoint.rcoordinates().setLon(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
2020-04-08 00:54:35 +02:00
|
|
|
break;
|
|
|
|
case 5:
|
2020-04-08 22:28:35 +02:00
|
|
|
waypoint.setDescription(coursePointDesc.value(val.toUInt()));
|
2020-04-08 00:54:35 +02:00
|
|
|
break;
|
|
|
|
case 6:
|
2020-04-08 22:28:35 +02:00
|
|
|
waypoint.setName(val.toString());
|
2018-07-03 01:29:14 +02:00
|
|
|
break;
|
|
|
|
}
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
for (int i = 0; i < def->numDevFields; i++) {
|
2016-11-03 08:02:28 +01:00
|
|
|
field = &def->devFields[i];
|
2020-04-08 22:28:35 +02:00
|
|
|
if (!readField(ctx, field, val, valid))
|
2016-11-03 08:02:28 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
|
2018-07-03 01:29:14 +02:00
|
|
|
if (def->globalId == EVENT_MESSAGE) {
|
|
|
|
if ((event.id == 42 || event.id == 43) && event.type == 3) {
|
|
|
|
quint32 front = ((event.data & 0xFF000000) >> 24);
|
|
|
|
quint32 rear = ((event.data & 0x0000FF00) >> 8);
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.ratio = ((qreal)front / (qreal)rear);
|
|
|
|
}
|
|
|
|
} else if (def->globalId == RECORD_MESSAGE) {
|
2020-11-10 20:07:46 +01:00
|
|
|
if (ctx.trackpoint.coordinates().isValid()) {
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.trackpoint.setTimestamp(QDateTime::fromTime_t(ctx.timestamp
|
|
|
|
+ 631065600));
|
|
|
|
ctx.trackpoint.setRatio(ctx.ratio);
|
2019-02-11 23:28:08 +01:00
|
|
|
ctx.segment.append(ctx.trackpoint);
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.trackpoint = Trackpoint();
|
2016-11-02 17:33:54 +01:00
|
|
|
}
|
2020-04-08 00:54:35 +02:00
|
|
|
} else if (def->globalId == COURSE_POINT)
|
2020-04-08 22:28:35 +02:00
|
|
|
if (waypoint.coordinates().isValid())
|
|
|
|
ctx.waypoints.append(waypoint);
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::parseDataMessage(CTX &ctx, quint8 header)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
2016-11-02 17:33:54 +01:00
|
|
|
int local_id = header & 0xf;
|
2018-07-03 19:08:46 +02:00
|
|
|
MessageDefinition *def = &(ctx.defs[local_id]);
|
|
|
|
return parseData(ctx, def);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::parseCompressedMessage(CTX &ctx, quint8 header)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
int local_id = (header >> 5) & 3;
|
2018-07-03 19:08:46 +02:00
|
|
|
MessageDefinition *def = &(ctx.defs[local_id]);
|
|
|
|
ctx.timestamp += header & 0x1f;
|
|
|
|
return parseData(ctx, def);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::parseRecord(CTX &ctx)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
quint8 header;
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readValue(ctx, header))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (header & 0x80)
|
2018-07-03 19:08:46 +02:00
|
|
|
return parseCompressedMessage(ctx, header);
|
2016-10-31 22:59:08 +01:00
|
|
|
else if (header & 0x40)
|
2018-07-03 19:08:46 +02:00
|
|
|
return parseDefinitionMessage(ctx, header);
|
2016-10-31 22:59:08 +01:00
|
|
|
else
|
2018-07-03 19:08:46 +02:00
|
|
|
return parseDataMessage(ctx, header);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
bool FITParser::parseHeader(CTX &ctx)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
|
|
|
FileHeader hdr;
|
|
|
|
quint16 crc;
|
|
|
|
qint64 len;
|
|
|
|
|
2016-11-05 17:32:15 +01:00
|
|
|
STATIC_ASSERT(sizeof(hdr) == 12);
|
2018-07-03 19:08:46 +02:00
|
|
|
len = ctx.file->read((char*)&hdr, sizeof(hdr));
|
2016-10-31 22:59:08 +01:00
|
|
|
if (len < 0) {
|
|
|
|
_errorString = "I/O error";
|
|
|
|
return false;
|
|
|
|
} else if ((size_t)len < sizeof(hdr)
|
2018-07-03 19:08:46 +02:00
|
|
|
|| hdr.magic != qToLittleEndian((quint32)FIT_MAGIC)) {
|
2016-10-31 22:59:08 +01:00
|
|
|
_errorString = "Not a FIT file";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
ctx.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))
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!readData(ctx.file, (char *)&crc, sizeof(crc)))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-07-27 19:47:46 +02:00
|
|
|
bool FITParser::parse(QFile *file, QList<TrackData> &tracks,
|
2019-01-31 01:46:53 +01:00
|
|
|
QList<RouteData> &routes,
|
|
|
|
QList<Area> &polygons, QVector<Waypoint> &waypoints)
|
2016-10-31 22:59:08 +01:00
|
|
|
{
|
2017-07-27 19:47:46 +02:00
|
|
|
Q_UNUSED(routes);
|
2019-01-31 01:46:53 +01:00
|
|
|
Q_UNUSED(polygons);
|
2020-04-08 00:54:35 +02:00
|
|
|
CTX ctx(file, waypoints);
|
2016-11-11 21:37:35 +01:00
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
if (!parseHeader(ctx))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
while (ctx.len)
|
|
|
|
if (!parseRecord(ctx))
|
|
|
|
return false;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
tracks.append(TrackData());
|
|
|
|
tracks.last().append(ctx.segment);
|
2016-11-02 17:33:54 +01:00
|
|
|
|
2018-07-03 19:08:46 +02:00
|
|
|
return true;
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|