fit-file-parser 4.0.2 → 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 +41 -0
- package/README.md +190 -119
- package/dist/binary.d.ts +5 -1
- package/dist/binary.js +202 -141
- package/dist/cjs/binary.d.ts +5 -1
- package/dist/cjs/binary.js +202 -141
- 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/binary.js
CHANGED
|
@@ -3,30 +3,47 @@ import { FIT } from './fit.js';
|
|
|
3
3
|
import { getFitMessage, getFitMessageBaseType } from './messages.js';
|
|
4
4
|
const CompressedLocalMsgNumMask = 0x60;
|
|
5
5
|
const CompressedHeaderMask = 0x80;
|
|
6
|
+
const CompressedTimestampMask = 0x1F;
|
|
6
7
|
const GarminTimeOffset = 631065600000;
|
|
7
|
-
let monitoring_timestamp = 0;
|
|
8
8
|
const InvalidFieldData = Symbol('invalid FIT field data');
|
|
9
9
|
const formatTypeMetadata = new Map();
|
|
10
|
-
|
|
10
|
+
const uint8CompatibleTypes = new Set(['enum', 'uint8', 'byte']);
|
|
11
|
+
function baseTypeSize(type) {
|
|
11
12
|
switch (type) {
|
|
13
|
+
case 'enum':
|
|
14
|
+
case 'sint8':
|
|
15
|
+
case 'uint8':
|
|
16
|
+
case 'uint8z':
|
|
17
|
+
case 'byte':
|
|
18
|
+
return 1;
|
|
12
19
|
case 'sint16':
|
|
13
20
|
case 'uint16':
|
|
14
21
|
case 'uint16z':
|
|
15
|
-
return
|
|
22
|
+
return 2;
|
|
16
23
|
case 'sint32':
|
|
17
24
|
case 'uint32':
|
|
18
25
|
case 'uint32z':
|
|
19
26
|
case 'float32':
|
|
20
|
-
return
|
|
27
|
+
return 4;
|
|
21
28
|
case 'float64':
|
|
22
|
-
return
|
|
23
|
-
case 'uint16_array':
|
|
24
|
-
return size % 2 !== 0;
|
|
25
|
-
case 'uint32_array':
|
|
26
|
-
return size % 4 !== 0;
|
|
29
|
+
return 8;
|
|
27
30
|
default:
|
|
28
|
-
return
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function requiresBoundedEndianDataView(type, size) {
|
|
35
|
+
const elementSize = baseTypeSize(type);
|
|
36
|
+
return elementSize !== undefined && size % elementSize !== 0;
|
|
37
|
+
}
|
|
38
|
+
function areProfileBaseTypesCompatible(profileType, wireType) {
|
|
39
|
+
if (profileType === undefined || profileType === wireType) {
|
|
40
|
+
return true;
|
|
29
41
|
}
|
|
42
|
+
// FIT producers commonly interchange enum, uint8, and byte definitions.
|
|
43
|
+
// They have the same width and value representation; keeping the semantic
|
|
44
|
+
// profile type is safe while still rejecting width and sign conflicts.
|
|
45
|
+
return uint8CompatibleTypes.has(String(profileType))
|
|
46
|
+
&& uint8CompatibleTypes.has(String(wireType));
|
|
30
47
|
}
|
|
31
48
|
export function addEndian(littleEndian, bytes) {
|
|
32
49
|
let result = 0;
|
|
@@ -37,77 +54,10 @@ export function addEndian(littleEndian, bytes) {
|
|
|
37
54
|
}
|
|
38
55
|
return result;
|
|
39
56
|
}
|
|
40
|
-
function readData(blob, dataView, fDef, startIndex
|
|
41
|
-
var _a;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (let i = 0; i < fDef.size; i++) {
|
|
45
|
-
array8.push(blob[startIndex + i]);
|
|
46
|
-
}
|
|
47
|
-
return array8;
|
|
48
|
-
}
|
|
49
|
-
if (fDef.endianAbility) {
|
|
50
|
-
const requiresBoundedDataView = (_a = fDef.requiresBoundedDataView) !== null && _a !== void 0 ? _a : (fDef.requiresBoundedDataView = requiresBoundedEndianDataView(fDef.type, fDef.size));
|
|
51
|
-
let fieldDataView = dataView;
|
|
52
|
-
let fieldStartIndex = startIndex;
|
|
53
|
-
if (startIndex < 0
|
|
54
|
-
|| startIndex + fDef.size > blob.length
|
|
55
|
-
|| startIndex + fDef.size > dataView.byteLength) {
|
|
56
|
-
const paddedField = new Uint8Array(fDef.size);
|
|
57
|
-
for (let index = 0; index < fDef.size; index++) {
|
|
58
|
-
paddedField[index] = blob[startIndex + index];
|
|
59
|
-
}
|
|
60
|
-
fieldDataView = new DataView(paddedField.buffer);
|
|
61
|
-
fieldStartIndex = 0;
|
|
62
|
-
}
|
|
63
|
-
else if (requiresBoundedDataView) {
|
|
64
|
-
fieldDataView = new DataView(dataView.buffer, dataView.byteOffset + startIndex, fDef.size);
|
|
65
|
-
fieldStartIndex = 0;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
switch (fDef.type) {
|
|
69
|
-
case 'sint16':
|
|
70
|
-
return fieldDataView.getInt16(fieldStartIndex, fDef.littleEndian);
|
|
71
|
-
case 'uint16':
|
|
72
|
-
case 'uint16z':
|
|
73
|
-
return fieldDataView.getUint16(fieldStartIndex, fDef.littleEndian);
|
|
74
|
-
case 'sint32':
|
|
75
|
-
return fieldDataView.getInt32(fieldStartIndex, fDef.littleEndian);
|
|
76
|
-
case 'uint32':
|
|
77
|
-
case 'uint32z':
|
|
78
|
-
return fieldDataView.getUint32(fieldStartIndex, fDef.littleEndian);
|
|
79
|
-
case 'float32':
|
|
80
|
-
return fieldDataView.getFloat32(fieldStartIndex, fDef.littleEndian);
|
|
81
|
-
case 'float64':
|
|
82
|
-
return fieldDataView.getFloat64(fieldStartIndex, fDef.littleEndian);
|
|
83
|
-
case 'uint32_array': {
|
|
84
|
-
const array32 = [];
|
|
85
|
-
for (let i = 0; i < fDef.size; i += 4) {
|
|
86
|
-
array32.push(fieldDataView.getUint32(fieldStartIndex + i, fDef.littleEndian));
|
|
87
|
-
}
|
|
88
|
-
return array32;
|
|
89
|
-
}
|
|
90
|
-
case 'uint16_array': {
|
|
91
|
-
const array16 = [];
|
|
92
|
-
for (let i = 0; i < fDef.size; i += 2) {
|
|
93
|
-
array16.push(fieldDataView.getUint16(fieldStartIndex + i, fDef.littleEndian));
|
|
94
|
-
}
|
|
95
|
-
return array16;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
if (!options.force) {
|
|
101
|
-
throw e;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const temp = [];
|
|
105
|
-
for (let i = 0; i < fDef.size; i++) {
|
|
106
|
-
temp.push(blob[startIndex + i]);
|
|
107
|
-
}
|
|
108
|
-
return addEndian(fDef.littleEndian, temp);
|
|
109
|
-
}
|
|
110
|
-
if (fDef.type === 'string') {
|
|
57
|
+
function readData(blob, dataView, fDef, startIndex) {
|
|
58
|
+
var _a, _b;
|
|
59
|
+
const rawType = (_b = (_a = fDef.rawType) !== null && _a !== void 0 ? _a : FIT.types.fit_base_type[fDef.baseTypeNo]) !== null && _b !== void 0 ? _b : fDef.type;
|
|
60
|
+
if (rawType === 'string') {
|
|
111
61
|
const temp = [];
|
|
112
62
|
for (let i = 0; i < fDef.size; i++) {
|
|
113
63
|
if (blob[startIndex + i]) {
|
|
@@ -116,18 +66,57 @@ function readData(blob, dataView, fDef, startIndex, options) {
|
|
|
116
66
|
}
|
|
117
67
|
return Buffer.from(temp).toString('utf-8');
|
|
118
68
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
69
|
+
const elementSize = baseTypeSize(rawType);
|
|
70
|
+
if (elementSize === undefined) {
|
|
71
|
+
return InvalidFieldData;
|
|
72
|
+
}
|
|
73
|
+
if (fDef.size < elementSize
|
|
74
|
+
|| fDef.size % elementSize !== 0
|
|
75
|
+
|| startIndex < 0
|
|
76
|
+
|| startIndex + fDef.size > blob.length
|
|
77
|
+
|| startIndex + fDef.size > dataView.byteLength) {
|
|
78
|
+
return InvalidFieldData;
|
|
125
79
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
80
|
+
const isArray = fDef.array === true
|
|
81
|
+
|| String(fDef.type).endsWith('_array')
|
|
82
|
+
|| fDef.type === 'exercise_category_array'
|
|
83
|
+
|| (fDef.isDeveloperField === true && fDef.size > elementSize);
|
|
84
|
+
const elementCount = isArray ? fDef.size / elementSize : 1;
|
|
85
|
+
const values = [];
|
|
86
|
+
for (let elementIndex = 0; elementIndex < elementCount; elementIndex++) {
|
|
87
|
+
const offset = startIndex + elementIndex * elementSize;
|
|
88
|
+
switch (rawType) {
|
|
89
|
+
case 'sint8': {
|
|
90
|
+
const value = blob[offset];
|
|
91
|
+
values.push(value > 127 ? value - 256 : value);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case 'sint16':
|
|
95
|
+
values.push(dataView.getInt16(offset, fDef.littleEndian));
|
|
96
|
+
break;
|
|
97
|
+
case 'uint16':
|
|
98
|
+
case 'uint16z':
|
|
99
|
+
values.push(dataView.getUint16(offset, fDef.littleEndian));
|
|
100
|
+
break;
|
|
101
|
+
case 'sint32':
|
|
102
|
+
values.push(dataView.getInt32(offset, fDef.littleEndian));
|
|
103
|
+
break;
|
|
104
|
+
case 'uint32':
|
|
105
|
+
case 'uint32z':
|
|
106
|
+
values.push(dataView.getUint32(offset, fDef.littleEndian));
|
|
107
|
+
break;
|
|
108
|
+
case 'float32':
|
|
109
|
+
values.push(dataView.getFloat32(offset, fDef.littleEndian));
|
|
110
|
+
break;
|
|
111
|
+
case 'float64':
|
|
112
|
+
values.push(dataView.getFloat64(offset, fDef.littleEndian));
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
values.push(blob[offset]);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
129
118
|
}
|
|
130
|
-
return
|
|
119
|
+
return isArray ? values : values[0];
|
|
131
120
|
}
|
|
132
121
|
function formatByType(data, type, scale, offset) {
|
|
133
122
|
switch (type) {
|
|
@@ -154,6 +143,16 @@ function formatByType(data, type, scale, offset) {
|
|
|
154
143
|
});
|
|
155
144
|
}
|
|
156
145
|
return scale ? data / scale + offset : data;
|
|
146
|
+
case 'exercise_category_array':
|
|
147
|
+
if (Array.isArray(data)) {
|
|
148
|
+
return data.map((dataItem) => {
|
|
149
|
+
if (isInvalidValue(dataItem, 'uint16')) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return formatByType(dataItem, 'exercise_category', scale, offset);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return formatByType(data, 'exercise_category', scale, offset);
|
|
157
156
|
default:
|
|
158
157
|
{
|
|
159
158
|
const typeMap = FIT.types[type];
|
|
@@ -212,10 +211,9 @@ function isInvalidValue(data, type) {
|
|
|
212
211
|
case 'string':
|
|
213
212
|
return data === 0x00;
|
|
214
213
|
case 'float32':
|
|
215
|
-
return data
|
|
214
|
+
return Number.isNaN(data);
|
|
216
215
|
case 'float64':
|
|
217
|
-
|
|
218
|
-
return data === 0xFFFFFFFFFFFFFFFF;
|
|
216
|
+
return Number.isNaN(data);
|
|
219
217
|
case 'uint8z':
|
|
220
218
|
return data === 0x00;
|
|
221
219
|
case 'uint16z':
|
|
@@ -243,6 +241,27 @@ function isInvalidBaseTypeValue(data, baseTypeNo) {
|
|
|
243
241
|
const baseType = FIT.types.fit_base_type[baseTypeNo];
|
|
244
242
|
return typeof baseType === 'string' ? isInvalidValue(data, baseType) : false;
|
|
245
243
|
}
|
|
244
|
+
function formatFieldValue(data, fDef, options, fields) {
|
|
245
|
+
var _a, _b, _c;
|
|
246
|
+
const field = fDef.name;
|
|
247
|
+
const scale = (_a = fDef.scale) !== null && _a !== void 0 ? _a : null;
|
|
248
|
+
const offset = (_b = fDef.offset) !== null && _b !== void 0 ? _b : 0;
|
|
249
|
+
if (Array.isArray(data)
|
|
250
|
+
&& !String(fDef.type).endsWith('_array')
|
|
251
|
+
&& fDef.type !== 'exercise_category_array') {
|
|
252
|
+
const rawType = (_c = fDef.rawType) !== null && _c !== void 0 ? _c : FIT.types.fit_base_type[fDef.baseTypeNo];
|
|
253
|
+
return data.map((item) => {
|
|
254
|
+
if (isInvalidValue(item, rawType)) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return applyOptions(formatByType(item, fDef.type, scale, offset), field, options, fields);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return applyOptions(formatByType(data, fDef.type, scale, offset), field, options, fields);
|
|
261
|
+
}
|
|
262
|
+
function isOutputFieldName(field) {
|
|
263
|
+
return field !== 'unknown' && field !== '' && field !== undefined;
|
|
264
|
+
}
|
|
246
265
|
function convertTo(data, unitsList, unitName) {
|
|
247
266
|
const options = FIT.options[unitsList];
|
|
248
267
|
const unit = options[unitName];
|
|
@@ -301,8 +320,10 @@ function applyOptions(data, field, options, fields) {
|
|
|
301
320
|
return convertTo(data, 'lengthUnits', options.lengthUnit);
|
|
302
321
|
case 'temperature':
|
|
303
322
|
case 'min_temperature':
|
|
323
|
+
case 'temperature_min':
|
|
304
324
|
case 'avg_temperature':
|
|
305
325
|
case 'max_temperature':
|
|
326
|
+
case 'temperature_max':
|
|
306
327
|
return convertTo(data, 'temperatureUnits', options.temperatureUnit);
|
|
307
328
|
case 'pressure':
|
|
308
329
|
case 'start_pressure':
|
|
@@ -334,7 +355,7 @@ function applyGarminProductName(fields) {
|
|
|
334
355
|
}
|
|
335
356
|
}
|
|
336
357
|
function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, developerFields, options) {
|
|
337
|
-
var _a, _b, _c, _d;
|
|
358
|
+
var _a, _b, _c, _d, _e, _f;
|
|
338
359
|
const description = (_a = developerFields[developerFieldDef.developerDataIndex]) === null || _a === void 0 ? void 0 : _a[developerFieldDef.fieldDefinitionNumber];
|
|
339
360
|
if (!description) {
|
|
340
361
|
developerFieldDef.resolvedFieldDef = undefined;
|
|
@@ -345,9 +366,12 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
345
366
|
&& developerFieldDef.resolvedFieldDef) {
|
|
346
367
|
return developerFieldDef.resolvedFieldDef;
|
|
347
368
|
}
|
|
348
|
-
const
|
|
369
|
+
const describedBaseType = description.fit_base_type_id;
|
|
370
|
+
const baseType = typeof describedBaseType === 'number'
|
|
371
|
+
? describedBaseType
|
|
372
|
+
: Number((_b = Object.entries(FIT.types.fit_base_type).find(([, typeName]) => typeName === describedBaseType)) === null || _b === void 0 ? void 0 : _b[0]);
|
|
349
373
|
const type = FIT.types.fit_base_type[baseType];
|
|
350
|
-
if (
|
|
374
|
+
if (!Number.isInteger(baseType) || type === undefined) {
|
|
351
375
|
developerFieldDef.resolvedFieldDef = undefined;
|
|
352
376
|
developerFieldDef.resolvedFrom = undefined;
|
|
353
377
|
if (options.force) {
|
|
@@ -357,15 +381,18 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
357
381
|
}
|
|
358
382
|
const resolvedFieldDef = {
|
|
359
383
|
type,
|
|
384
|
+
rawType: type,
|
|
360
385
|
fDefNo: developerFieldDef.fieldDefinitionNumber,
|
|
361
386
|
size: developerFieldDef.size,
|
|
387
|
+
array: type !== 'string'
|
|
388
|
+
&& developerFieldDef.size > ((_c = baseTypeSize(type)) !== null && _c !== void 0 ? _c : developerFieldDef.size),
|
|
362
389
|
endianAbility: (baseType & 128) === 128,
|
|
363
390
|
littleEndian,
|
|
364
391
|
baseTypeNo: baseType,
|
|
365
|
-
name: (
|
|
392
|
+
name: (_d = description.field_name) !== null && _d !== void 0 ? _d : '',
|
|
366
393
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
367
|
-
scale: (
|
|
368
|
-
offset: (
|
|
394
|
+
scale: (_e = description.scale) !== null && _e !== void 0 ? _e : 1,
|
|
395
|
+
offset: (_f = description.offset) !== null && _f !== void 0 ? _f : 0,
|
|
369
396
|
requiresBoundedDataView: requiresBoundedEndianDataView(type, developerFieldDef.size),
|
|
370
397
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
371
398
|
isDeveloperField: true,
|
|
@@ -374,11 +401,12 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
374
401
|
developerFieldDef.resolvedFrom = description;
|
|
375
402
|
return resolvedFieldDef;
|
|
376
403
|
}
|
|
377
|
-
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength)) {
|
|
378
|
-
var _a, _b, _c
|
|
404
|
+
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), decoderState = {}) {
|
|
405
|
+
var _a, _b, _c;
|
|
379
406
|
const recordHeader = blob[startIndex];
|
|
380
407
|
let localMessageType = recordHeader & 15;
|
|
381
|
-
|
|
408
|
+
const isCompressedTimestamp = (recordHeader & CompressedHeaderMask) === CompressedHeaderMask;
|
|
409
|
+
if (isCompressedTimestamp) {
|
|
382
410
|
// compressed timestamp
|
|
383
411
|
localMessageType = (recordHeader & CompressedLocalMsgNumMask) >> 5;
|
|
384
412
|
}
|
|
@@ -406,19 +434,26 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
406
434
|
for (let i = 0; i < numberOfFields; i++) {
|
|
407
435
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
408
436
|
const baseType = blob[fDefIndex + 2];
|
|
409
|
-
const
|
|
437
|
+
const wireType = FIT.types.fit_base_type[baseType];
|
|
438
|
+
const { field, type, baseType: profileBaseType, array, scale, offset, aliases, } = message.getAttributes(blob[fDefIndex]);
|
|
439
|
+
const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
410
440
|
const fDef = {
|
|
411
|
-
type,
|
|
441
|
+
type: profileCompatible ? type : wireType,
|
|
442
|
+
rawType: wireType,
|
|
412
443
|
fDefNo: blob[fDefIndex],
|
|
413
444
|
size: blob[fDefIndex + 1],
|
|
445
|
+
array: profileCompatible
|
|
446
|
+
? array === true || String(type).endsWith('_array')
|
|
447
|
+
: false,
|
|
414
448
|
endianAbility: (baseType & 128) === 128,
|
|
415
449
|
littleEndian: lEnd,
|
|
416
450
|
baseTypeNo: baseType,
|
|
417
|
-
name: field,
|
|
451
|
+
name: profileCompatible ? field : '',
|
|
418
452
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
419
|
-
scale,
|
|
420
|
-
offset,
|
|
421
|
-
requiresBoundedDataView: requiresBoundedEndianDataView(
|
|
453
|
+
scale: profileCompatible ? scale : null,
|
|
454
|
+
offset: profileCompatible ? offset : 0,
|
|
455
|
+
requiresBoundedDataView: requiresBoundedEndianDataView(wireType, blob[fDefIndex + 1]),
|
|
456
|
+
aliases: profileCompatible ? aliases : undefined,
|
|
422
457
|
};
|
|
423
458
|
mTypeDef.fieldDefs.push(fDef);
|
|
424
459
|
}
|
|
@@ -440,8 +475,6 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
440
475
|
};
|
|
441
476
|
}
|
|
442
477
|
const messageType = messageTypes[localMessageType] || messageTypes[0];
|
|
443
|
-
// TODO: handle compressed header ((recordHeader & 128) == 128)
|
|
444
|
-
// uncompressed header
|
|
445
478
|
let messageSize = 0;
|
|
446
479
|
let readDataFromIndex = startIndex + 1;
|
|
447
480
|
const fields = {};
|
|
@@ -452,10 +485,15 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
452
485
|
let validFieldCount = 0;
|
|
453
486
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
454
487
|
const fDef = messageType.fieldDefs[i];
|
|
455
|
-
const data = readData(blob, dataView, fDef, readDataFromIndex
|
|
456
|
-
if (
|
|
488
|
+
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
489
|
+
if (data !== InvalidFieldData
|
|
490
|
+
&& !isInvalidValue(data, fDef.type)
|
|
491
|
+
&& !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
457
492
|
rawData[i] = data;
|
|
458
493
|
validFieldCount++;
|
|
494
|
+
if (!isCompressedTimestamp && fDef.fDefNo === 253 && typeof data === 'number') {
|
|
495
|
+
decoderState.lastTimestamp = data;
|
|
496
|
+
}
|
|
459
497
|
}
|
|
460
498
|
else {
|
|
461
499
|
rawData[i] = InvalidFieldData;
|
|
@@ -468,8 +506,10 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
468
506
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
469
507
|
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
470
508
|
if (fDef) {
|
|
471
|
-
const data = readData(blob, dataView, fDef, readDataFromIndex
|
|
472
|
-
if (
|
|
509
|
+
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
510
|
+
if (data !== InvalidFieldData
|
|
511
|
+
&& !isInvalidValue(data, fDef.type)
|
|
512
|
+
&& !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
473
513
|
rawData[rawDataIndex] = data;
|
|
474
514
|
validFieldCount++;
|
|
475
515
|
}
|
|
@@ -489,9 +529,15 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
489
529
|
continue;
|
|
490
530
|
}
|
|
491
531
|
const fDef = messageType.fieldDefs[i];
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
532
|
+
if (isOutputFieldName(fDef.name)) {
|
|
533
|
+
fields[fDef.name] = data;
|
|
534
|
+
}
|
|
535
|
+
if (fDef.aliases) {
|
|
536
|
+
for (const alias of fDef.aliases) {
|
|
537
|
+
if (isOutputFieldName(alias.field)) {
|
|
538
|
+
fields[alias.field] = data;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
495
541
|
}
|
|
496
542
|
}
|
|
497
543
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
@@ -511,19 +557,14 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
511
557
|
continue;
|
|
512
558
|
}
|
|
513
559
|
const fDef = messageType.fieldDefs[i];
|
|
514
|
-
if (fDef.
|
|
515
|
-
|
|
516
|
-
const { type } = fDef;
|
|
517
|
-
const scale = (_d = fDef.scale) !== null && _d !== void 0 ? _d : null;
|
|
518
|
-
const offset = (_e = fDef.offset) !== null && _e !== void 0 ? _e : 0;
|
|
519
|
-
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
560
|
+
if (isOutputFieldName(fDef.name)) {
|
|
561
|
+
fields[fDef.name] = formatFieldValue(data, fDef, options, fields);
|
|
520
562
|
}
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
563
|
+
if (fDef.aliases) {
|
|
564
|
+
for (const alias of fDef.aliases) {
|
|
565
|
+
if (isOutputFieldName(alias.field)) {
|
|
566
|
+
fields[alias.field] = formatFieldValue(data, Object.assign(Object.assign(Object.assign({}, fDef), alias), { name: alias.field, aliases: undefined }), options, fields);
|
|
567
|
+
}
|
|
527
568
|
}
|
|
528
569
|
}
|
|
529
570
|
}
|
|
@@ -535,7 +576,24 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
535
576
|
}
|
|
536
577
|
const field = fDef.name;
|
|
537
578
|
if (field !== 'unknown' && field !== '') {
|
|
538
|
-
fields[field] =
|
|
579
|
+
fields[field] = formatFieldValue(data, fDef, options, fields);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (isCompressedTimestamp) {
|
|
583
|
+
const previousTimestamp = decoderState.lastTimestamp;
|
|
584
|
+
if (previousTimestamp === undefined) {
|
|
585
|
+
if (!options.force) {
|
|
586
|
+
throw new Error('Compressed timestamp requires a previous timestamp');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
const timeOffset = recordHeader & CompressedTimestampMask;
|
|
591
|
+
const previousOffset = previousTimestamp & CompressedTimestampMask;
|
|
592
|
+
const rollover = timeOffset < previousOffset ? 0x20 : 0;
|
|
593
|
+
const timestamp = (previousTimestamp & ~CompressedTimestampMask) + timeOffset + rollover;
|
|
594
|
+
decoderState.lastTimestamp = timestamp;
|
|
595
|
+
fields.timestamp = new Date(timestamp * 1000 + GarminTimeOffset);
|
|
596
|
+
validFieldCount++;
|
|
539
597
|
}
|
|
540
598
|
}
|
|
541
599
|
applyGarminProductName(fields);
|
|
@@ -549,17 +607,20 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
549
607
|
developerFields[fields.developer_data_index][fields.field_definition_number] = fields;
|
|
550
608
|
}
|
|
551
609
|
if (message.name === 'monitoring') {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
610
|
+
const timestampFieldIndex = messageType.fieldDefs.findIndex(fieldDefinition => fieldDefinition.fDefNo === 253);
|
|
611
|
+
const rawTimestamp = timestampFieldIndex >= 0
|
|
612
|
+
? rawData[timestampFieldIndex]
|
|
613
|
+
: InvalidFieldData;
|
|
614
|
+
if (rawTimestamp !== InvalidFieldData && typeof rawTimestamp === 'number') {
|
|
615
|
+
decoderState.monitoringTimestamp = rawTimestamp;
|
|
616
|
+
fields.timestamp = new Date(rawTimestamp * 1000 + GarminTimeOffset);
|
|
557
617
|
}
|
|
558
|
-
if (fields.timestamp16
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
618
|
+
else if (fields.timestamp16
|
|
619
|
+
&& decoderState.monitoringTimestamp !== undefined
|
|
620
|
+
&& !fields.timestamp) {
|
|
621
|
+
decoderState.monitoringTimestamp
|
|
622
|
+
+= (fields.timestamp16 - (decoderState.monitoringTimestamp & 0xFFFF)) & 0xFFFF;
|
|
623
|
+
fields.timestamp = new Date(decoderState.monitoringTimestamp * 1000 + GarminTimeOffset);
|
|
563
624
|
}
|
|
564
625
|
}
|
|
565
626
|
return {
|
package/dist/cjs/binary.d.ts
CHANGED
|
@@ -17,8 +17,12 @@ export interface DeveloperFieldDefinition {
|
|
|
17
17
|
resolvedFieldDef?: FieldDefinition;
|
|
18
18
|
resolvedFrom?: unknown;
|
|
19
19
|
}
|
|
20
|
+
export interface DecoderState {
|
|
21
|
+
lastTimestamp?: number;
|
|
22
|
+
monitoringTimestamp?: number;
|
|
23
|
+
}
|
|
20
24
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
21
|
-
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView): {
|
|
25
|
+
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState): {
|
|
22
26
|
messageType: MesgNum | '';
|
|
23
27
|
nextIndex: number;
|
|
24
28
|
message?: any;
|