fit-file-parser 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +190 -119
- package/dist/binary.d.ts +13 -1
- package/dist/binary.js +285 -164
- package/dist/cjs/binary.d.ts +13 -1
- package/dist/cjs/binary.js +285 -164
- package/dist/cjs/fit-parser.js +37 -19
- package/dist/cjs/fit.d.ts +6 -0
- package/dist/cjs/fit.js +167 -44
- package/dist/cjs/fit_types.d.ts +1297 -207
- package/dist/cjs/garmin_profile.generated.d.ts +9 -0
- package/dist/cjs/garmin_profile.generated.js +17838 -0
- package/dist/cjs/type_generator.d.ts +3 -0
- package/dist/cjs/type_generator.js +37 -9
- package/dist/fit-parser.js +37 -19
- package/dist/fit.d.ts +6 -0
- package/dist/fit.js +167 -44
- package/dist/fit_types.d.ts +1297 -207
- package/dist/garmin_profile.generated.d.ts +9 -0
- package/dist/garmin_profile.generated.js +17835 -0
- package/dist/type_generator.d.ts +3 -0
- package/dist/type_generator.js +36 -9
- package/package.json +9 -6
package/dist/cjs/binary.js
CHANGED
|
@@ -9,30 +9,47 @@ const fit_js_1 = require("./fit.js");
|
|
|
9
9
|
const messages_js_1 = require("./messages.js");
|
|
10
10
|
const CompressedLocalMsgNumMask = 0x60;
|
|
11
11
|
const CompressedHeaderMask = 0x80;
|
|
12
|
+
const CompressedTimestampMask = 0x1F;
|
|
12
13
|
const GarminTimeOffset = 631065600000;
|
|
13
|
-
let monitoring_timestamp = 0;
|
|
14
14
|
const InvalidFieldData = Symbol('invalid FIT field data');
|
|
15
15
|
const formatTypeMetadata = new Map();
|
|
16
|
-
|
|
16
|
+
const uint8CompatibleTypes = new Set(['enum', 'uint8', 'byte']);
|
|
17
|
+
function baseTypeSize(type) {
|
|
17
18
|
switch (type) {
|
|
19
|
+
case 'enum':
|
|
20
|
+
case 'sint8':
|
|
21
|
+
case 'uint8':
|
|
22
|
+
case 'uint8z':
|
|
23
|
+
case 'byte':
|
|
24
|
+
return 1;
|
|
18
25
|
case 'sint16':
|
|
19
26
|
case 'uint16':
|
|
20
27
|
case 'uint16z':
|
|
21
|
-
return
|
|
28
|
+
return 2;
|
|
22
29
|
case 'sint32':
|
|
23
30
|
case 'uint32':
|
|
24
31
|
case 'uint32z':
|
|
25
32
|
case 'float32':
|
|
26
|
-
return
|
|
33
|
+
return 4;
|
|
27
34
|
case 'float64':
|
|
28
|
-
return
|
|
29
|
-
case 'uint16_array':
|
|
30
|
-
return size % 2 !== 0;
|
|
31
|
-
case 'uint32_array':
|
|
32
|
-
return size % 4 !== 0;
|
|
35
|
+
return 8;
|
|
33
36
|
default:
|
|
34
|
-
return
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function requiresBoundedEndianDataView(type, size) {
|
|
41
|
+
const elementSize = baseTypeSize(type);
|
|
42
|
+
return elementSize !== undefined && size % elementSize !== 0;
|
|
43
|
+
}
|
|
44
|
+
function areProfileBaseTypesCompatible(profileType, wireType) {
|
|
45
|
+
if (profileType === undefined || profileType === wireType) {
|
|
46
|
+
return true;
|
|
35
47
|
}
|
|
48
|
+
// FIT producers commonly interchange enum, uint8, and byte definitions.
|
|
49
|
+
// They have the same width and value representation; keeping the semantic
|
|
50
|
+
// profile type is safe while still rejecting width and sign conflicts.
|
|
51
|
+
return uint8CompatibleTypes.has(String(profileType))
|
|
52
|
+
&& uint8CompatibleTypes.has(String(wireType));
|
|
36
53
|
}
|
|
37
54
|
function addEndian(littleEndian, bytes) {
|
|
38
55
|
let result = 0;
|
|
@@ -43,77 +60,10 @@ function addEndian(littleEndian, bytes) {
|
|
|
43
60
|
}
|
|
44
61
|
return result;
|
|
45
62
|
}
|
|
46
|
-
function readData(blob, dataView, fDef, startIndex
|
|
47
|
-
var _a;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
for (let i = 0; i < fDef.size; i++) {
|
|
51
|
-
array8.push(blob[startIndex + i]);
|
|
52
|
-
}
|
|
53
|
-
return array8;
|
|
54
|
-
}
|
|
55
|
-
if (fDef.endianAbility) {
|
|
56
|
-
const requiresBoundedDataView = (_a = fDef.requiresBoundedDataView) !== null && _a !== void 0 ? _a : (fDef.requiresBoundedDataView = requiresBoundedEndianDataView(fDef.type, fDef.size));
|
|
57
|
-
let fieldDataView = dataView;
|
|
58
|
-
let fieldStartIndex = startIndex;
|
|
59
|
-
if (startIndex < 0
|
|
60
|
-
|| startIndex + fDef.size > blob.length
|
|
61
|
-
|| startIndex + fDef.size > dataView.byteLength) {
|
|
62
|
-
const paddedField = new Uint8Array(fDef.size);
|
|
63
|
-
for (let index = 0; index < fDef.size; index++) {
|
|
64
|
-
paddedField[index] = blob[startIndex + index];
|
|
65
|
-
}
|
|
66
|
-
fieldDataView = new DataView(paddedField.buffer);
|
|
67
|
-
fieldStartIndex = 0;
|
|
68
|
-
}
|
|
69
|
-
else if (requiresBoundedDataView) {
|
|
70
|
-
fieldDataView = new DataView(dataView.buffer, dataView.byteOffset + startIndex, fDef.size);
|
|
71
|
-
fieldStartIndex = 0;
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
switch (fDef.type) {
|
|
75
|
-
case 'sint16':
|
|
76
|
-
return fieldDataView.getInt16(fieldStartIndex, fDef.littleEndian);
|
|
77
|
-
case 'uint16':
|
|
78
|
-
case 'uint16z':
|
|
79
|
-
return fieldDataView.getUint16(fieldStartIndex, fDef.littleEndian);
|
|
80
|
-
case 'sint32':
|
|
81
|
-
return fieldDataView.getInt32(fieldStartIndex, fDef.littleEndian);
|
|
82
|
-
case 'uint32':
|
|
83
|
-
case 'uint32z':
|
|
84
|
-
return fieldDataView.getUint32(fieldStartIndex, fDef.littleEndian);
|
|
85
|
-
case 'float32':
|
|
86
|
-
return fieldDataView.getFloat32(fieldStartIndex, fDef.littleEndian);
|
|
87
|
-
case 'float64':
|
|
88
|
-
return fieldDataView.getFloat64(fieldStartIndex, fDef.littleEndian);
|
|
89
|
-
case 'uint32_array': {
|
|
90
|
-
const array32 = [];
|
|
91
|
-
for (let i = 0; i < fDef.size; i += 4) {
|
|
92
|
-
array32.push(fieldDataView.getUint32(fieldStartIndex + i, fDef.littleEndian));
|
|
93
|
-
}
|
|
94
|
-
return array32;
|
|
95
|
-
}
|
|
96
|
-
case 'uint16_array': {
|
|
97
|
-
const array16 = [];
|
|
98
|
-
for (let i = 0; i < fDef.size; i += 2) {
|
|
99
|
-
array16.push(fieldDataView.getUint16(fieldStartIndex + i, fDef.littleEndian));
|
|
100
|
-
}
|
|
101
|
-
return array16;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
catch (e) {
|
|
106
|
-
if (!options.force) {
|
|
107
|
-
throw e;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const temp = [];
|
|
111
|
-
for (let i = 0; i < fDef.size; i++) {
|
|
112
|
-
temp.push(blob[startIndex + i]);
|
|
113
|
-
}
|
|
114
|
-
return addEndian(fDef.littleEndian, temp);
|
|
115
|
-
}
|
|
116
|
-
if (fDef.type === 'string') {
|
|
63
|
+
function readData(blob, dataView, fDef, startIndex) {
|
|
64
|
+
var _a, _b;
|
|
65
|
+
const rawType = (_b = (_a = fDef.rawType) !== null && _a !== void 0 ? _a : fit_js_1.FIT.types.fit_base_type[fDef.baseTypeNo]) !== null && _b !== void 0 ? _b : fDef.type;
|
|
66
|
+
if (rawType === 'string') {
|
|
117
67
|
const temp = [];
|
|
118
68
|
for (let i = 0; i < fDef.size; i++) {
|
|
119
69
|
if (blob[startIndex + i]) {
|
|
@@ -122,18 +72,57 @@ function readData(blob, dataView, fDef, startIndex, options) {
|
|
|
122
72
|
}
|
|
123
73
|
return buffer_1.Buffer.from(temp).toString('utf-8');
|
|
124
74
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
75
|
+
const elementSize = baseTypeSize(rawType);
|
|
76
|
+
if (elementSize === undefined) {
|
|
77
|
+
return InvalidFieldData;
|
|
78
|
+
}
|
|
79
|
+
if (fDef.size < elementSize
|
|
80
|
+
|| fDef.size % elementSize !== 0
|
|
81
|
+
|| startIndex < 0
|
|
82
|
+
|| startIndex + fDef.size > blob.length
|
|
83
|
+
|| startIndex + fDef.size > dataView.byteLength) {
|
|
84
|
+
return InvalidFieldData;
|
|
131
85
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
86
|
+
const isArray = fDef.array === true
|
|
87
|
+
|| String(fDef.type).endsWith('_array')
|
|
88
|
+
|| fDef.type === 'exercise_category_array'
|
|
89
|
+
|| (fDef.isDeveloperField === true && fDef.size > elementSize);
|
|
90
|
+
const elementCount = isArray ? fDef.size / elementSize : 1;
|
|
91
|
+
const values = [];
|
|
92
|
+
for (let elementIndex = 0; elementIndex < elementCount; elementIndex++) {
|
|
93
|
+
const offset = startIndex + elementIndex * elementSize;
|
|
94
|
+
switch (rawType) {
|
|
95
|
+
case 'sint8': {
|
|
96
|
+
const value = blob[offset];
|
|
97
|
+
values.push(value > 127 ? value - 256 : value);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case 'sint16':
|
|
101
|
+
values.push(dataView.getInt16(offset, fDef.littleEndian));
|
|
102
|
+
break;
|
|
103
|
+
case 'uint16':
|
|
104
|
+
case 'uint16z':
|
|
105
|
+
values.push(dataView.getUint16(offset, fDef.littleEndian));
|
|
106
|
+
break;
|
|
107
|
+
case 'sint32':
|
|
108
|
+
values.push(dataView.getInt32(offset, fDef.littleEndian));
|
|
109
|
+
break;
|
|
110
|
+
case 'uint32':
|
|
111
|
+
case 'uint32z':
|
|
112
|
+
values.push(dataView.getUint32(offset, fDef.littleEndian));
|
|
113
|
+
break;
|
|
114
|
+
case 'float32':
|
|
115
|
+
values.push(dataView.getFloat32(offset, fDef.littleEndian));
|
|
116
|
+
break;
|
|
117
|
+
case 'float64':
|
|
118
|
+
values.push(dataView.getFloat64(offset, fDef.littleEndian));
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
values.push(blob[offset]);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
135
124
|
}
|
|
136
|
-
return
|
|
125
|
+
return isArray ? values : values[0];
|
|
137
126
|
}
|
|
138
127
|
function formatByType(data, type, scale, offset) {
|
|
139
128
|
switch (type) {
|
|
@@ -160,6 +149,16 @@ function formatByType(data, type, scale, offset) {
|
|
|
160
149
|
});
|
|
161
150
|
}
|
|
162
151
|
return scale ? data / scale + offset : data;
|
|
152
|
+
case 'exercise_category_array':
|
|
153
|
+
if (Array.isArray(data)) {
|
|
154
|
+
return data.map((dataItem) => {
|
|
155
|
+
if (isInvalidValue(dataItem, 'uint16')) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return formatByType(dataItem, 'exercise_category', scale, offset);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return formatByType(data, 'exercise_category', scale, offset);
|
|
163
162
|
default:
|
|
164
163
|
{
|
|
165
164
|
const typeMap = fit_js_1.FIT.types[type];
|
|
@@ -218,10 +217,9 @@ function isInvalidValue(data, type) {
|
|
|
218
217
|
case 'string':
|
|
219
218
|
return data === 0x00;
|
|
220
219
|
case 'float32':
|
|
221
|
-
return data
|
|
220
|
+
return Number.isNaN(data);
|
|
222
221
|
case 'float64':
|
|
223
|
-
|
|
224
|
-
return data === 0xFFFFFFFFFFFFFFFF;
|
|
222
|
+
return Number.isNaN(data);
|
|
225
223
|
case 'uint8z':
|
|
226
224
|
return data === 0x00;
|
|
227
225
|
case 'uint16z':
|
|
@@ -249,6 +247,27 @@ function isInvalidBaseTypeValue(data, baseTypeNo) {
|
|
|
249
247
|
const baseType = fit_js_1.FIT.types.fit_base_type[baseTypeNo];
|
|
250
248
|
return typeof baseType === 'string' ? isInvalidValue(data, baseType) : false;
|
|
251
249
|
}
|
|
250
|
+
function formatFieldValue(data, fDef, options, fields) {
|
|
251
|
+
var _a, _b, _c;
|
|
252
|
+
const field = fDef.name;
|
|
253
|
+
const scale = (_a = fDef.scale) !== null && _a !== void 0 ? _a : null;
|
|
254
|
+
const offset = (_b = fDef.offset) !== null && _b !== void 0 ? _b : 0;
|
|
255
|
+
if (Array.isArray(data)
|
|
256
|
+
&& !String(fDef.type).endsWith('_array')
|
|
257
|
+
&& fDef.type !== 'exercise_category_array') {
|
|
258
|
+
const rawType = (_c = fDef.rawType) !== null && _c !== void 0 ? _c : fit_js_1.FIT.types.fit_base_type[fDef.baseTypeNo];
|
|
259
|
+
return data.map((item) => {
|
|
260
|
+
if (isInvalidValue(item, rawType)) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return applyOptions(formatByType(item, fDef.type, scale, offset), field, options, fields);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return applyOptions(formatByType(data, fDef.type, scale, offset), field, options, fields);
|
|
267
|
+
}
|
|
268
|
+
function isOutputFieldName(field) {
|
|
269
|
+
return field !== 'unknown' && field !== '' && field !== undefined;
|
|
270
|
+
}
|
|
252
271
|
function convertTo(data, unitsList, unitName) {
|
|
253
272
|
const options = fit_js_1.FIT.options[unitsList];
|
|
254
273
|
const unit = options[unitName];
|
|
@@ -307,8 +326,10 @@ function applyOptions(data, field, options, fields) {
|
|
|
307
326
|
return convertTo(data, 'lengthUnits', options.lengthUnit);
|
|
308
327
|
case 'temperature':
|
|
309
328
|
case 'min_temperature':
|
|
329
|
+
case 'temperature_min':
|
|
310
330
|
case 'avg_temperature':
|
|
311
331
|
case 'max_temperature':
|
|
332
|
+
case 'temperature_max':
|
|
312
333
|
return convertTo(data, 'temperatureUnits', options.temperatureUnit);
|
|
313
334
|
case 'pressure':
|
|
314
335
|
case 'start_pressure':
|
|
@@ -339,11 +360,59 @@ function applyGarminProductName(fields) {
|
|
|
339
360
|
fields.product_name = productName;
|
|
340
361
|
}
|
|
341
362
|
}
|
|
342
|
-
function
|
|
343
|
-
var _a, _b, _c, _d, _e;
|
|
363
|
+
function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, developerFields, options) {
|
|
364
|
+
var _a, _b, _c, _d, _e, _f;
|
|
365
|
+
const description = (_a = developerFields[developerFieldDef.developerDataIndex]) === null || _a === void 0 ? void 0 : _a[developerFieldDef.fieldDefinitionNumber];
|
|
366
|
+
if (!description) {
|
|
367
|
+
developerFieldDef.resolvedFieldDef = undefined;
|
|
368
|
+
developerFieldDef.resolvedFrom = undefined;
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
if (developerFieldDef.resolvedFrom === description
|
|
372
|
+
&& developerFieldDef.resolvedFieldDef) {
|
|
373
|
+
return developerFieldDef.resolvedFieldDef;
|
|
374
|
+
}
|
|
375
|
+
const describedBaseType = description.fit_base_type_id;
|
|
376
|
+
const baseType = typeof describedBaseType === 'number'
|
|
377
|
+
? describedBaseType
|
|
378
|
+
: Number((_b = Object.entries(fit_js_1.FIT.types.fit_base_type).find(([, typeName]) => typeName === describedBaseType)) === null || _b === void 0 ? void 0 : _b[0]);
|
|
379
|
+
const type = fit_js_1.FIT.types.fit_base_type[baseType];
|
|
380
|
+
if (!Number.isInteger(baseType) || type === undefined) {
|
|
381
|
+
developerFieldDef.resolvedFieldDef = undefined;
|
|
382
|
+
developerFieldDef.resolvedFrom = undefined;
|
|
383
|
+
if (options.force) {
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
throw new Error(`Unsupported base type for developer data index ${developerFieldDef.developerDataIndex}, field ${developerFieldDef.fieldDefinitionNumber}`);
|
|
387
|
+
}
|
|
388
|
+
const resolvedFieldDef = {
|
|
389
|
+
type,
|
|
390
|
+
rawType: type,
|
|
391
|
+
fDefNo: developerFieldDef.fieldDefinitionNumber,
|
|
392
|
+
size: developerFieldDef.size,
|
|
393
|
+
array: type !== 'string'
|
|
394
|
+
&& developerFieldDef.size > ((_c = baseTypeSize(type)) !== null && _c !== void 0 ? _c : developerFieldDef.size),
|
|
395
|
+
endianAbility: (baseType & 128) === 128,
|
|
396
|
+
littleEndian,
|
|
397
|
+
baseTypeNo: baseType,
|
|
398
|
+
name: (_d = description.field_name) !== null && _d !== void 0 ? _d : '',
|
|
399
|
+
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
400
|
+
scale: (_e = description.scale) !== null && _e !== void 0 ? _e : 1,
|
|
401
|
+
offset: (_f = description.offset) !== null && _f !== void 0 ? _f : 0,
|
|
402
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(type, developerFieldDef.size),
|
|
403
|
+
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
404
|
+
isDeveloperField: true,
|
|
405
|
+
};
|
|
406
|
+
developerFieldDef.resolvedFieldDef = resolvedFieldDef;
|
|
407
|
+
developerFieldDef.resolvedFrom = description;
|
|
408
|
+
return resolvedFieldDef;
|
|
409
|
+
}
|
|
410
|
+
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), decoderState = {}) {
|
|
411
|
+
var _a, _b, _c;
|
|
344
412
|
const recordHeader = blob[startIndex];
|
|
345
413
|
let localMessageType = recordHeader & 15;
|
|
346
|
-
|
|
414
|
+
const isCompressedTimestamp = (recordHeader & CompressedHeaderMask) === CompressedHeaderMask;
|
|
415
|
+
if (isCompressedTimestamp) {
|
|
347
416
|
// compressed timestamp
|
|
348
417
|
localMessageType = (recordHeader & CompressedLocalMsgNumMask) >> 5;
|
|
349
418
|
}
|
|
@@ -364,63 +433,45 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
364
433
|
]),
|
|
365
434
|
numberOfFields: numberOfFields + numberOfDeveloperDataFields,
|
|
366
435
|
fieldDefs: [],
|
|
436
|
+
developerFieldDefs: [],
|
|
367
437
|
rawData: [],
|
|
368
438
|
};
|
|
369
439
|
const message = (0, messages_js_1.getFitMessage)(mTypeDef.globalMessageNumber);
|
|
370
440
|
for (let i = 0; i < numberOfFields; i++) {
|
|
371
441
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
372
442
|
const baseType = blob[fDefIndex + 2];
|
|
373
|
-
const
|
|
443
|
+
const wireType = fit_js_1.FIT.types.fit_base_type[baseType];
|
|
444
|
+
const { field, type, baseType: profileBaseType, array, scale, offset, aliases, } = message.getAttributes(blob[fDefIndex]);
|
|
445
|
+
const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
374
446
|
const fDef = {
|
|
375
|
-
type,
|
|
447
|
+
type: profileCompatible ? type : wireType,
|
|
448
|
+
rawType: wireType,
|
|
376
449
|
fDefNo: blob[fDefIndex],
|
|
377
450
|
size: blob[fDefIndex + 1],
|
|
451
|
+
array: profileCompatible
|
|
452
|
+
? array === true || String(type).endsWith('_array')
|
|
453
|
+
: false,
|
|
378
454
|
endianAbility: (baseType & 128) === 128,
|
|
379
455
|
littleEndian: lEnd,
|
|
380
456
|
baseTypeNo: baseType,
|
|
381
|
-
name: field,
|
|
457
|
+
name: profileCompatible ? field : '',
|
|
382
458
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
383
|
-
scale,
|
|
384
|
-
offset,
|
|
385
|
-
requiresBoundedDataView: requiresBoundedEndianDataView(
|
|
459
|
+
scale: profileCompatible ? scale : null,
|
|
460
|
+
offset: profileCompatible ? offset : 0,
|
|
461
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(wireType, blob[fDefIndex + 1]),
|
|
462
|
+
aliases: profileCompatible ? aliases : undefined,
|
|
386
463
|
};
|
|
387
464
|
mTypeDef.fieldDefs.push(fDef);
|
|
388
465
|
}
|
|
389
|
-
// numberOfDeveloperDataFields = 0 so it wont crash here and wont loop
|
|
390
466
|
for (let i = 0; i < numberOfDeveloperDataFields; i++) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const devDef = developerFields[devDataIndex][fieldNum];
|
|
398
|
-
const baseType = devDef.fit_base_type_id;
|
|
399
|
-
const fDef = {
|
|
400
|
-
type: fit_js_1.FIT.types.fit_base_type[baseType],
|
|
401
|
-
fDefNo: fieldNum,
|
|
402
|
-
size,
|
|
403
|
-
endianAbility: (baseType & 128) === 128,
|
|
404
|
-
littleEndian: lEnd,
|
|
405
|
-
baseTypeNo: baseType,
|
|
406
|
-
name: devDef.field_name,
|
|
407
|
-
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
408
|
-
scale: devDef.scale || 1,
|
|
409
|
-
offset: devDef.offset || 0,
|
|
410
|
-
requiresBoundedDataView: requiresBoundedEndianDataView(fit_js_1.FIT.types.fit_base_type[baseType], size),
|
|
411
|
-
developerDataIndex: devDataIndex,
|
|
412
|
-
isDeveloperField: true,
|
|
413
|
-
};
|
|
414
|
-
mTypeDef.fieldDefs.push(fDef);
|
|
415
|
-
}
|
|
416
|
-
catch (e) {
|
|
417
|
-
if (options.force) {
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
throw e;
|
|
421
|
-
}
|
|
467
|
+
const fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + i * 3;
|
|
468
|
+
(_a = mTypeDef.developerFieldDefs) === null || _a === void 0 ? void 0 : _a.push({
|
|
469
|
+
fieldDefinitionNumber: blob[fDefIndex],
|
|
470
|
+
size: blob[fDefIndex + 1],
|
|
471
|
+
developerDataIndex: blob[fDefIndex + 2],
|
|
472
|
+
});
|
|
422
473
|
}
|
|
423
|
-
mTypeDef.rawData = Array.from({ length: mTypeDef.
|
|
474
|
+
mTypeDef.rawData = Array.from({ length: mTypeDef.numberOfFields }, () => InvalidFieldData);
|
|
424
475
|
messageTypes[localMessageType] = mTypeDef;
|
|
425
476
|
const nextIndex = startIndex + 6 + mTypeDef.numberOfFields * 3;
|
|
426
477
|
const nextIndexWithDeveloperData = nextIndex + 1;
|
|
@@ -430,20 +481,25 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
430
481
|
};
|
|
431
482
|
}
|
|
432
483
|
const messageType = messageTypes[localMessageType] || messageTypes[0];
|
|
433
|
-
// TODO: handle compressed header ((recordHeader & 128) == 128)
|
|
434
|
-
// uncompressed header
|
|
435
484
|
let messageSize = 0;
|
|
436
485
|
let readDataFromIndex = startIndex + 1;
|
|
437
486
|
const fields = {};
|
|
438
487
|
const message = (0, messages_js_1.getFitMessage)(messageType.globalMessageNumber);
|
|
439
|
-
const
|
|
488
|
+
const developerFieldDefs = (_b = messageType.developerFieldDefs) !== null && _b !== void 0 ? _b : [];
|
|
489
|
+
const totalFieldCount = messageType.fieldDefs.length + developerFieldDefs.length;
|
|
490
|
+
const rawData = (_c = messageType.rawData) !== null && _c !== void 0 ? _c : (messageType.rawData = Array.from({ length: totalFieldCount }, () => InvalidFieldData));
|
|
440
491
|
let validFieldCount = 0;
|
|
441
492
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
442
493
|
const fDef = messageType.fieldDefs[i];
|
|
443
|
-
const data = readData(blob, dataView, fDef, readDataFromIndex
|
|
444
|
-
if (
|
|
494
|
+
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
495
|
+
if (data !== InvalidFieldData
|
|
496
|
+
&& !isInvalidValue(data, fDef.type)
|
|
497
|
+
&& !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
445
498
|
rawData[i] = data;
|
|
446
499
|
validFieldCount++;
|
|
500
|
+
if (!isCompressedTimestamp && fDef.fDefNo === 253 && typeof data === 'number') {
|
|
501
|
+
decoderState.lastTimestamp = data;
|
|
502
|
+
}
|
|
447
503
|
}
|
|
448
504
|
else {
|
|
449
505
|
rawData[i] = InvalidFieldData;
|
|
@@ -451,14 +507,53 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
451
507
|
readDataFromIndex += fDef.size;
|
|
452
508
|
messageSize += fDef.size;
|
|
453
509
|
}
|
|
510
|
+
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
511
|
+
const developerFieldDef = developerFieldDefs[i];
|
|
512
|
+
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
513
|
+
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
514
|
+
if (fDef) {
|
|
515
|
+
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
516
|
+
if (data !== InvalidFieldData
|
|
517
|
+
&& !isInvalidValue(data, fDef.type)
|
|
518
|
+
&& !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
519
|
+
rawData[rawDataIndex] = data;
|
|
520
|
+
validFieldCount++;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
rawData[rawDataIndex] = InvalidFieldData;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
rawData[rawDataIndex] = InvalidFieldData;
|
|
528
|
+
}
|
|
529
|
+
readDataFromIndex += developerFieldDef.size;
|
|
530
|
+
messageSize += developerFieldDef.size;
|
|
531
|
+
}
|
|
454
532
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
455
533
|
const data = rawData[i];
|
|
456
534
|
if (data === InvalidFieldData) {
|
|
457
535
|
continue;
|
|
458
536
|
}
|
|
459
537
|
const fDef = messageType.fieldDefs[i];
|
|
538
|
+
if (isOutputFieldName(fDef.name)) {
|
|
539
|
+
fields[fDef.name] = data;
|
|
540
|
+
}
|
|
541
|
+
if (fDef.aliases) {
|
|
542
|
+
for (const alias of fDef.aliases) {
|
|
543
|
+
if (isOutputFieldName(alias.field)) {
|
|
544
|
+
fields[alias.field] = data;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
550
|
+
const data = rawData[messageType.fieldDefs.length + i];
|
|
551
|
+
const fDef = developerFieldDefs[i].resolvedFieldDef;
|
|
552
|
+
if (data === InvalidFieldData || !fDef) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
460
555
|
const field = fDef.name;
|
|
461
|
-
if (field !== 'unknown' && field !== ''
|
|
556
|
+
if (field !== 'unknown' && field !== '') {
|
|
462
557
|
fields[field] = data;
|
|
463
558
|
}
|
|
464
559
|
}
|
|
@@ -468,21 +563,44 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
468
563
|
continue;
|
|
469
564
|
}
|
|
470
565
|
const fDef = messageType.fieldDefs[i];
|
|
471
|
-
if (fDef.
|
|
472
|
-
|
|
473
|
-
const { type } = fDef;
|
|
474
|
-
const scale = (_b = fDef.scale) !== null && _b !== void 0 ? _b : null;
|
|
475
|
-
const offset = (_c = fDef.offset) !== null && _c !== void 0 ? _c : 0;
|
|
476
|
-
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
566
|
+
if (isOutputFieldName(fDef.name)) {
|
|
567
|
+
fields[fDef.name] = formatFieldValue(data, fDef, options, fields);
|
|
477
568
|
}
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
569
|
+
if (fDef.aliases) {
|
|
570
|
+
for (const alias of fDef.aliases) {
|
|
571
|
+
if (isOutputFieldName(alias.field)) {
|
|
572
|
+
fields[alias.field] = formatFieldValue(data, Object.assign(Object.assign(Object.assign({}, fDef), alias), { name: alias.field, aliases: undefined }), options, fields);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
578
|
+
const data = rawData[messageType.fieldDefs.length + i];
|
|
579
|
+
const fDef = developerFieldDefs[i].resolvedFieldDef;
|
|
580
|
+
if (data === InvalidFieldData || !fDef) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
const field = fDef.name;
|
|
584
|
+
if (field !== 'unknown' && field !== '') {
|
|
585
|
+
fields[field] = formatFieldValue(data, fDef, options, fields);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (isCompressedTimestamp) {
|
|
589
|
+
const previousTimestamp = decoderState.lastTimestamp;
|
|
590
|
+
if (previousTimestamp === undefined) {
|
|
591
|
+
if (!options.force) {
|
|
592
|
+
throw new Error('Compressed timestamp requires a previous timestamp');
|
|
484
593
|
}
|
|
485
594
|
}
|
|
595
|
+
else {
|
|
596
|
+
const timeOffset = recordHeader & CompressedTimestampMask;
|
|
597
|
+
const previousOffset = previousTimestamp & CompressedTimestampMask;
|
|
598
|
+
const rollover = timeOffset < previousOffset ? 0x20 : 0;
|
|
599
|
+
const timestamp = (previousTimestamp & ~CompressedTimestampMask) + timeOffset + rollover;
|
|
600
|
+
decoderState.lastTimestamp = timestamp;
|
|
601
|
+
fields.timestamp = new Date(timestamp * 1000 + GarminTimeOffset);
|
|
602
|
+
validFieldCount++;
|
|
603
|
+
}
|
|
486
604
|
}
|
|
487
605
|
applyGarminProductName(fields);
|
|
488
606
|
if (validFieldCount > 0 && message.name === 'record' && options.elapsedRecordField) {
|
|
@@ -495,17 +613,20 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
495
613
|
developerFields[fields.developer_data_index][fields.field_definition_number] = fields;
|
|
496
614
|
}
|
|
497
615
|
if (message.name === 'monitoring') {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
616
|
+
const timestampFieldIndex = messageType.fieldDefs.findIndex(fieldDefinition => fieldDefinition.fDefNo === 253);
|
|
617
|
+
const rawTimestamp = timestampFieldIndex >= 0
|
|
618
|
+
? rawData[timestampFieldIndex]
|
|
619
|
+
: InvalidFieldData;
|
|
620
|
+
if (rawTimestamp !== InvalidFieldData && typeof rawTimestamp === 'number') {
|
|
621
|
+
decoderState.monitoringTimestamp = rawTimestamp;
|
|
622
|
+
fields.timestamp = new Date(rawTimestamp * 1000 + GarminTimeOffset);
|
|
503
623
|
}
|
|
504
|
-
if (fields.timestamp16
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
624
|
+
else if (fields.timestamp16
|
|
625
|
+
&& decoderState.monitoringTimestamp !== undefined
|
|
626
|
+
&& !fields.timestamp) {
|
|
627
|
+
decoderState.monitoringTimestamp
|
|
628
|
+
+= (fields.timestamp16 - (decoderState.monitoringTimestamp & 0xFFFF)) & 0xFFFF;
|
|
629
|
+
fields.timestamp = new Date(decoderState.monitoringTimestamp * 1000 + GarminTimeOffset);
|
|
509
630
|
}
|
|
510
631
|
}
|
|
511
632
|
return {
|