fit-file-parser 1.20.0 → 2.0.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/.github/workflows/ci.yml +29 -0
- package/CHANGELOG.md +22 -22
- package/CONTRIBUTORS.md +3 -1
- package/README.md +39 -21
- package/dist/binary.d.ts +11 -0
- package/dist/binary.js +181 -221
- package/dist/fit-parser.d.ts +19 -0
- package/dist/fit-parser.js +232 -252
- package/dist/fit.d.ts +52 -0
- package/dist/fit.js +7886 -4295
- package/dist/helper.d.ts +3 -0
- package/dist/helper.js +63 -66
- package/dist/messages.d.ts +6 -0
- package/dist/messages.js +19 -20
- package/dist/types.d.ts +55 -0
- package/dist/types.js +1 -0
- package/eslint.config.js +7 -0
- package/package.json +38 -48
- package/tsconfig.json +15 -0
- package/vitest.config.js +8 -0
package/dist/binary.js
CHANGED
|
@@ -1,47 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var _buffer = require('buffer');
|
|
16
|
-
|
|
17
|
-
function addEndian(littleEndian, bytes) {
|
|
18
|
-
var result = 0;
|
|
19
|
-
if (!littleEndian) bytes.reverse();
|
|
20
|
-
for (var i = 0; i < bytes.length; i++) {
|
|
21
|
-
result += bytes[i] << (i << 3) >>> 0;
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { FIT } from './fit.js';
|
|
3
|
+
import { getFitMessage, getFitMessageBaseType } from './messages.js';
|
|
4
|
+
const CompressedLocalMsgNumMask = 0x60;
|
|
5
|
+
const CompressedHeaderMask = 0x80;
|
|
6
|
+
const GarminTimeOffset = 631065600000;
|
|
7
|
+
let monitoring_timestamp = 0;
|
|
8
|
+
export function addEndian(littleEndian, bytes) {
|
|
9
|
+
let result = 0;
|
|
10
|
+
if (!littleEndian)
|
|
11
|
+
bytes.reverse();
|
|
12
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
13
|
+
result += (bytes[i] << (i << 3)) >>> 0;
|
|
22
14
|
}
|
|
23
|
-
|
|
24
15
|
return result;
|
|
25
16
|
}
|
|
26
|
-
|
|
27
|
-
var timestamp = 0;
|
|
28
|
-
var lastTimeOffset = 0;
|
|
29
|
-
var CompressedTimeMask = 31;
|
|
30
|
-
var CompressedLocalMesgNumMask = 0x60;
|
|
31
|
-
var CompressedHeaderMask = 0x80;
|
|
32
|
-
var GarminTimeOffset = 631065600000;
|
|
33
|
-
var monitoring_timestamp = 0;
|
|
34
|
-
|
|
35
17
|
function readData(blob, fDef, startIndex, options) {
|
|
36
|
-
if (fDef.endianAbility
|
|
37
|
-
|
|
38
|
-
for (
|
|
18
|
+
if (fDef.endianAbility) {
|
|
19
|
+
const temp = [];
|
|
20
|
+
for (let i = 0; i < fDef.size; i++) {
|
|
39
21
|
temp.push(blob[startIndex + i]);
|
|
40
22
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
var dataView = new DataView(buffer);
|
|
44
|
-
|
|
23
|
+
const { buffer } = new Uint8Array(temp);
|
|
24
|
+
const dataView = new DataView(buffer);
|
|
45
25
|
try {
|
|
46
26
|
switch (fDef.type) {
|
|
47
27
|
case 'sint16':
|
|
@@ -58,56 +38,54 @@ function readData(blob, fDef, startIndex, options) {
|
|
|
58
38
|
return dataView.getFloat32(0, fDef.littleEndian);
|
|
59
39
|
case 'float64':
|
|
60
40
|
return dataView.getFloat64(0, fDef.littleEndian);
|
|
61
|
-
case 'uint32_array':
|
|
62
|
-
|
|
63
|
-
for (
|
|
64
|
-
array32.push(dataView.getUint32(
|
|
41
|
+
case 'uint32_array': {
|
|
42
|
+
const array32 = [];
|
|
43
|
+
for (let i = 0; i < fDef.size; i += 4) {
|
|
44
|
+
array32.push(dataView.getUint32(i, fDef.littleEndian));
|
|
65
45
|
}
|
|
66
46
|
return array32;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
}
|
|
48
|
+
case 'uint16_array': {
|
|
49
|
+
const array = [];
|
|
50
|
+
for (let i = 0; i < fDef.size; i += 2) {
|
|
51
|
+
array.push(dataView.getUint16(i, fDef.littleEndian));
|
|
71
52
|
}
|
|
72
53
|
return array;
|
|
54
|
+
}
|
|
73
55
|
}
|
|
74
|
-
}
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
75
58
|
if (!options.force) {
|
|
76
59
|
throw e;
|
|
77
60
|
}
|
|
78
61
|
}
|
|
79
|
-
|
|
80
62
|
return addEndian(fDef.littleEndian, temp);
|
|
81
63
|
}
|
|
82
|
-
|
|
83
64
|
if (fDef.type === 'string') {
|
|
84
|
-
|
|
85
|
-
for (
|
|
86
|
-
if (blob[startIndex +
|
|
87
|
-
|
|
65
|
+
const temp = [];
|
|
66
|
+
for (let i = 0; i < fDef.size; i++) {
|
|
67
|
+
if (blob[startIndex + i]) {
|
|
68
|
+
temp.push(blob[startIndex + i]);
|
|
88
69
|
}
|
|
89
70
|
}
|
|
90
|
-
return
|
|
71
|
+
return Buffer.from(temp).toString('utf-8');
|
|
91
72
|
}
|
|
92
|
-
|
|
93
73
|
if (fDef.type === 'byte_array') {
|
|
94
|
-
|
|
95
|
-
for (
|
|
96
|
-
|
|
74
|
+
const temp = [];
|
|
75
|
+
for (let i = 0; i < fDef.size; i++) {
|
|
76
|
+
temp.push(blob[startIndex + i]);
|
|
97
77
|
}
|
|
98
|
-
return
|
|
78
|
+
return temp;
|
|
99
79
|
}
|
|
100
|
-
|
|
101
80
|
return blob[startIndex];
|
|
102
81
|
}
|
|
103
|
-
|
|
104
82
|
function formatByType(data, type, scale, offset) {
|
|
105
83
|
switch (type) {
|
|
106
84
|
case 'date_time':
|
|
107
85
|
case 'local_date_time':
|
|
108
86
|
return new Date(data * 1000 + GarminTimeOffset);
|
|
109
87
|
case 'sint32':
|
|
110
|
-
return data *
|
|
88
|
+
return data * FIT.scConst;
|
|
111
89
|
case 'uint8':
|
|
112
90
|
case 'sint16':
|
|
113
91
|
case 'uint32':
|
|
@@ -115,37 +93,37 @@ function formatByType(data, type, scale, offset) {
|
|
|
115
93
|
return scale ? data / scale + offset : data;
|
|
116
94
|
case 'uint32_array':
|
|
117
95
|
case 'uint16_array':
|
|
118
|
-
return data.map(
|
|
119
|
-
return scale ? dataItem / scale + offset : dataItem;
|
|
120
|
-
});
|
|
96
|
+
return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
|
|
121
97
|
default:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// Quick check for a mask
|
|
126
|
-
var values = [];
|
|
127
|
-
for (var key in _fit.FIT.types[type]) {
|
|
128
|
-
if (_fit.FIT.types[type].hasOwnProperty(key)) {
|
|
129
|
-
values.push(_fit.FIT.types[type][key]);
|
|
98
|
+
{
|
|
99
|
+
if (!FIT.types[type]) {
|
|
100
|
+
return data;
|
|
130
101
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
for (var key in _fit.FIT.types[type]) {
|
|
137
|
-
if (_fit.FIT.types[type].hasOwnProperty(key)) {
|
|
138
|
-
if (_fit.FIT.types[type][key] === 'mask') {
|
|
139
|
-
dataItem.value = data & key;
|
|
140
|
-
} else {
|
|
141
|
-
dataItem[_fit.FIT.types[type][key]] = !!((data & key) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
102
|
+
// Quick check for a mask
|
|
103
|
+
const values = [];
|
|
104
|
+
for (const key in FIT.types[type]) {
|
|
105
|
+
if (key in FIT.types[type]) {
|
|
106
|
+
values.push(String(FIT.types[type][key]));
|
|
142
107
|
}
|
|
143
108
|
}
|
|
109
|
+
if (!values.includes('mask')) {
|
|
110
|
+
return FIT.types[type][data];
|
|
111
|
+
}
|
|
112
|
+
const dataItem = {};
|
|
113
|
+
for (const key in FIT.types[type]) {
|
|
114
|
+
if (key in FIT.types[type]) {
|
|
115
|
+
if (FIT.types[type][key] === 'mask') {
|
|
116
|
+
dataItem.value = data & Number(key);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
dataItem[FIT.types[type][key]] = !!((data & Number(key)) >> 7); // Not sure if we need the >> 7 and casting to boolean but from all the masked props of fields so far this seems to be the case
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return dataItem;
|
|
144
124
|
}
|
|
145
|
-
return dataItem;
|
|
146
125
|
}
|
|
147
126
|
}
|
|
148
|
-
|
|
149
127
|
function isInvalidValue(data, type) {
|
|
150
128
|
switch (type) {
|
|
151
129
|
case 'enum':
|
|
@@ -167,6 +145,7 @@ function isInvalidValue(data, type) {
|
|
|
167
145
|
case 'float32':
|
|
168
146
|
return data === 0xFFFFFFFF;
|
|
169
147
|
case 'float64':
|
|
148
|
+
// eslint-disable-next-line no-loss-of-precision
|
|
170
149
|
return data === 0xFFFFFFFFFFFFFFFF;
|
|
171
150
|
case 'uint8z':
|
|
172
151
|
return data === 0x00;
|
|
@@ -177,8 +156,10 @@ function isInvalidValue(data, type) {
|
|
|
177
156
|
case 'byte':
|
|
178
157
|
return data === 0xFF;
|
|
179
158
|
case 'sint64':
|
|
159
|
+
// eslint-disable-next-line no-loss-of-precision
|
|
180
160
|
return data === 0x7FFFFFFFFFFFFFFF;
|
|
181
161
|
case 'uint64':
|
|
162
|
+
// eslint-disable-next-line no-loss-of-precision
|
|
182
163
|
return data === 0xFFFFFFFFFFFFFFFF;
|
|
183
164
|
case 'uint64z':
|
|
184
165
|
return data === 0x0000000000000000;
|
|
@@ -186,12 +167,11 @@ function isInvalidValue(data, type) {
|
|
|
186
167
|
return false;
|
|
187
168
|
}
|
|
188
169
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
170
|
+
function convertTo(data, unitsList, unitName) {
|
|
171
|
+
const options = FIT.options[unitsList];
|
|
172
|
+
const unit = options[unitName];
|
|
173
|
+
return unit ? data * unit.multiplier + unit.offset : data;
|
|
193
174
|
}
|
|
194
|
-
|
|
195
175
|
function applyOptions(data, field, options) {
|
|
196
176
|
switch (field) {
|
|
197
177
|
case 'speed':
|
|
@@ -240,205 +220,185 @@ function applyOptions(data, field, options) {
|
|
|
240
220
|
return data;
|
|
241
221
|
}
|
|
242
222
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
var localMessageType = recordHeader & 15;
|
|
247
|
-
|
|
223
|
+
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
|
|
224
|
+
const recordHeader = blob[startIndex];
|
|
225
|
+
let localMessageType = recordHeader & 15;
|
|
248
226
|
if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
|
|
249
|
-
//compressed timestamp
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
lastTimeOffset = timeoffset;
|
|
254
|
-
|
|
255
|
-
localMessageType = (recordHeader & CompressedLocalMesgNumMask) >> 5;
|
|
256
|
-
} else if ((recordHeader & 64) === 64) {
|
|
227
|
+
// compressed timestamp
|
|
228
|
+
localMessageType = (recordHeader & CompressedLocalMsgNumMask) >> 5;
|
|
229
|
+
}
|
|
230
|
+
else if ((recordHeader & 64) === 64) {
|
|
257
231
|
// is definition message
|
|
258
232
|
// startIndex + 1 is reserved
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
233
|
+
const hasDeveloperData = (recordHeader & 32) === 32;
|
|
234
|
+
const lEnd = blob[startIndex + 2] === 0;
|
|
235
|
+
const numberOfFields = blob[startIndex + 5];
|
|
236
|
+
const numberOfDeveloperDataFields = hasDeveloperData
|
|
237
|
+
? blob[startIndex + 5 + numberOfFields * 3 + 1]
|
|
238
|
+
: 0;
|
|
239
|
+
const mTypeDef = {
|
|
266
240
|
littleEndian: lEnd,
|
|
267
|
-
globalMessageNumber: addEndian(lEnd, [
|
|
241
|
+
globalMessageNumber: addEndian(lEnd, [
|
|
242
|
+
blob[startIndex + 3],
|
|
243
|
+
blob[startIndex + 4],
|
|
244
|
+
]),
|
|
268
245
|
numberOfFields: numberOfFields + numberOfDeveloperDataFields,
|
|
269
|
-
fieldDefs: []
|
|
246
|
+
fieldDefs: [],
|
|
270
247
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
var _message$getAttribute = _message.getAttributes(blob[fDefIndex]),
|
|
279
|
-
field = _message$getAttribute.field,
|
|
280
|
-
type = _message$getAttribute.type;
|
|
281
|
-
|
|
282
|
-
var fDef = {
|
|
283
|
-
type: type,
|
|
248
|
+
const message = getFitMessage(mTypeDef.globalMessageNumber);
|
|
249
|
+
for (let i = 0; i < numberOfFields; i++) {
|
|
250
|
+
const fDefIndex = startIndex + 6 + i * 3;
|
|
251
|
+
const baseType = blob[fDefIndex + 2];
|
|
252
|
+
const { field, type } = message.getAttributes(blob[fDefIndex]);
|
|
253
|
+
const fDef = {
|
|
254
|
+
type,
|
|
284
255
|
fDefNo: blob[fDefIndex],
|
|
285
256
|
size: blob[fDefIndex + 1],
|
|
286
257
|
endianAbility: (baseType & 128) === 128,
|
|
287
258
|
littleEndian: lEnd,
|
|
288
259
|
baseTypeNo: baseType & 15,
|
|
289
260
|
name: field,
|
|
290
|
-
dataType:
|
|
261
|
+
dataType: getFitMessageBaseType(baseType & 15),
|
|
291
262
|
};
|
|
292
|
-
|
|
293
263
|
mTypeDef.fieldDefs.push(fDef);
|
|
294
264
|
}
|
|
295
|
-
|
|
296
265
|
// numberOfDeveloperDataFields = 0 so it wont crash here and wont loop
|
|
297
|
-
for (
|
|
266
|
+
for (let i = 0; i < numberOfDeveloperDataFields; i++) {
|
|
298
267
|
// If we fail to parse then try catch
|
|
299
268
|
try {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
var _baseType = devDef.fit_base_type_id;
|
|
309
|
-
|
|
310
|
-
var _fDef = {
|
|
311
|
-
type: _fit.FIT.types.fit_base_type[_baseType],
|
|
269
|
+
const fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + i * 3;
|
|
270
|
+
const fieldNum = blob[fDefIndex];
|
|
271
|
+
const size = blob[fDefIndex + 1];
|
|
272
|
+
const devDataIndex = blob[fDefIndex + 2];
|
|
273
|
+
const devDef = developerFields[devDataIndex][fieldNum];
|
|
274
|
+
const baseType = devDef.fit_base_type_id;
|
|
275
|
+
const fDef = {
|
|
276
|
+
type: FIT.types.fit_base_type[baseType],
|
|
312
277
|
fDefNo: fieldNum,
|
|
313
|
-
size
|
|
314
|
-
endianAbility: (
|
|
278
|
+
size,
|
|
279
|
+
endianAbility: (baseType & 128) === 128,
|
|
315
280
|
littleEndian: lEnd,
|
|
316
|
-
baseTypeNo:
|
|
281
|
+
baseTypeNo: baseType & 15,
|
|
317
282
|
name: devDef.field_name,
|
|
318
|
-
dataType:
|
|
283
|
+
dataType: getFitMessageBaseType(baseType & 15),
|
|
319
284
|
scale: devDef.scale || 1,
|
|
320
285
|
offset: devDef.offset || 0,
|
|
321
286
|
developerDataIndex: devDataIndex,
|
|
322
|
-
isDeveloperField: true
|
|
287
|
+
isDeveloperField: true,
|
|
323
288
|
};
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
289
|
+
mTypeDef.fieldDefs.push(fDef);
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
327
292
|
if (options.force) {
|
|
328
293
|
continue;
|
|
329
294
|
}
|
|
330
295
|
throw e;
|
|
331
296
|
}
|
|
332
297
|
}
|
|
333
|
-
|
|
334
298
|
messageTypes[localMessageType] = mTypeDef;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
var nextIndexWithDeveloperData = nextIndex + 1;
|
|
338
|
-
|
|
299
|
+
const nextIndex = startIndex + 6 + mTypeDef.numberOfFields * 3;
|
|
300
|
+
const nextIndexWithDeveloperData = nextIndex + 1;
|
|
339
301
|
return {
|
|
340
302
|
messageType: 'definition',
|
|
341
|
-
nextIndex: hasDeveloperData ? nextIndexWithDeveloperData : nextIndex
|
|
303
|
+
nextIndex: hasDeveloperData ? nextIndexWithDeveloperData : nextIndex,
|
|
342
304
|
};
|
|
343
305
|
}
|
|
344
|
-
|
|
345
|
-
var messageType = messageTypes[localMessageType] || messageTypes[0];
|
|
346
|
-
|
|
306
|
+
const messageType = messageTypes[localMessageType] || messageTypes[0];
|
|
347
307
|
// TODO: handle compressed header ((recordHeader & 128) == 128)
|
|
348
|
-
|
|
349
308
|
// uncompressed header
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
var _message$getAttribute2 = message.getAttributes(_fDef2.fDefNo),
|
|
370
|
-
_field = _message$getAttribute2.field,
|
|
371
|
-
_type = _message$getAttribute2.type,
|
|
372
|
-
_scale = _message$getAttribute2.scale,
|
|
373
|
-
_offset = _message$getAttribute2.offset;
|
|
374
|
-
|
|
375
|
-
if (_field !== 'unknown' && _field !== '' && _field !== undefined) {
|
|
376
|
-
fields[_field] = applyOptions(formatByType(data, _type, _scale, _offset), _field, options);
|
|
309
|
+
let messageSize = 0;
|
|
310
|
+
let readDataFromIndex = startIndex + 1;
|
|
311
|
+
const fields = {};
|
|
312
|
+
const message = getFitMessage(messageType.globalMessageNumber);
|
|
313
|
+
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
314
|
+
const fDef = messageType.fieldDefs[i];
|
|
315
|
+
const data = readData(blob, fDef, readDataFromIndex, options);
|
|
316
|
+
if (!isInvalidValue(data, fDef.type)) {
|
|
317
|
+
if (fDef.isDeveloperField) {
|
|
318
|
+
const field = fDef.name;
|
|
319
|
+
const { type } = fDef;
|
|
320
|
+
const { scale } = fDef;
|
|
321
|
+
const { offset } = fDef;
|
|
322
|
+
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const { field, type, scale, offset } = message.getAttributes(fDef.fDefNo);
|
|
326
|
+
if (field !== 'unknown' && field !== '' && field !== undefined) {
|
|
327
|
+
fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options);
|
|
377
328
|
}
|
|
378
329
|
}
|
|
379
|
-
|
|
380
330
|
if (message.name === 'record' && options.elapsedRecordField) {
|
|
381
|
-
fields.elapsed_time = (fields.timestamp - startDate) / 1000;
|
|
331
|
+
fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
|
|
382
332
|
fields.timer_time = fields.elapsed_time - pausedTime;
|
|
383
333
|
}
|
|
384
334
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
messageSize += _fDef2.size;
|
|
335
|
+
readDataFromIndex += fDef.size;
|
|
336
|
+
messageSize += fDef.size;
|
|
388
337
|
}
|
|
389
|
-
|
|
390
338
|
if (message.name === 'field_description') {
|
|
391
|
-
developerFields[fields.developer_data_index]
|
|
339
|
+
developerFields[fields.developer_data_index]
|
|
340
|
+
= developerFields[fields.developer_data_index] || [];
|
|
392
341
|
developerFields[fields.developer_data_index][fields.field_definition_number] = fields;
|
|
393
342
|
}
|
|
394
|
-
|
|
395
343
|
if (message.name === 'monitoring') {
|
|
396
|
-
//
|
|
344
|
+
// TODO weirdly uses global variables?
|
|
345
|
+
// we need to keep the raw timestamp value so we can calculate subsequent timestamp16 fields
|
|
397
346
|
if (fields.timestamp) {
|
|
398
347
|
monitoring_timestamp = fields.timestamp;
|
|
399
348
|
fields.timestamp = new Date(fields.timestamp * 1000 + GarminTimeOffset);
|
|
400
349
|
}
|
|
401
350
|
if (fields.timestamp16 && !fields.timestamp) {
|
|
402
|
-
monitoring_timestamp
|
|
403
|
-
|
|
351
|
+
monitoring_timestamp
|
|
352
|
+
+= (fields.timestamp16 - (monitoring_timestamp & 0xFFFF)) & 0xFFFF;
|
|
353
|
+
// fields.timestamp = monitoring_timestamp;
|
|
404
354
|
fields.timestamp = new Date(monitoring_timestamp * 1000 + GarminTimeOffset);
|
|
405
355
|
}
|
|
406
356
|
}
|
|
407
|
-
|
|
408
|
-
var result = {
|
|
357
|
+
return {
|
|
409
358
|
messageType: message.name,
|
|
410
359
|
nextIndex: startIndex + messageSize + 1,
|
|
411
|
-
message: fields
|
|
360
|
+
message: fields,
|
|
412
361
|
};
|
|
413
|
-
|
|
414
|
-
return result;
|
|
415
362
|
}
|
|
416
|
-
|
|
417
|
-
function getArrayBuffer(buffer) {
|
|
363
|
+
export function getArrayBuffer(buffer) {
|
|
418
364
|
if (buffer instanceof ArrayBuffer) {
|
|
419
365
|
return buffer;
|
|
420
366
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
for (
|
|
367
|
+
const ab = new ArrayBuffer(buffer.length);
|
|
368
|
+
const view = new Uint8Array(ab);
|
|
369
|
+
for (let i = 0; i < buffer.length; ++i) {
|
|
424
370
|
view[i] = buffer[i];
|
|
425
371
|
}
|
|
426
372
|
return ab;
|
|
427
373
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
374
|
+
export function calculateCRC(blob, start, end) {
|
|
375
|
+
const crcTable = [
|
|
376
|
+
0x0000,
|
|
377
|
+
0xCC01,
|
|
378
|
+
0xD801,
|
|
379
|
+
0x1400,
|
|
380
|
+
0xF001,
|
|
381
|
+
0x3C00,
|
|
382
|
+
0x2800,
|
|
383
|
+
0xE401,
|
|
384
|
+
0xA001,
|
|
385
|
+
0x6C00,
|
|
386
|
+
0x7800,
|
|
387
|
+
0xB401,
|
|
388
|
+
0x5000,
|
|
389
|
+
0x9C01,
|
|
390
|
+
0x8801,
|
|
391
|
+
0x4400,
|
|
392
|
+
];
|
|
393
|
+
let crc = 0;
|
|
394
|
+
for (let i = start; i < end; i++) {
|
|
395
|
+
const byteVal = blob[i];
|
|
396
|
+
let tmp = crcTable[crc & 0xF];
|
|
397
|
+
crc = (crc >> 4) & 0x0FFF;
|
|
437
398
|
crc = crc ^ tmp ^ crcTable[byteVal & 0xF];
|
|
438
399
|
tmp = crcTable[crc & 0xF];
|
|
439
|
-
crc = crc >> 4 & 0x0FFF;
|
|
440
|
-
crc = crc ^ tmp ^ crcTable[byteVal >> 4 & 0xF];
|
|
400
|
+
crc = (crc >> 4) & 0x0FFF;
|
|
401
|
+
crc = crc ^ tmp ^ crcTable[(byteVal >> 4) & 0xF];
|
|
441
402
|
}
|
|
442
|
-
|
|
443
403
|
return crc;
|
|
444
|
-
}
|
|
404
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Buffer } from 'node:buffer';
|
|
2
|
+
import type { ParsedFit } from './types.js';
|
|
3
|
+
export interface FitParserOptions {
|
|
4
|
+
force?: boolean;
|
|
5
|
+
speedUnit?: string;
|
|
6
|
+
lengthUnit?: string;
|
|
7
|
+
temperatureUnit?: string;
|
|
8
|
+
elapsedRecordField?: boolean;
|
|
9
|
+
pressureUnit?: string;
|
|
10
|
+
mode?: 'list' | 'cascade' | 'both';
|
|
11
|
+
}
|
|
12
|
+
type FitParserCallback = (error: string | undefined, data: ParsedFit | undefined) => void;
|
|
13
|
+
export default class FitParser {
|
|
14
|
+
private readonly options;
|
|
15
|
+
constructor(options?: FitParserOptions);
|
|
16
|
+
parseAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit>;
|
|
17
|
+
parse(content: ArrayBuffer | Buffer<ArrayBuffer>, callback: FitParserCallback): void;
|
|
18
|
+
}
|
|
19
|
+
export {};
|