2016-10-31 22:59:08 +01:00
|
|
|
#include <QtEndian>
|
2024-09-04 09:15:46 +02:00
|
|
|
#include "GUI/format.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
|
|
|
|
2024-09-04 09:15:46 +02:00
|
|
|
#define LAP 19
|
|
|
|
#define RECORD 20
|
|
|
|
#define EVENT 21
|
|
|
|
#define LOCATION 29
|
|
|
|
#define COURSEPOINT 32
|
|
|
|
|
|
|
|
#define TIMESTAMP 253
|
2016-11-02 17:33:54 +01:00
|
|
|
|
2024-04-01 12:28:50 +02:00
|
|
|
static QMap<int, QString> coursePointSymbolsInit()
|
2020-04-08 00:54:35 +02:00
|
|
|
{
|
|
|
|
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");
|
2024-04-01 12:28:50 +02:00
|
|
|
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");
|
2020-04-08 00:54:35 +02:00
|
|
|
map.insert(15, "Sprint");
|
2024-04-01 12:28:50 +02:00
|
|
|
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");
|
2020-04-08 00:54:35 +02:00
|
|
|
map.insert(23, "U-Turn");
|
2024-04-01 12:28:50 +02:00
|
|
|
map.insert(24, "Segment Start");
|
|
|
|
map.insert(25, "Segment End");
|
|
|
|
map.insert(27, "Campground");
|
|
|
|
map.insert(28, "Aid Station");
|
|
|
|
map.insert(29, "Rest Area");
|
|
|
|
map.insert(30, "General Distance");
|
|
|
|
map.insert(31, "Service");
|
|
|
|
map.insert(32, "Energy Gel");
|
|
|
|
map.insert(33, "Sports Drink");
|
|
|
|
map.insert(34, "Mile Marker");
|
|
|
|
map.insert(35, "Checkpoint");
|
|
|
|
map.insert(36, "Shelter");
|
|
|
|
map.insert(37, "Meeting Spot");
|
|
|
|
map.insert(38, "Overlook");
|
|
|
|
map.insert(39, "Toilet");
|
|
|
|
map.insert(40, "Shower");
|
|
|
|
map.insert(41, "Gear");
|
|
|
|
map.insert(42, "Sharp Curve");
|
|
|
|
map.insert(43, "Steep Incline");
|
|
|
|
map.insert(44, "Tunnel");
|
|
|
|
map.insert(45, "Bridge");
|
|
|
|
map.insert(46, "Obstacle");
|
|
|
|
map.insert(47, "Crossing");
|
|
|
|
map.insert(48, "Store");
|
|
|
|
map.insert(49, "Transition");
|
|
|
|
map.insert(50, "Navaid");
|
|
|
|
map.insert(51, "Transport");
|
|
|
|
map.insert(52, "Alert");
|
|
|
|
map.insert(53, "Info");
|
2020-04-08 00:54:35 +02:00
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
2024-04-03 02:30:26 +02:00
|
|
|
static QMap<int, QString> locationPointSymbolsInit()
|
|
|
|
{
|
|
|
|
QMap<int, QString> map;
|
|
|
|
|
|
|
|
/* The location symbols are a typical GARMIN mess. Every GPS unit
|
|
|
|
has probably its own list, so we only add a few generic icons seen
|
|
|
|
"in the wild" most often. */
|
|
|
|
map.insert(94, "Flag, Blue");
|
|
|
|
map.insert(95, "Flag, Green");
|
|
|
|
map.insert(96, "Flag, Red");
|
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
2024-04-01 12:28:50 +02:00
|
|
|
static QMap<int, QString> coursePointSymbols = coursePointSymbolsInit();
|
2024-04-03 02:30:26 +02:00
|
|
|
static QMap<int, QString> locationPointSymbols = locationPointSymbolsInit();
|
2020-04-08 00:54:35 +02:00
|
|
|
|
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
|
|
|
{
|
2024-06-20 05:34:13 +02:00
|
|
|
MessageDefinition *def = &(ctx.defs[header & 0x0f]);
|
|
|
|
quint8 numFields;
|
2016-10-31 22:59:08 +01:00
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
def->fields.clear();
|
|
|
|
def->devFields.clear();
|
2016-10-31 22:59:08 +01:00
|
|
|
|
|
|
|
// reserved/unused
|
2024-06-20 05:34:13 +02:00
|
|
|
if (!skipValue(ctx, 1))
|
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;
|
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
// definition records
|
|
|
|
if (!readValue(ctx, numFields))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
def->fields.resize(numFields);
|
|
|
|
for (int i = 0; i < def->fields.size(); i++) {
|
|
|
|
if (!readData(ctx.file, (char*)&(def->fields[i]), sizeof(Field)))
|
2016-10-31 22:59:08 +01:00
|
|
|
return false;
|
2024-06-20 05:34:13 +02:00
|
|
|
ctx.len -= sizeof(Field);
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
|
2016-11-03 08:02:28 +01:00
|
|
|
// developer definition records
|
|
|
|
if (header & 0x20) {
|
2024-06-20 05:34:13 +02:00
|
|
|
if (!readValue(ctx, numFields))
|
2016-11-03 08:02:28 +01:00
|
|
|
return false;
|
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
def->devFields.resize(numFields);
|
|
|
|
for (int i = 0; i < def->devFields.size(); i++) {
|
|
|
|
if (!readData(ctx.file, (char*)&(def->devFields[i]), sizeof(Field)))
|
2016-11-03 08:02:28 +01:00
|
|
|
return false;
|
2024-06-20 05:34:13 +02:00
|
|
|
ctx.len -= sizeof(Field);
|
2016-11-03 08:02:28 +01:00
|
|
|
}
|
2024-06-20 05:34:13 +02:00
|
|
|
}
|
2016-11-03 08:02:28 +01:00
|
|
|
|
2016-10-31 22:59:08 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
bool FITParser::readField(CTX &ctx, const 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;
|
2021-11-27 10:41:39 +01:00
|
|
|
case 3:
|
2016-10-31 22:59:08 +01:00
|
|
|
case 0x83: // sint16
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(qint16, 0x7fffU);
|
|
|
|
break;
|
2021-11-27 10:41:39 +01:00
|
|
|
case 4:
|
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;
|
2021-11-27 10:41:39 +01:00
|
|
|
case 5:
|
2016-10-31 22:59:08 +01:00
|
|
|
case 0x85: // sint32
|
2020-04-08 22:28:35 +02:00
|
|
|
VAL(qint32, 0x7fffffffU);
|
|
|
|
break;
|
2021-11-27 10:41:39 +01:00
|
|
|
case 6:
|
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;
|
2021-11-27 10:41:39 +01:00
|
|
|
case 7: // UTF8 nul terminated string
|
|
|
|
{QByteArray ba(ctx.file->read(field->size));
|
|
|
|
ctx.len -= field->size;
|
|
|
|
ret = (ba.size() == field->size);
|
2024-09-02 09:55:35 +02:00
|
|
|
val = ret ? QString(ba.left(ba.indexOf('\0'))) : QString();
|
2024-04-03 02:30:26 +02:00
|
|
|
valid = (!ba.isEmpty() && ba.at(0) != 0);}
|
2021-11-27 10:41:39 +01:00
|
|
|
break;
|
2016-10-31 22:59:08 +01:00
|
|
|
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
|
|
|
{
|
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
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
if (!def->fields.size() && !def->devFields.size()) {
|
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
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
for (int i = 0; i < def->fields.size(); i++) {
|
|
|
|
const Field *field = &def->fields.at(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
|
|
|
|
2024-09-04 09:15:46 +02:00
|
|
|
if (field->id == TIMESTAMP)
|
2020-04-08 00:54:35 +02:00
|
|
|
ctx.timestamp = val.toUInt();
|
2024-09-04 09:15:46 +02:00
|
|
|
else if (def->globalId == RECORD) {
|
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
|
|
|
}
|
2024-09-04 09:15:46 +02:00
|
|
|
} else if (def->globalId == EVENT) {
|
2018-07-03 01:29:14 +02:00
|
|
|
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
|
|
|
}
|
2024-09-04 09:15:46 +02:00
|
|
|
} else if (def->globalId == COURSEPOINT) {
|
2020-04-08 00:54:35 +02:00
|
|
|
switch (field->id) {
|
2018-07-03 01:29:14 +02:00
|
|
|
case 1:
|
2024-04-01 12:37:06 +02:00
|
|
|
waypoint.setTimestamp(QDateTime::fromSecsSinceEpoch(
|
|
|
|
val.toUInt() + 631065600, Qt::UTC));
|
2020-04-08 00:54:35 +02:00
|
|
|
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:
|
2024-04-01 12:28:50 +02:00
|
|
|
waypoint.setSymbol(coursePointSymbols.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;
|
|
|
|
}
|
2021-09-24 19:45:17 +02:00
|
|
|
} else if (def->globalId == LOCATION) {
|
|
|
|
switch (field->id) {
|
|
|
|
case 0:
|
|
|
|
waypoint.setName(val.toString());
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
waypoint.rcoordinates().setLat(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
waypoint.rcoordinates().setLon(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
|
|
|
break;
|
2024-04-03 02:30:26 +02:00
|
|
|
case 3:
|
|
|
|
waypoint.setSymbol(locationPointSymbols.value(val.toUInt()));
|
|
|
|
break;
|
2021-09-24 19:45:17 +02:00
|
|
|
case 4:
|
|
|
|
waypoint.setElevation((val.toUInt() / 5.0) - 500);
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
waypoint.setDescription(val.toString());
|
|
|
|
break;
|
|
|
|
}
|
2024-09-04 09:15:46 +02:00
|
|
|
} else if (def->globalId == LAP) {
|
|
|
|
switch (field->id) {
|
|
|
|
case 5:
|
|
|
|
waypoint.rcoordinates().setLat(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
waypoint.rcoordinates().setLon(
|
|
|
|
(val.toInt() / (double)0x7fffffff) * 180);
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
waypoint.setDescription(Format::timeSpan(val.toUInt() / 1000));
|
|
|
|
break;
|
|
|
|
case 254:
|
|
|
|
waypoint.setName("#" + QString::number(val.toUInt()));
|
|
|
|
break;
|
|
|
|
}
|
2016-10-31 22:59:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-20 05:34:13 +02:00
|
|
|
for (int i = 0; i < def->devFields.size(); i++)
|
|
|
|
if (!readField(ctx, &def->devFields.at(i), val, valid))
|
2016-11-03 08:02:28 +01:00
|
|
|
return false;
|
2018-07-03 19:08:46 +02:00
|
|
|
|
2024-09-04 09:15:46 +02:00
|
|
|
if (def->globalId == EVENT) {
|
|
|
|
if ((event.id == 42 || event.id == 43) && event.type == 3) {
|
2018-07-03 01:29:14 +02:00
|
|
|
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);
|
|
|
|
}
|
2024-09-04 09:15:46 +02:00
|
|
|
} else if (def->globalId == RECORD) {
|
2020-11-10 20:07:46 +01:00
|
|
|
if (ctx.trackpoint.coordinates().isValid()) {
|
2024-04-01 12:37:06 +02:00
|
|
|
ctx.trackpoint.setTimestamp(QDateTime::fromSecsSinceEpoch(
|
|
|
|
ctx.timestamp + 631065600, Qt::UTC));
|
2018-07-03 19:08:46 +02:00
|
|
|
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
|
|
|
}
|
2024-09-04 09:15:46 +02:00
|
|
|
} else if (def->globalId == COURSEPOINT || def->globalId == LAP) {
|
2020-04-08 22:28:35 +02:00
|
|
|
if (waypoint.coordinates().isValid())
|
|
|
|
ctx.waypoints.append(waypoint);
|
2021-09-24 19:45:17 +02:00
|
|
|
} else if (def->globalId == LOCATION) {
|
|
|
|
if (waypoint.coordinates().isValid()) {
|
|
|
|
waypoint.setTimestamp(QDateTime::fromSecsSinceEpoch(ctx.timestamp
|
|
|
|
+ 631065600, Qt::UTC));
|
|
|
|
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
|
|
|
{
|
2024-06-20 05:34:13 +02:00
|
|
|
int localId = header & 0xf;
|
|
|
|
MessageDefinition *def = &(ctx.defs[localId]);
|
2018-07-03 19:08:46 +02:00
|
|
|
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
|
|
|
{
|
2024-06-20 05:34:13 +02:00
|
|
|
int localId = (header >> 5) & 3;
|
|
|
|
MessageDefinition *def = &(ctx.defs[localId]);
|
2018-07-03 19:08:46 +02:00
|
|
|
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;
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
2024-03-10 08:09:44 +01:00
|
|
|
tracks.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
|
|
|
}
|