fit-file-parser 4.1.0 → 5.0.2
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 +59 -0
- package/README.md +90 -1
- package/dist/binary.d.ts +2 -3
- package/dist/binary.js +23 -40
- package/dist/cjs/binary.d.ts +2 -3
- package/dist/cjs/binary.js +23 -40
- package/dist/cjs/fit-parser.js +4 -2
- package/dist/cjs/fit.d.ts +11 -9
- package/dist/cjs/fit.js +123 -9187
- package/dist/cjs/fit_types.d.ts +551 -555
- package/dist/cjs/garmin_profile.generated.js +479 -479
- package/dist/cjs/helper.js +22 -12
- package/dist/cjs/messages.d.ts +2 -3
- package/dist/cjs/type_generator.js +36 -35
- package/dist/fit-parser.js +4 -2
- package/dist/fit.d.ts +11 -9
- package/dist/fit.js +122 -9186
- package/dist/fit_types.d.ts +551 -555
- package/dist/garmin_profile.generated.js +479 -479
- package/dist/helper.js +22 -12
- package/dist/messages.d.ts +2 -3
- package/dist/type_generator.js +36 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 5.0.2
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Clarify the 5.0 migration for standard session field 196: use canonical
|
|
10
|
+
`metabolic_calories` rather than the old duplicate `resting_calories` label.
|
|
11
|
+
- Clarify that the removed guessed `recovery_advisor` label was standard
|
|
12
|
+
session field 140, which the SDK defines as `avg_depth` in meters.
|
|
13
|
+
|
|
14
|
+
## 5.0.1
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Run CI and version-driven npm publishing from the repository's `main`
|
|
19
|
+
branch, with a guarded manual recovery path for a missed release.
|
|
20
|
+
|
|
21
|
+
## 5.0.0
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Generate the complete standard message, field, and type surface from the
|
|
26
|
+
pinned Garmin FIT SDK without handwritten standard-profile overrides.
|
|
27
|
+
- Preserve SDK alphanumeric tokens when generating public `snake_case` names,
|
|
28
|
+
including `n2`, `po2`, `time128`, and Garmin product identifiers.
|
|
29
|
+
- Restrict non-SDK support to a collision-checked allowlist of Garmin fields
|
|
30
|
+
and private messages observed in the external FIT corpus.
|
|
31
|
+
- Emit only parsed FIT values: Garmin product names are no longer inferred,
|
|
32
|
+
and record elapsed/timer values are generated only when
|
|
33
|
+
`elapsedRecordField: true` is requested.
|
|
34
|
+
|
|
35
|
+
### Breaking
|
|
36
|
+
|
|
37
|
+
- Remove compatibility aliases such as `resting_calories`,
|
|
38
|
+
`recovery_advisor`, `repeat_dive_time`, and deprecated time-in-zone names.
|
|
39
|
+
- Remove guessed stress fields, handwritten enum/type additions, placeholder
|
|
40
|
+
zero-mask names, and the misspelled `hip_swing_excercise_name` type.
|
|
41
|
+
- Use the pinned SDK's `cadence_zone_high_bondary` spelling and generated
|
|
42
|
+
alphanumeric field/type spellings instead of manual corrections.
|
|
43
|
+
- Correct generated declarations to expose runtime `Date` values, numeric FIT
|
|
44
|
+
`bool` fields and unknown enum IDs, decoded mask objects, nullable array
|
|
45
|
+
entries, and optional profile fields.
|
|
46
|
+
- Keep undated cascade rows in file order and avoid duplicating them when a lap
|
|
47
|
+
or session boundary timestamp is absent.
|
|
48
|
+
|
|
49
|
+
## 4.1.1
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- Apply Garmin FIT profile scale and offset to primitive numeric fields, while
|
|
54
|
+
retaining semicircle conversion only for fields declared in semicircles.
|
|
55
|
+
- Apply FIT developer-field offsets with the parser's equivalent signed form,
|
|
56
|
+
including scaled signed developer values.
|
|
57
|
+
- Decode native diving ascent-rate fields as meters per second instead of
|
|
58
|
+
geographic coordinates.
|
|
59
|
+
- Decode native dive depth and bottom-time fields with their Garmin FIT SDK
|
|
60
|
+
scale instead of exposing their encoded integer representation.
|
|
61
|
+
- Keep the pinned Garmin SDK authoritative for standard field types, arrays,
|
|
62
|
+
scales, offsets, and units while retaining compatible public field names.
|
|
63
|
+
|
|
5
64
|
## 4.1.0
|
|
6
65
|
|
|
7
66
|
### Added
|
package/README.md
CHANGED
|
@@ -29,6 +29,57 @@ including developer-defined data.
|
|
|
29
29
|
npm install fit-file-parser
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Migrating from 4.x to 5.0
|
|
33
|
+
|
|
34
|
+
Version 5.0 is a breaking release because parsed output now follows the pinned
|
|
35
|
+
Garmin FIT SDK profile without compatibility aliases or guessed fields. Parser
|
|
36
|
+
construction, module imports, output modes, and parser options are unchanged.
|
|
37
|
+
|
|
38
|
+
Update field access, destructuring, persisted schemas, and snapshots to use the
|
|
39
|
+
SDK-backed names. Common migrations include:
|
|
40
|
+
|
|
41
|
+
| 4.x name | 5.0 name |
|
|
42
|
+
| ------------------------------ | ---------------------------- |
|
|
43
|
+
| `speed_1s` | `speed1s` |
|
|
44
|
+
| `start_n_2`, `end_n_2` | `start_n2`, `end_n2` |
|
|
45
|
+
| `o_2_toxicity` | `o2_toxicity` |
|
|
46
|
+
| `avg_spo_2`, `reading_spo_2` | `avg_spo2`, `reading_spo2` |
|
|
47
|
+
| `po_2` | `po2` |
|
|
48
|
+
| `cycle_length_16` | `cycle_length16` |
|
|
49
|
+
| `map_3_sample_mean` | `map3_sample_mean` |
|
|
50
|
+
| `time_256` | `time256` |
|
|
51
|
+
| `last_night_5_min_high` | `last_night5_min_high` |
|
|
52
|
+
| `average_7_day_deviation` | `average7_day_deviation` |
|
|
53
|
+
| `spo_2_data`, `hsa_spo_2_data` | `spo2_data`, `hsa_spo2_data` |
|
|
54
|
+
| `repeat_dive_time` | `repeat_dive_interval` |
|
|
55
|
+
| `cadence_zone_high_boundary` | `cadence_zone_high_bondary` |
|
|
56
|
+
| `HipSwingExcerciseName` | `HipSwingExerciseName` |
|
|
57
|
+
| `resting_calories` | `metabolic_calories` |
|
|
58
|
+
|
|
59
|
+
The same alphanumeric-token rule applies to enum strings, for example
|
|
60
|
+
`camera_orientation_90` becomes `camera_orientation90`, `po_2_warn` becomes
|
|
61
|
+
`po2_warn`, and `power_3s` becomes `power3s`. The generated TypeScript
|
|
62
|
+
declarations are the exhaustive name and value reference for the pinned SDK.
|
|
63
|
+
|
|
64
|
+
Parser 4 exposed standard session field 196 (`metabolic_calories`) a second
|
|
65
|
+
time as `resting_calories`; use the canonical `metabolic_calories` name in 5.0.
|
|
66
|
+
`recovery_advisor` was a guessed label for standard session field 140, whose
|
|
67
|
+
canonical SDK field is `avg_depth` in meters. Other behavior to account for
|
|
68
|
+
during migration:
|
|
69
|
+
|
|
70
|
+
- `product_name` is emitted only when it exists in the FIT input. It is no
|
|
71
|
+
longer inferred from `manufacturer` and `product`.
|
|
72
|
+
- Record `elapsed_time` and `timer_time` require
|
|
73
|
+
`elapsedRecordField: true`.
|
|
74
|
+
- FIT timestamps are typed as `Date`, FIT `bool` values remain numeric, mask
|
|
75
|
+
fields are `{ value, ...flags }` objects, unknown enum values remain numbers,
|
|
76
|
+
and array entries may be `null` when the FIT invalid sentinel is retained.
|
|
77
|
+
- Every profile field is optional because individual FIT message definitions
|
|
78
|
+
determine which fields are present.
|
|
79
|
+
- When upgrading from 4.1.0 or earlier, remove application-side scale or offset
|
|
80
|
+
corrections for parsed numeric values, including dive depth, bottom time,
|
|
81
|
+
ascent rate, and developer fields. The parser now applies the SDK metadata.
|
|
82
|
+
|
|
32
83
|
## Quick start
|
|
33
84
|
|
|
34
85
|
The Promise API is the simplest way to parse a file:
|
|
@@ -125,6 +176,28 @@ const diveSummaries = data.messages?.dive_summary ?? []
|
|
|
125
176
|
Existing root lists, cascade nesting, and last-message root properties remain
|
|
126
177
|
unchanged.
|
|
127
178
|
|
|
179
|
+
## Profile-backed output
|
|
180
|
+
|
|
181
|
+
Standard message names, field names, enum values, wire types, scales, offsets,
|
|
182
|
+
arrays, and units come from the exactly pinned Garmin FIT SDK profile. Public
|
|
183
|
+
names use the parser's generated `snake_case` form while preserving SDK
|
|
184
|
+
alphanumeric tokens such as `n2`, `po2`, and `time128`. The parser does not add
|
|
185
|
+
compatibility aliases for alternate field spellings.
|
|
186
|
+
|
|
187
|
+
The small vendor extension table contains only Garmin fields and private
|
|
188
|
+
messages observed in the external FIT corpus. Extensions cannot replace a
|
|
189
|
+
standard SDK field or type value; the profile audit rejects collisions and
|
|
190
|
+
unregistered additions.
|
|
191
|
+
|
|
192
|
+
Only values present in the FIT input are emitted. In particular,
|
|
193
|
+
`product_name` is not inferred from `manufacturer` and `product`, and record
|
|
194
|
+
`elapsed_time` and `timer_time` are added only when `elapsedRecordField: true`
|
|
195
|
+
is requested. Parsed FIT timestamps are `Date` objects, FIT `bool` fields keep
|
|
196
|
+
their numeric wire values, mask fields decode to `{ value, ...flags }` objects,
|
|
197
|
+
unknown enum IDs remain numbers, and invalid entries retained inside FIT arrays
|
|
198
|
+
are `null`. All profile fields are optional because each FIT message definition
|
|
199
|
+
chooses which fields are present.
|
|
200
|
+
|
|
128
201
|
## Inputs
|
|
129
202
|
|
|
130
203
|
Both parser methods accept:
|
|
@@ -228,8 +301,24 @@ Run commands from the repository root.
|
|
|
228
301
|
| `npm run examples` | Build and regenerate checked-in example outputs. |
|
|
229
302
|
| `npm run check` | Run profile audit, lint, types, tests, and builds. |
|
|
230
303
|
|
|
304
|
+
### External FIT corpus (optional)
|
|
305
|
+
|
|
306
|
+
The external corpus is not part of this repository or the npm package. For the
|
|
307
|
+
standard contributor layout, clone it alongside this checkout, then run the
|
|
308
|
+
aggregate-only validation command:
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
git clone https://github.com/ThomasKuehne/FIT-test-files.git ../FIT-test-files
|
|
312
|
+
npm run corpus:check -- ../FIT-test-files --allow-force-recovery
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The command accepts any corpus path; the sibling location is only a convenient
|
|
316
|
+
convention. The corpus contains a known header-CRC failure that is expected to
|
|
317
|
+
recover only in force mode. It reports aggregate counts and never prints file
|
|
318
|
+
names or parsed activity data.
|
|
319
|
+
|
|
231
320
|
Do not edit `src/garmin_profile.generated.ts` or `src/fit_types.ts` manually.
|
|
232
|
-
Update the pinned SDK,
|
|
321
|
+
Update the pinned SDK, audited vendor extensions, or a generator, then run
|
|
233
322
|
`npm run codegen`.
|
|
234
323
|
|
|
235
324
|
Repository-specific automation guidance is tracked in
|
package/dist/binary.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { FitParserOptions } from './fit-parser.js';
|
|
2
|
-
import type { FieldDefinition } from './fit.js';
|
|
3
|
-
import type { MesgNum } from './fit_types.js';
|
|
2
|
+
import type { FieldDefinition, MessageName } from './fit.js';
|
|
4
3
|
import { Buffer } from 'buffer';
|
|
5
4
|
export interface MessageTypeDefinition {
|
|
6
5
|
littleEndian: boolean;
|
|
@@ -23,7 +22,7 @@ export interface DecoderState {
|
|
|
23
22
|
}
|
|
24
23
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
25
24
|
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState): {
|
|
26
|
-
messageType:
|
|
25
|
+
messageType: MessageName | 'definition' | '';
|
|
27
26
|
nextIndex: number;
|
|
28
27
|
message?: any;
|
|
29
28
|
};
|
package/dist/binary.js
CHANGED
|
@@ -118,17 +118,25 @@ function readData(blob, dataView, fDef, startIndex) {
|
|
|
118
118
|
}
|
|
119
119
|
return isArray ? values : values[0];
|
|
120
120
|
}
|
|
121
|
-
function formatByType(data, type, scale, offset) {
|
|
121
|
+
function formatByType(data, type, scale, offset, units) {
|
|
122
122
|
switch (type) {
|
|
123
123
|
case 'date_time':
|
|
124
124
|
case 'local_date_time':
|
|
125
125
|
return new Date(data * 1000 + GarminTimeOffset);
|
|
126
126
|
case 'sint32':
|
|
127
|
-
return
|
|
127
|
+
return units === 'semicircles'
|
|
128
|
+
? data * FIT.scConst
|
|
129
|
+
: scale ? data / scale + offset : data;
|
|
130
|
+
case 'sint8':
|
|
128
131
|
case 'uint8':
|
|
132
|
+
case 'uint8z':
|
|
129
133
|
case 'sint16':
|
|
130
|
-
case 'uint32':
|
|
131
134
|
case 'uint16':
|
|
135
|
+
case 'uint16z':
|
|
136
|
+
case 'uint32':
|
|
137
|
+
case 'uint32z':
|
|
138
|
+
case 'float32':
|
|
139
|
+
case 'float64':
|
|
132
140
|
return scale ? data / scale + offset : data;
|
|
133
141
|
case 'uint32_array':
|
|
134
142
|
case 'uint16_array':
|
|
@@ -185,7 +193,7 @@ function formatByType(data, type, scale, offset) {
|
|
|
185
193
|
dataItem.value = data & Number(key);
|
|
186
194
|
}
|
|
187
195
|
else {
|
|
188
|
-
dataItem[value] =
|
|
196
|
+
dataItem[value] = (data & Number(key)) !== 0;
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
199
|
return dataItem;
|
|
@@ -254,10 +262,10 @@ function formatFieldValue(data, fDef, options, fields) {
|
|
|
254
262
|
if (isInvalidValue(item, rawType)) {
|
|
255
263
|
return null;
|
|
256
264
|
}
|
|
257
|
-
return applyOptions(formatByType(item, fDef.type, scale, offset), field, options, fields);
|
|
265
|
+
return applyOptions(formatByType(item, fDef.type, scale, offset, fDef.units), field, options, fields);
|
|
258
266
|
});
|
|
259
267
|
}
|
|
260
|
-
return applyOptions(formatByType(data, fDef.type, scale, offset), field, options, fields);
|
|
268
|
+
return applyOptions(formatByType(data, fDef.type, scale, offset, fDef.units), field, options, fields);
|
|
261
269
|
}
|
|
262
270
|
function isOutputFieldName(field) {
|
|
263
271
|
return field !== 'unknown' && field !== '' && field !== undefined;
|
|
@@ -289,7 +297,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
289
297
|
case 'vertical_speed':
|
|
290
298
|
case 'avg_speed':
|
|
291
299
|
case 'max_speed':
|
|
292
|
-
case '
|
|
300
|
+
case 'speed1s':
|
|
293
301
|
case 'ball_speed':
|
|
294
302
|
case 'enhanced_avg_speed':
|
|
295
303
|
case 'enhanced_max_speed':
|
|
@@ -340,22 +348,8 @@ function applyOptions(data, field, options, fields) {
|
|
|
340
348
|
return data;
|
|
341
349
|
}
|
|
342
350
|
}
|
|
343
|
-
function applyGarminProductName(fields) {
|
|
344
|
-
if (fields.product_name !== undefined || fields.manufacturer !== 'garmin') {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const product = fields.product;
|
|
348
|
-
if (typeof product !== 'number') {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const productName = FIT.types.garmin_product[product];
|
|
352
|
-
if (typeof productName === 'string') {
|
|
353
|
-
// Keep the raw protocol ID and expose the SDK product name separately.
|
|
354
|
-
fields.product_name = productName;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
351
|
function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, developerFields, options) {
|
|
358
|
-
var _a, _b, _c, _d, _e, _f;
|
|
352
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
359
353
|
const description = (_a = developerFields[developerFieldDef.developerDataIndex]) === null || _a === void 0 ? void 0 : _a[developerFieldDef.fieldDefinitionNumber];
|
|
360
354
|
if (!description) {
|
|
361
355
|
developerFieldDef.resolvedFieldDef = undefined;
|
|
@@ -392,7 +386,11 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
392
386
|
name: (_d = description.field_name) !== null && _d !== void 0 ? _d : '',
|
|
393
387
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
394
388
|
scale: (_e = description.scale) !== null && _e !== void 0 ? _e : 1,
|
|
395
|
-
|
|
389
|
+
// FIT developer-field descriptions use `raw / scale - offset`. This
|
|
390
|
+
// parser's formatter retains the legacy equivalent signed offset and adds
|
|
391
|
+
// it after scaling.
|
|
392
|
+
offset: -((_f = description.offset) !== null && _f !== void 0 ? _f : 0),
|
|
393
|
+
units: (_g = description.units) !== null && _g !== void 0 ? _g : '',
|
|
396
394
|
requiresBoundedDataView: requiresBoundedEndianDataView(type, developerFieldDef.size),
|
|
397
395
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
398
396
|
isDeveloperField: true,
|
|
@@ -435,7 +433,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
435
433
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
436
434
|
const baseType = blob[fDefIndex + 2];
|
|
437
435
|
const wireType = FIT.types.fit_base_type[baseType];
|
|
438
|
-
const { field, type, baseType: profileBaseType, array, scale, offset,
|
|
436
|
+
const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
|
|
439
437
|
const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
440
438
|
const fDef = {
|
|
441
439
|
type: profileCompatible ? type : wireType,
|
|
@@ -452,8 +450,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
452
450
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
453
451
|
scale: profileCompatible ? scale : null,
|
|
454
452
|
offset: profileCompatible ? offset : 0,
|
|
453
|
+
units: profileCompatible ? units : '',
|
|
455
454
|
requiresBoundedDataView: requiresBoundedEndianDataView(wireType, blob[fDefIndex + 1]),
|
|
456
|
-
aliases: profileCompatible ? aliases : undefined,
|
|
457
455
|
};
|
|
458
456
|
mTypeDef.fieldDefs.push(fDef);
|
|
459
457
|
}
|
|
@@ -532,13 +530,6 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
532
530
|
if (isOutputFieldName(fDef.name)) {
|
|
533
531
|
fields[fDef.name] = data;
|
|
534
532
|
}
|
|
535
|
-
if (fDef.aliases) {
|
|
536
|
-
for (const alias of fDef.aliases) {
|
|
537
|
-
if (isOutputFieldName(alias.field)) {
|
|
538
|
-
fields[alias.field] = data;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
533
|
}
|
|
543
534
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
544
535
|
const data = rawData[messageType.fieldDefs.length + i];
|
|
@@ -560,13 +551,6 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
560
551
|
if (isOutputFieldName(fDef.name)) {
|
|
561
552
|
fields[fDef.name] = formatFieldValue(data, fDef, options, fields);
|
|
562
553
|
}
|
|
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
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
554
|
}
|
|
571
555
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
572
556
|
const data = rawData[messageType.fieldDefs.length + i];
|
|
@@ -596,7 +580,6 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
596
580
|
validFieldCount++;
|
|
597
581
|
}
|
|
598
582
|
}
|
|
599
|
-
applyGarminProductName(fields);
|
|
600
583
|
if (validFieldCount > 0 && message.name === 'record' && options.elapsedRecordField) {
|
|
601
584
|
fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
|
|
602
585
|
fields.timer_time = fields.elapsed_time - pausedTime;
|
package/dist/cjs/binary.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { FitParserOptions } from './fit-parser.js';
|
|
2
|
-
import type { FieldDefinition } from './fit.js';
|
|
3
|
-
import type { MesgNum } from './fit_types.js';
|
|
2
|
+
import type { FieldDefinition, MessageName } from './fit.js';
|
|
4
3
|
import { Buffer } from 'buffer';
|
|
5
4
|
export interface MessageTypeDefinition {
|
|
6
5
|
littleEndian: boolean;
|
|
@@ -23,7 +22,7 @@ export interface DecoderState {
|
|
|
23
22
|
}
|
|
24
23
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
25
24
|
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState): {
|
|
26
|
-
messageType:
|
|
25
|
+
messageType: MessageName | 'definition' | '';
|
|
27
26
|
nextIndex: number;
|
|
28
27
|
message?: any;
|
|
29
28
|
};
|
package/dist/cjs/binary.js
CHANGED
|
@@ -124,17 +124,25 @@ function readData(blob, dataView, fDef, startIndex) {
|
|
|
124
124
|
}
|
|
125
125
|
return isArray ? values : values[0];
|
|
126
126
|
}
|
|
127
|
-
function formatByType(data, type, scale, offset) {
|
|
127
|
+
function formatByType(data, type, scale, offset, units) {
|
|
128
128
|
switch (type) {
|
|
129
129
|
case 'date_time':
|
|
130
130
|
case 'local_date_time':
|
|
131
131
|
return new Date(data * 1000 + GarminTimeOffset);
|
|
132
132
|
case 'sint32':
|
|
133
|
-
return
|
|
133
|
+
return units === 'semicircles'
|
|
134
|
+
? data * fit_js_1.FIT.scConst
|
|
135
|
+
: scale ? data / scale + offset : data;
|
|
136
|
+
case 'sint8':
|
|
134
137
|
case 'uint8':
|
|
138
|
+
case 'uint8z':
|
|
135
139
|
case 'sint16':
|
|
136
|
-
case 'uint32':
|
|
137
140
|
case 'uint16':
|
|
141
|
+
case 'uint16z':
|
|
142
|
+
case 'uint32':
|
|
143
|
+
case 'uint32z':
|
|
144
|
+
case 'float32':
|
|
145
|
+
case 'float64':
|
|
138
146
|
return scale ? data / scale + offset : data;
|
|
139
147
|
case 'uint32_array':
|
|
140
148
|
case 'uint16_array':
|
|
@@ -191,7 +199,7 @@ function formatByType(data, type, scale, offset) {
|
|
|
191
199
|
dataItem.value = data & Number(key);
|
|
192
200
|
}
|
|
193
201
|
else {
|
|
194
|
-
dataItem[value] =
|
|
202
|
+
dataItem[value] = (data & Number(key)) !== 0;
|
|
195
203
|
}
|
|
196
204
|
}
|
|
197
205
|
return dataItem;
|
|
@@ -260,10 +268,10 @@ function formatFieldValue(data, fDef, options, fields) {
|
|
|
260
268
|
if (isInvalidValue(item, rawType)) {
|
|
261
269
|
return null;
|
|
262
270
|
}
|
|
263
|
-
return applyOptions(formatByType(item, fDef.type, scale, offset), field, options, fields);
|
|
271
|
+
return applyOptions(formatByType(item, fDef.type, scale, offset, fDef.units), field, options, fields);
|
|
264
272
|
});
|
|
265
273
|
}
|
|
266
|
-
return applyOptions(formatByType(data, fDef.type, scale, offset), field, options, fields);
|
|
274
|
+
return applyOptions(formatByType(data, fDef.type, scale, offset, fDef.units), field, options, fields);
|
|
267
275
|
}
|
|
268
276
|
function isOutputFieldName(field) {
|
|
269
277
|
return field !== 'unknown' && field !== '' && field !== undefined;
|
|
@@ -295,7 +303,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
295
303
|
case 'vertical_speed':
|
|
296
304
|
case 'avg_speed':
|
|
297
305
|
case 'max_speed':
|
|
298
|
-
case '
|
|
306
|
+
case 'speed1s':
|
|
299
307
|
case 'ball_speed':
|
|
300
308
|
case 'enhanced_avg_speed':
|
|
301
309
|
case 'enhanced_max_speed':
|
|
@@ -346,22 +354,8 @@ function applyOptions(data, field, options, fields) {
|
|
|
346
354
|
return data;
|
|
347
355
|
}
|
|
348
356
|
}
|
|
349
|
-
function applyGarminProductName(fields) {
|
|
350
|
-
if (fields.product_name !== undefined || fields.manufacturer !== 'garmin') {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const product = fields.product;
|
|
354
|
-
if (typeof product !== 'number') {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
const productName = fit_js_1.FIT.types.garmin_product[product];
|
|
358
|
-
if (typeof productName === 'string') {
|
|
359
|
-
// Keep the raw protocol ID and expose the SDK product name separately.
|
|
360
|
-
fields.product_name = productName;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
357
|
function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, developerFields, options) {
|
|
364
|
-
var _a, _b, _c, _d, _e, _f;
|
|
358
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
365
359
|
const description = (_a = developerFields[developerFieldDef.developerDataIndex]) === null || _a === void 0 ? void 0 : _a[developerFieldDef.fieldDefinitionNumber];
|
|
366
360
|
if (!description) {
|
|
367
361
|
developerFieldDef.resolvedFieldDef = undefined;
|
|
@@ -398,7 +392,11 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
398
392
|
name: (_d = description.field_name) !== null && _d !== void 0 ? _d : '',
|
|
399
393
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
400
394
|
scale: (_e = description.scale) !== null && _e !== void 0 ? _e : 1,
|
|
401
|
-
|
|
395
|
+
// FIT developer-field descriptions use `raw / scale - offset`. This
|
|
396
|
+
// parser's formatter retains the legacy equivalent signed offset and adds
|
|
397
|
+
// it after scaling.
|
|
398
|
+
offset: -((_f = description.offset) !== null && _f !== void 0 ? _f : 0),
|
|
399
|
+
units: (_g = description.units) !== null && _g !== void 0 ? _g : '',
|
|
402
400
|
requiresBoundedDataView: requiresBoundedEndianDataView(type, developerFieldDef.size),
|
|
403
401
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
404
402
|
isDeveloperField: true,
|
|
@@ -441,7 +439,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
441
439
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
442
440
|
const baseType = blob[fDefIndex + 2];
|
|
443
441
|
const wireType = fit_js_1.FIT.types.fit_base_type[baseType];
|
|
444
|
-
const { field, type, baseType: profileBaseType, array, scale, offset,
|
|
442
|
+
const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
|
|
445
443
|
const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
446
444
|
const fDef = {
|
|
447
445
|
type: profileCompatible ? type : wireType,
|
|
@@ -458,8 +456,8 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
458
456
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
459
457
|
scale: profileCompatible ? scale : null,
|
|
460
458
|
offset: profileCompatible ? offset : 0,
|
|
459
|
+
units: profileCompatible ? units : '',
|
|
461
460
|
requiresBoundedDataView: requiresBoundedEndianDataView(wireType, blob[fDefIndex + 1]),
|
|
462
|
-
aliases: profileCompatible ? aliases : undefined,
|
|
463
461
|
};
|
|
464
462
|
mTypeDef.fieldDefs.push(fDef);
|
|
465
463
|
}
|
|
@@ -538,13 +536,6 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
538
536
|
if (isOutputFieldName(fDef.name)) {
|
|
539
537
|
fields[fDef.name] = data;
|
|
540
538
|
}
|
|
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
539
|
}
|
|
549
540
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
550
541
|
const data = rawData[messageType.fieldDefs.length + i];
|
|
@@ -566,13 +557,6 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
566
557
|
if (isOutputFieldName(fDef.name)) {
|
|
567
558
|
fields[fDef.name] = formatFieldValue(data, fDef, options, fields);
|
|
568
559
|
}
|
|
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
560
|
}
|
|
577
561
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
578
562
|
const data = rawData[messageType.fieldDefs.length + i];
|
|
@@ -602,7 +586,6 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
602
586
|
validFieldCount++;
|
|
603
587
|
}
|
|
604
588
|
}
|
|
605
|
-
applyGarminProductName(fields);
|
|
606
589
|
if (validFieldCount > 0 && message.name === 'record' && options.elapsedRecordField) {
|
|
607
590
|
fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
|
|
608
591
|
fields.timer_time = fields.elapsed_time - pausedTime;
|
package/dist/cjs/fit-parser.js
CHANGED
|
@@ -168,8 +168,10 @@ class FitParser {
|
|
|
168
168
|
case 'record':
|
|
169
169
|
if (!startDate) {
|
|
170
170
|
startDate = message.timestamp;
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
if (this.options.elapsedRecordField) {
|
|
172
|
+
message.elapsed_time = 0;
|
|
173
|
+
message.timer_time = 0;
|
|
174
|
+
}
|
|
173
175
|
}
|
|
174
176
|
records.push(message);
|
|
175
177
|
break;
|
package/dist/cjs/fit.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FitOptions, MesgNum } from './fit_types.js';
|
|
2
|
+
export type MessageName = Exclude<MesgNum, number | 'definition'>;
|
|
2
3
|
export interface FieldDefinition {
|
|
3
4
|
type: string | number;
|
|
4
5
|
rawType?: string | number;
|
|
@@ -12,10 +13,10 @@ export interface FieldDefinition {
|
|
|
12
13
|
dataType: string;
|
|
13
14
|
scale?: number | null;
|
|
14
15
|
offset?: number;
|
|
16
|
+
units?: string;
|
|
15
17
|
requiresBoundedDataView?: boolean;
|
|
16
18
|
developerDataIndex?: number;
|
|
17
19
|
isDeveloperField?: boolean;
|
|
18
|
-
aliases?: MessageObject[];
|
|
19
20
|
}
|
|
20
21
|
export interface MessageObject {
|
|
21
22
|
field: string;
|
|
@@ -25,20 +26,21 @@ export interface MessageObject {
|
|
|
25
26
|
scale: number | null;
|
|
26
27
|
offset: number;
|
|
27
28
|
units: string;
|
|
28
|
-
aliases?: MessageObject[];
|
|
29
29
|
}
|
|
30
30
|
export interface Message {
|
|
31
|
-
name:
|
|
31
|
+
name: MessageName;
|
|
32
32
|
[fieldId: number]: MessageObject;
|
|
33
33
|
}
|
|
34
34
|
export interface FitType {
|
|
35
35
|
scConst: number;
|
|
36
36
|
options: FitOptions;
|
|
37
|
-
messages:
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
types: {
|
|
41
|
-
[typeName: string]: Record<number, string | number>;
|
|
42
|
-
};
|
|
37
|
+
messages: Record<number, Message>;
|
|
38
|
+
types: Record<string, Record<number, string | number>>;
|
|
43
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Garmin fields observed in the external FIT corpus but absent from the pinned
|
|
42
|
+
* public SDK profile. These additions may not replace standard SDK fields.
|
|
43
|
+
*/
|
|
44
|
+
export declare const FIT_VENDOR_MESSAGE_EXTENSIONS: Record<number, Message>;
|
|
45
|
+
export declare const FIT_VENDOR_TYPE_EXTENSIONS: Record<string, Record<number, string>>;
|
|
44
46
|
export declare const FIT: FitType;
|