fit-file-parser 6.1.2 → 6.2.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 +32 -0
- package/PROFILE.md +23 -0
- package/README.md +42 -5
- package/dist/binary.js +25 -12
- package/dist/cjs/binary.js +25 -12
- package/dist/cjs/fit-parser.d.ts +8 -0
- package/dist/cjs/fit-parser.js +84 -0
- package/dist/cjs/fit_types.d.ts +23 -1
- package/dist/cjs/profile.js +166 -0
- package/dist/cjs/type_generator.js +1 -0
- package/dist/fit-parser.d.ts +8 -0
- package/dist/fit-parser.js +84 -0
- package/dist/fit_types.d.ts +23 -1
- package/dist/profile.js +166 -0
- package/dist/type_generator.js +1 -0
- package/package.json +14 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 6.2.0 - 2026-10-10
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add `parseFilesAsync()` to return every complete FIT file in concatenated
|
|
8
|
+
input as a separate result, with independent decoder state and file order.
|
|
9
|
+
- Decode documented session and lap swimming summaries, segment and gear-shift
|
|
10
|
+
counts, lap bounds, metabolic calories, and grade-adjusted speed.
|
|
11
|
+
- Retain every battery sample in `batteries` and `messages.battery`, including
|
|
12
|
+
timestamp and capacity percentage when present.
|
|
13
|
+
- Add `--all-files` to the external corpus checker to validate appended files.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Preserve exact bytes for developer fields without usable names in opt-in
|
|
18
|
+
`unmapped_messages`, including fields whose descriptions have been cached.
|
|
19
|
+
- Recover unsigned-byte record temperatures from 0 through 126 with normal
|
|
20
|
+
unit conversion, while retaining their original bytes in opt-in output.
|
|
21
|
+
- Reject records and definitions extending beyond the declared data section
|
|
22
|
+
when using the complete-file `parseFilesAsync()` API.
|
|
23
|
+
|
|
24
|
+
### Compatibility
|
|
25
|
+
|
|
26
|
+
- Existing `parseAsync()` and callback APIs retain their single-file output
|
|
27
|
+
and recovery behavior. Newly supported fields and battery collections are
|
|
28
|
+
additive.
|
|
29
|
+
- `parseFilesAsync()` returns a one-element array for ordinary FIT input and
|
|
30
|
+
rejects incomplete files or trailing non-FIT data, even with `force: true`.
|
|
31
|
+
CRC validation follows the caller's `force` option for every file.
|
|
32
|
+
- Unsigned temperature bytes from 127 through 255 and other undocumented or
|
|
33
|
+
incompatible fields remain undecoded and available through opt-in retention.
|
|
34
|
+
|
|
3
35
|
## 6.1.2 - 2026-09-22
|
|
4
36
|
|
|
5
37
|
### Changed
|
package/PROFILE.md
CHANGED
|
@@ -31,6 +31,23 @@ complete raw representation of selected messages can continue to use
|
|
|
31
31
|
`includeRawMessages`; `unmapped_messages` stores only the fields the maintained
|
|
32
32
|
table cannot decode.
|
|
33
33
|
|
|
34
|
+
The unsigned-byte record-temperature interoperability recovery also retains
|
|
35
|
+
the original byte here, even when its unambiguous value is decoded.
|
|
36
|
+
|
|
37
|
+
## Focused interoperability additions
|
|
38
|
+
|
|
39
|
+
Session fields 79, 80, 106, 107, 108, and 211; lap fields 27–30, 73, 90, 96,
|
|
40
|
+
97, 155, and 161; and battery message 104 fields 2 and 253 were checked against
|
|
41
|
+
independently observed wire definitions in the external FIT corpus and the
|
|
42
|
+
redistributable interoperability definitions for
|
|
43
|
+
[session](https://github.com/muktihari/fit/blob/126e1a11233f87b7f139254d991bc3d2d952b62c/profile/mesgdef/session_gen.go),
|
|
44
|
+
[lap](https://github.com/muktihari/fit/blob/126e1a11233f87b7f139254d991bc3d2d952b62c/profile/mesgdef/lap_gen.go),
|
|
45
|
+
and [battery](https://github.com/muktihari/fit/blob/126e1a11233f87b7f139254d991bc3d2d952b62c/profile/mesgdef/battery_gen.go).
|
|
46
|
+
The focused mappings expose swimming summaries, segment and shift counts,
|
|
47
|
+
lap bounds, metabolic calories, grade-adjusted speed, and battery samples.
|
|
48
|
+
Synthetic regression tests pin identifiers, wire types, scales, units, invalid
|
|
49
|
+
sentinels, and collection behavior. Other battery fields remain unmapped.
|
|
50
|
+
|
|
34
51
|
## Updating the table
|
|
35
52
|
|
|
36
53
|
Every mapping change requires focused regression coverage. Do not guess
|
|
@@ -45,7 +62,13 @@ npm run profile:check
|
|
|
45
62
|
npm run check
|
|
46
63
|
npm run compatibility:check -- /path/to/reference-package /path/to/fit-corpus
|
|
47
64
|
npm run corpus:check -- ../FIT-test-files --allow-force-recovery
|
|
65
|
+
npm run corpus:check -- ../FIT-test-files --allow-force-recovery --all-files
|
|
48
66
|
```
|
|
49
67
|
|
|
50
68
|
`src/fit_types.ts` is generated from the maintained table and must not be
|
|
51
69
|
edited manually.
|
|
70
|
+
|
|
71
|
+
`--all-files` validates every appended FIT file through `parseFilesAsync()` and
|
|
72
|
+
reports both parsed FIT file counts and concatenated input counts. Recovery
|
|
73
|
+
counts apply to whole inputs; unmapped summaries count each parsed FIT file
|
|
74
|
+
separately in this mode.
|
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ including developer-defined data.
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- Parse Node.js `Buffer` and standard `ArrayBuffer` inputs.
|
|
14
|
+
- Parse concatenated FIT files as separate results with `parseFilesAsync()`.
|
|
14
15
|
- Choose flat lists, nested activity data, or both output shapes.
|
|
15
16
|
- Convert speed, length, temperature, and pressure fields to preferred units.
|
|
16
17
|
- Decode developer fields while preserving record alignment when descriptions
|
|
@@ -33,7 +34,7 @@ npm install fit-file-parser
|
|
|
33
34
|
|
|
34
35
|
## Profile coverage
|
|
35
36
|
|
|
36
|
-
The static profile contains
|
|
37
|
+
The static profile contains 127 messages, 1,462 fields, 200 types, and 4,406
|
|
37
38
|
mapped values. Pass `includeUnmappedMessages: true` to retain fields outside
|
|
38
39
|
that maintained surface by number and exact bytes; applications that need all
|
|
39
40
|
bytes for recognized messages can also opt into `raw_messages`.
|
|
@@ -62,6 +63,27 @@ console.log({
|
|
|
62
63
|
})
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
### Concatenated FIT files
|
|
67
|
+
|
|
68
|
+
Some exports append several complete FIT files into one input. Use
|
|
69
|
+
`parseFilesAsync()` to return a `ParsedFit[]` in file order:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const files = await new FitParser({ force: false }).parseFilesAsync(content)
|
|
73
|
+
for (const file of files) {
|
|
74
|
+
console.log(file.sessions?.length ?? 0)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Each result has independent message definitions, developer descriptions,
|
|
79
|
+
timestamps, and activity collections. Results are never merged. A single FIT
|
|
80
|
+
file returns a one-element array. Empty input, incomplete files (including
|
|
81
|
+
missing file CRC bytes), and trailing non-FIT bytes reject the entire call,
|
|
82
|
+
even with `force: true`. Records and definitions extending beyond the declared
|
|
83
|
+
data section are also rejected. CRC validation follows the `force` option for every
|
|
84
|
+
file. Failures reject with an `Error`. The existing `parseAsync()` and callback
|
|
85
|
+
APIs continue to parse the first FIT file only.
|
|
86
|
+
|
|
65
87
|
### Callback API
|
|
66
88
|
|
|
67
89
|
```javascript
|
|
@@ -86,8 +108,9 @@ readFile('./activity.fit', (readError, content) => {
|
|
|
86
108
|
})
|
|
87
109
|
```
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
The callback reports validation errors as strings. `parseAsync()` rejects with
|
|
112
|
+
those values; decoder failures can reject with an `Error`. `parseFilesAsync()`
|
|
113
|
+
consistently rejects with an `Error`.
|
|
91
114
|
|
|
92
115
|
## Parser options
|
|
93
116
|
|
|
@@ -148,8 +171,8 @@ message numbers.
|
|
|
148
171
|
|
|
149
172
|
Set `includeUnmappedMessages: true` to return fields that are absent from the
|
|
150
173
|
maintained profile or belong to an unknown global message in
|
|
151
|
-
`unmapped_messages`.
|
|
152
|
-
are not duplicated:
|
|
174
|
+
`unmapped_messages`. Fields without usable developer names are also retained.
|
|
175
|
+
Normal known fields are not duplicated:
|
|
153
176
|
|
|
154
177
|
```javascript
|
|
155
178
|
const data = await new FitParser({ includeUnmappedMessages: true })
|
|
@@ -169,6 +192,20 @@ Each entry uses the same wire-level field representation as `raw_messages`.
|
|
|
169
192
|
This opt-in ensures a newer FIT field cannot disappear merely because it
|
|
170
193
|
does not yet have a reviewed semantic mapping.
|
|
171
194
|
|
|
195
|
+
For record temperature encoded as one unsigned byte, values from 0 through
|
|
196
|
+
126 can be decoded as Celsius before the selected temperature conversion.
|
|
197
|
+
Values from 127 through 255 remain undecoded. This narrow interoperability
|
|
198
|
+
recovery preserves the original unsigned byte in `unmapped_messages` when
|
|
199
|
+
enabled, including values that also receive a decoded temperature.
|
|
200
|
+
|
|
201
|
+
### Battery samples
|
|
202
|
+
|
|
203
|
+
`batteries` retains every battery message in file order, including its
|
|
204
|
+
timestamp and capacity percentage when present. These samples are also
|
|
205
|
+
available in `messages.battery`. Undocumented battery fields remain available
|
|
206
|
+
through `includeUnmappedMessages`; the parser does not infer their device
|
|
207
|
+
association or calculate battery consumption.
|
|
208
|
+
|
|
172
209
|
### Lossless selected messages
|
|
173
210
|
|
|
174
211
|
Set `includeRawMessages` when a consumer needs native FIT numeric codes rather
|
package/dist/binary.js
CHANGED
|
@@ -305,6 +305,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
305
305
|
case 'enhanced_speed':
|
|
306
306
|
case 'vertical_speed':
|
|
307
307
|
case 'avg_speed':
|
|
308
|
+
case 'avg_grade_adjusted_speed':
|
|
308
309
|
case 'max_speed':
|
|
309
310
|
case 'speed1s':
|
|
310
311
|
case 'ball_speed':
|
|
@@ -465,7 +466,12 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
465
466
|
const fieldSize = blob[fDefIndex + 1];
|
|
466
467
|
const wireType = FIT.types.fit_base_type[baseType];
|
|
467
468
|
const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
|
|
468
|
-
|
|
469
|
+
// Narrow producer interoperability case: only the shared, non-sentinel
|
|
470
|
+
// temperature range is decoded below. All original bytes remain unmapped.
|
|
471
|
+
const unsignedTemperature = mTypeDef.globalMessageNumber === 20
|
|
472
|
+
&& fieldNumber === 13 && fieldSize === 1
|
|
473
|
+
&& profileBaseType === 'sint8' && wireType === 'uint8';
|
|
474
|
+
const profileCompatible = unsignedTemperature || areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
469
475
|
const fDef = {
|
|
470
476
|
type: profileCompatible ? type : wireType,
|
|
471
477
|
rawType: wireType,
|
|
@@ -539,7 +545,11 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
539
545
|
rawData[i] = InvalidFieldData;
|
|
540
546
|
continue;
|
|
541
547
|
}
|
|
542
|
-
|
|
548
|
+
const unsignedTemperature = messageType.globalMessageNumber === 20
|
|
549
|
+
&& fDef.fDefNo === 13 && fDef.size === 1
|
|
550
|
+
&& fDef.type === 'sint8' && fDef.rawType === 'uint8';
|
|
551
|
+
const preserveNative = !isOutputFieldName(fDef.name) || unsignedTemperature;
|
|
552
|
+
if ((rawFields || (unmappedFields && preserveNative))
|
|
543
553
|
&& readDataFromIndex + fDef.size <= dataEnd) {
|
|
544
554
|
const rawField = {
|
|
545
555
|
fieldDefinitionNumber: fDef.fDefNo,
|
|
@@ -547,11 +557,13 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
547
557
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + fDef.size)),
|
|
548
558
|
};
|
|
549
559
|
rawFields === null || rawFields === void 0 ? void 0 : rawFields.push(rawField);
|
|
550
|
-
if (
|
|
560
|
+
if (preserveNative) {
|
|
551
561
|
unmappedFields === null || unmappedFields === void 0 ? void 0 : unmappedFields.push(rawField);
|
|
552
562
|
}
|
|
553
563
|
}
|
|
554
|
-
const data =
|
|
564
|
+
const data = unsignedTemperature && blob[readDataFromIndex] >= 127
|
|
565
|
+
? InvalidFieldData
|
|
566
|
+
: readData(blob, dataView, fDef, readDataFromIndex);
|
|
555
567
|
if (data !== InvalidFieldData
|
|
556
568
|
&& !isInvalidValue(data, fDef.type)
|
|
557
569
|
&& !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
@@ -573,20 +585,21 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
573
585
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
574
586
|
const developerFieldDef = developerFieldDefs[i];
|
|
575
587
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
588
|
+
// Resolve before deciding whether raw retention is needed. A resolved
|
|
589
|
+
// description can still have no usable output name, including after cache use.
|
|
590
|
+
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
591
|
+
const preserveDeveloper = !fDef || !isOutputFieldName(fDef.name);
|
|
592
|
+
if ((rawDeveloperFields || (unmappedDeveloperFields && preserveDeveloper))
|
|
579
593
|
&& readDataFromIndex + developerFieldDef.size <= dataEnd) {
|
|
580
|
-
rawDeveloperField = {
|
|
594
|
+
const rawDeveloperField = {
|
|
581
595
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
582
596
|
fieldDefinitionNumber: developerFieldDef.fieldDefinitionNumber,
|
|
583
597
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + developerFieldDef.size)),
|
|
584
598
|
};
|
|
585
599
|
rawDeveloperFields === null || rawDeveloperFields === void 0 ? void 0 : rawDeveloperFields.push(rawDeveloperField);
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
unmappedDeveloperFields === null || unmappedDeveloperFields === void 0 ? void 0 : unmappedDeveloperFields.push(rawDeveloperField);
|
|
600
|
+
if (preserveDeveloper) {
|
|
601
|
+
unmappedDeveloperFields === null || unmappedDeveloperFields === void 0 ? void 0 : unmappedDeveloperFields.push(rawDeveloperField);
|
|
602
|
+
}
|
|
590
603
|
}
|
|
591
604
|
if (fDef) {
|
|
592
605
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
package/dist/cjs/binary.js
CHANGED
|
@@ -311,6 +311,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
311
311
|
case 'enhanced_speed':
|
|
312
312
|
case 'vertical_speed':
|
|
313
313
|
case 'avg_speed':
|
|
314
|
+
case 'avg_grade_adjusted_speed':
|
|
314
315
|
case 'max_speed':
|
|
315
316
|
case 'speed1s':
|
|
316
317
|
case 'ball_speed':
|
|
@@ -471,7 +472,12 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
471
472
|
const fieldSize = blob[fDefIndex + 1];
|
|
472
473
|
const wireType = fit_js_1.FIT.types.fit_base_type[baseType];
|
|
473
474
|
const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
|
|
474
|
-
|
|
475
|
+
// Narrow producer interoperability case: only the shared, non-sentinel
|
|
476
|
+
// temperature range is decoded below. All original bytes remain unmapped.
|
|
477
|
+
const unsignedTemperature = mTypeDef.globalMessageNumber === 20
|
|
478
|
+
&& fieldNumber === 13 && fieldSize === 1
|
|
479
|
+
&& profileBaseType === 'sint8' && wireType === 'uint8';
|
|
480
|
+
const profileCompatible = unsignedTemperature || areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
475
481
|
const fDef = {
|
|
476
482
|
type: profileCompatible ? type : wireType,
|
|
477
483
|
rawType: wireType,
|
|
@@ -545,7 +551,11 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
545
551
|
rawData[i] = InvalidFieldData;
|
|
546
552
|
continue;
|
|
547
553
|
}
|
|
548
|
-
|
|
554
|
+
const unsignedTemperature = messageType.globalMessageNumber === 20
|
|
555
|
+
&& fDef.fDefNo === 13 && fDef.size === 1
|
|
556
|
+
&& fDef.type === 'sint8' && fDef.rawType === 'uint8';
|
|
557
|
+
const preserveNative = !isOutputFieldName(fDef.name) || unsignedTemperature;
|
|
558
|
+
if ((rawFields || (unmappedFields && preserveNative))
|
|
549
559
|
&& readDataFromIndex + fDef.size <= dataEnd) {
|
|
550
560
|
const rawField = {
|
|
551
561
|
fieldDefinitionNumber: fDef.fDefNo,
|
|
@@ -553,11 +563,13 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
553
563
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + fDef.size)),
|
|
554
564
|
};
|
|
555
565
|
rawFields === null || rawFields === void 0 ? void 0 : rawFields.push(rawField);
|
|
556
|
-
if (
|
|
566
|
+
if (preserveNative) {
|
|
557
567
|
unmappedFields === null || unmappedFields === void 0 ? void 0 : unmappedFields.push(rawField);
|
|
558
568
|
}
|
|
559
569
|
}
|
|
560
|
-
const data =
|
|
570
|
+
const data = unsignedTemperature && blob[readDataFromIndex] >= 127
|
|
571
|
+
? InvalidFieldData
|
|
572
|
+
: readData(blob, dataView, fDef, readDataFromIndex);
|
|
561
573
|
if (data !== InvalidFieldData
|
|
562
574
|
&& !isInvalidValue(data, fDef.type)
|
|
563
575
|
&& !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
@@ -579,20 +591,21 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
579
591
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
580
592
|
const developerFieldDef = developerFieldDefs[i];
|
|
581
593
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
594
|
+
// Resolve before deciding whether raw retention is needed. A resolved
|
|
595
|
+
// description can still have no usable output name, including after cache use.
|
|
596
|
+
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
597
|
+
const preserveDeveloper = !fDef || !isOutputFieldName(fDef.name);
|
|
598
|
+
if ((rawDeveloperFields || (unmappedDeveloperFields && preserveDeveloper))
|
|
585
599
|
&& readDataFromIndex + developerFieldDef.size <= dataEnd) {
|
|
586
|
-
rawDeveloperField = {
|
|
600
|
+
const rawDeveloperField = {
|
|
587
601
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
588
602
|
fieldDefinitionNumber: developerFieldDef.fieldDefinitionNumber,
|
|
589
603
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + developerFieldDef.size)),
|
|
590
604
|
};
|
|
591
605
|
rawDeveloperFields === null || rawDeveloperFields === void 0 ? void 0 : rawDeveloperFields.push(rawDeveloperField);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
unmappedDeveloperFields === null || unmappedDeveloperFields === void 0 ? void 0 : unmappedDeveloperFields.push(rawDeveloperField);
|
|
606
|
+
if (preserveDeveloper) {
|
|
607
|
+
unmappedDeveloperFields === null || unmappedDeveloperFields === void 0 ? void 0 : unmappedDeveloperFields.push(rawDeveloperField);
|
|
608
|
+
}
|
|
596
609
|
}
|
|
597
610
|
if (fDef) {
|
|
598
611
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
package/dist/cjs/fit-parser.d.ts
CHANGED
|
@@ -28,5 +28,13 @@ export default class FitParser {
|
|
|
28
28
|
private readonly options;
|
|
29
29
|
constructor(options?: FitParserOptions);
|
|
30
30
|
parseAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit>;
|
|
31
|
+
/**
|
|
32
|
+
* Parses every complete FIT file in an input, preserving file order and
|
|
33
|
+
* independent decoder state. Rejects incomplete files and trailing bytes;
|
|
34
|
+
* each file uses this parser's options, including its CRC policy.
|
|
35
|
+
* Rejects with an Error when any file cannot be parsed.
|
|
36
|
+
*/
|
|
37
|
+
parseFilesAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit[]>;
|
|
31
38
|
parse(content: ArrayBuffer | Buffer<ArrayBuffer>, callback: FitParserCallback): void;
|
|
39
|
+
private parseFile;
|
|
32
40
|
}
|
package/dist/cjs/fit-parser.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
exports.readFitUnsignedField = exports.readFitStringField = exports.readFitMessages = exports.getFitBaseTypeId = exports.fitTimestampToUnixMilliseconds = exports.FitMessageReaderError = exports.getFitSubSportName = exports.getFitSubSportId = exports.getFitSportName = exports.getFitSportId = exports.getFitManufacturerName = exports.getFitGarminProductName = exports.getFitGarminProductDisplayName = exports.getFitCoursePointId = exports.FitEncoder = exports.FitBaseType = void 0;
|
|
4
13
|
const binary_js_1 = require("./binary.js");
|
|
@@ -51,7 +60,71 @@ class FitParser {
|
|
|
51
60
|
});
|
|
52
61
|
});
|
|
53
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Parses every complete FIT file in an input, preserving file order and
|
|
65
|
+
* independent decoder state. Rejects incomplete files and trailing bytes;
|
|
66
|
+
* each file uses this parser's options, including its CRC policy.
|
|
67
|
+
* Rejects with an Error when any file cannot be parsed.
|
|
68
|
+
*/
|
|
69
|
+
parseFilesAsync(content) {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const blob = content instanceof ArrayBuffer
|
|
72
|
+
? new Uint8Array(content)
|
|
73
|
+
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
74
|
+
const results = [];
|
|
75
|
+
let start = 0;
|
|
76
|
+
do {
|
|
77
|
+
const remaining = blob.length - start;
|
|
78
|
+
if (remaining < 12) {
|
|
79
|
+
throw new Error('File to small to be a FIT file');
|
|
80
|
+
}
|
|
81
|
+
const headerLength = blob[start];
|
|
82
|
+
if (headerLength !== 12 && headerLength !== 14) {
|
|
83
|
+
throw new Error('Incorrect header size');
|
|
84
|
+
}
|
|
85
|
+
if (remaining < headerLength) {
|
|
86
|
+
throw new Error('File to small to be a FIT file');
|
|
87
|
+
}
|
|
88
|
+
if (blob[start + 8] !== 0x2E
|
|
89
|
+
|| blob[start + 9] !== 0x46
|
|
90
|
+
|| blob[start + 10] !== 0x49
|
|
91
|
+
|| blob[start + 11] !== 0x54) {
|
|
92
|
+
throw new Error('Missing \'.FIT\' in header');
|
|
93
|
+
}
|
|
94
|
+
const dataLength = new DataView(blob.buffer, blob.byteOffset + start, remaining)
|
|
95
|
+
.getUint32(4, true);
|
|
96
|
+
const crcStart = start + headerLength + dataLength;
|
|
97
|
+
if (crcStart > blob.length) {
|
|
98
|
+
throw new Error('File data exceeds input length');
|
|
99
|
+
}
|
|
100
|
+
if (crcStart + 2 > blob.length) {
|
|
101
|
+
throw new Error('File CRC missing');
|
|
102
|
+
}
|
|
103
|
+
const end = crcStart + 2;
|
|
104
|
+
try {
|
|
105
|
+
results.push(yield new Promise((resolve, reject) => {
|
|
106
|
+
this.parseFile(blob.slice(start, end).buffer, (error, data) => {
|
|
107
|
+
if (error) {
|
|
108
|
+
reject(error);
|
|
109
|
+
}
|
|
110
|
+
else if (data) {
|
|
111
|
+
resolve(data);
|
|
112
|
+
}
|
|
113
|
+
}, true);
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
118
|
+
}
|
|
119
|
+
start = end;
|
|
120
|
+
} while (start < blob.length);
|
|
121
|
+
return results;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
54
124
|
parse(content, callback) {
|
|
125
|
+
this.parseFile(content, callback);
|
|
126
|
+
}
|
|
127
|
+
parseFile(content, callback, validateRecordBounds = false) {
|
|
55
128
|
var _a, _b, _c;
|
|
56
129
|
const blob = content instanceof ArrayBuffer
|
|
57
130
|
? new Uint8Array(content)
|
|
@@ -111,6 +184,7 @@ class FitParser {
|
|
|
111
184
|
protocolVersion,
|
|
112
185
|
};
|
|
113
186
|
const messages = {};
|
|
187
|
+
const batteries = [];
|
|
114
188
|
let sessions = [];
|
|
115
189
|
let laps = [];
|
|
116
190
|
const records = [];
|
|
@@ -160,6 +234,12 @@ class FitParser {
|
|
|
160
234
|
let pausedTime = 0;
|
|
161
235
|
while (loopIndex < crcStart) {
|
|
162
236
|
const { globalMessageNumber, littleEndian, message, messageType, nextIndex, compressedTimestamp, rawFields: recordRawFields, rawDeveloperFields: recordRawDeveloperFields, unmappedFields: recordUnmappedFields, unmappedDeveloperFields: recordUnmappedDeveloperFields, } = (0, binary_js_1.readRecord)(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
|
|
237
|
+
// Complete-file parsing also validates bounds without requiring raw
|
|
238
|
+
// retention. The single-file API keeps its existing recovery behavior.
|
|
239
|
+
if (validateRecordBounds
|
|
240
|
+
&& (!Number.isInteger(nextIndex) || nextIndex <= loopIndex || nextIndex > crcStart)) {
|
|
241
|
+
throw new Error('Invalid FIT record bounds');
|
|
242
|
+
}
|
|
163
243
|
loopIndex = nextIndex;
|
|
164
244
|
if (globalMessageNumber !== undefined) {
|
|
165
245
|
const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
|
|
@@ -210,6 +290,9 @@ class FitParser {
|
|
|
210
290
|
((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
|
|
211
291
|
}
|
|
212
292
|
switch (messageType) {
|
|
293
|
+
case 'battery':
|
|
294
|
+
batteries.push(message);
|
|
295
|
+
break;
|
|
213
296
|
case 'lap':
|
|
214
297
|
laps.push(message);
|
|
215
298
|
break;
|
|
@@ -354,6 +437,7 @@ class FitParser {
|
|
|
354
437
|
fitObj.time_in_zone = time_in_zone;
|
|
355
438
|
fitObj.activity_metrics = activity_metrics;
|
|
356
439
|
fitObj.user_metrics = user_metrics;
|
|
440
|
+
fitObj.batteries = batteries;
|
|
357
441
|
fitObj.messages = messages;
|
|
358
442
|
if (rawDeveloperFields) {
|
|
359
443
|
fitObj.raw_developer_fields = rawDeveloperFields;
|
package/dist/cjs/fit_types.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export type MaxMetCategory = 'generic' | 'cycling' | number;
|
|
|
156
156
|
export type MaxMetHeartRateSource = 'whr' | 'hrm' | number;
|
|
157
157
|
export type MaxMetSpeedSource = 'onboard_gps' | 'connected_gps' | 'cadence' | number;
|
|
158
158
|
export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type' | number;
|
|
159
|
-
export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'training_settings' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
|
|
159
|
+
export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'training_settings' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'battery' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
|
|
160
160
|
export interface MessageIndex {
|
|
161
161
|
value: number;
|
|
162
162
|
reserved: boolean;
|
|
@@ -498,6 +498,8 @@ export interface ParsedSession {
|
|
|
498
498
|
best_lap_index?: number;
|
|
499
499
|
min_altitude?: number;
|
|
500
500
|
active_time?: number;
|
|
501
|
+
avg_strokes_per_length?: number;
|
|
502
|
+
avg_swolf?: number;
|
|
501
503
|
player_score?: number;
|
|
502
504
|
opponent_score?: number;
|
|
503
505
|
opponent_name?: string;
|
|
@@ -522,6 +524,9 @@ export interface ParsedSession {
|
|
|
522
524
|
avg_left_pedal_smoothness?: number;
|
|
523
525
|
avg_right_pedal_smoothness?: number;
|
|
524
526
|
avg_combined_pedal_smoothness?: number;
|
|
527
|
+
num_segment_laps?: number;
|
|
528
|
+
front_gear_shift_count?: number;
|
|
529
|
+
rear_gear_shift_count?: number;
|
|
525
530
|
sport_profile_name?: string;
|
|
526
531
|
sport_index?: number;
|
|
527
532
|
time_standing?: number;
|
|
@@ -588,6 +593,7 @@ export interface ParsedSession {
|
|
|
588
593
|
avg_core_temperature?: number;
|
|
589
594
|
min_core_temperature?: number;
|
|
590
595
|
max_core_temperature?: number;
|
|
596
|
+
avg_grade_adjusted_speed?: number;
|
|
591
597
|
timestamp?: Date;
|
|
592
598
|
message_index?: MessageIndex;
|
|
593
599
|
laps?: ParsedLap[];
|
|
@@ -620,6 +626,10 @@ export interface ParsedLap {
|
|
|
620
626
|
lap_trigger?: LapTrigger;
|
|
621
627
|
sport?: Sport;
|
|
622
628
|
event_group?: number;
|
|
629
|
+
nec_lat?: number;
|
|
630
|
+
nec_long?: number;
|
|
631
|
+
swc_lat?: number;
|
|
632
|
+
swc_long?: number;
|
|
623
633
|
num_lengths?: number;
|
|
624
634
|
normalized_power?: number;
|
|
625
635
|
left_right_balance?: LeftRightBalance100;
|
|
@@ -653,6 +663,7 @@ export interface ParsedLap {
|
|
|
653
663
|
min_heart_rate?: number;
|
|
654
664
|
active_time?: number;
|
|
655
665
|
wkt_step_index?: MessageIndex;
|
|
666
|
+
avg_swolf?: number;
|
|
656
667
|
opponent_score?: number;
|
|
657
668
|
stroke_count?: (number | null)[];
|
|
658
669
|
zone_count?: (number | null)[];
|
|
@@ -669,11 +680,14 @@ export interface ParsedLap {
|
|
|
669
680
|
avg_saturated_hemoglobin_percent?: (number | null)[];
|
|
670
681
|
min_saturated_hemoglobin_percent?: (number | null)[];
|
|
671
682
|
max_saturated_hemoglobin_percent?: (number | null)[];
|
|
683
|
+
avg_strokes_per_length?: number;
|
|
672
684
|
avg_left_torque_effectiveness?: number;
|
|
673
685
|
avg_right_torque_effectiveness?: number;
|
|
674
686
|
avg_left_pedal_smoothness?: number;
|
|
675
687
|
avg_right_pedal_smoothness?: number;
|
|
676
688
|
avg_combined_pedal_smoothness?: number;
|
|
689
|
+
front_gear_shift_count?: number;
|
|
690
|
+
rear_gear_shift_count?: number;
|
|
677
691
|
time_standing?: number;
|
|
678
692
|
stand_count?: number;
|
|
679
693
|
avg_left_pco?: number;
|
|
@@ -710,11 +724,13 @@ export interface ParsedLap {
|
|
|
710
724
|
jump_count?: number;
|
|
711
725
|
avg_grit?: number;
|
|
712
726
|
avg_flow?: number;
|
|
727
|
+
metabolic_calories?: number;
|
|
713
728
|
total_fractional_ascent?: number;
|
|
714
729
|
total_fractional_descent?: number;
|
|
715
730
|
avg_core_temperature?: number;
|
|
716
731
|
min_core_temperature?: number;
|
|
717
732
|
max_core_temperature?: number;
|
|
733
|
+
avg_grade_adjusted_speed?: number;
|
|
718
734
|
timestamp?: Date;
|
|
719
735
|
message_index?: MessageIndex;
|
|
720
736
|
records?: ParsedRecord[];
|
|
@@ -1119,6 +1135,10 @@ export interface ParsedMonitoringInfo {
|
|
|
1119
1135
|
resting_metabolic_rate?: number;
|
|
1120
1136
|
timestamp?: Date;
|
|
1121
1137
|
}
|
|
1138
|
+
export interface ParsedBattery {
|
|
1139
|
+
capacity?: number;
|
|
1140
|
+
timestamp?: Date;
|
|
1141
|
+
}
|
|
1122
1142
|
export interface ParsedPad {
|
|
1123
1143
|
}
|
|
1124
1144
|
export interface ParsedSlaveDevice {
|
|
@@ -2007,6 +2027,7 @@ export interface ParsedMessages {
|
|
|
2007
2027
|
ant_channel_id?: ParsedAntChannelId[];
|
|
2008
2028
|
length?: ParsedLength[];
|
|
2009
2029
|
monitoring_info?: ParsedMonitoringInfo[];
|
|
2030
|
+
battery?: ParsedBattery[];
|
|
2010
2031
|
pad?: ParsedPad[];
|
|
2011
2032
|
slave_device?: ParsedSlaveDevice[];
|
|
2012
2033
|
connectivity?: ParsedConnectivity[];
|
|
@@ -2134,6 +2155,7 @@ export interface ParsedFit {
|
|
|
2134
2155
|
time_in_zone?: ParsedTimeInZone[];
|
|
2135
2156
|
activity_metrics?: ParsedActivityMetrics[];
|
|
2136
2157
|
user_metrics?: ParsedUserMetrics[];
|
|
2158
|
+
batteries?: ParsedBattery[];
|
|
2137
2159
|
capabilities?: ParsedCapabilities;
|
|
2138
2160
|
hrm_profile?: ParsedHrmProfile;
|
|
2139
2161
|
sdm_profile?: ParsedSdmProfile;
|
package/dist/cjs/profile.js
CHANGED
|
@@ -1348,6 +1348,60 @@ exports.PROFILE_MESSAGES = {
|
|
|
1348
1348
|
name: 'goal',
|
|
1349
1349
|
},
|
|
1350
1350
|
18: {
|
|
1351
|
+
79: {
|
|
1352
|
+
field: 'avg_strokes_per_length',
|
|
1353
|
+
type: 'uint16',
|
|
1354
|
+
baseType: 'uint16',
|
|
1355
|
+
array: false,
|
|
1356
|
+
scale: 10,
|
|
1357
|
+
offset: 0,
|
|
1358
|
+
units: 'strokes/length',
|
|
1359
|
+
},
|
|
1360
|
+
80: {
|
|
1361
|
+
field: 'avg_swolf',
|
|
1362
|
+
type: 'uint16',
|
|
1363
|
+
baseType: 'uint16',
|
|
1364
|
+
array: false,
|
|
1365
|
+
scale: 1,
|
|
1366
|
+
offset: 0,
|
|
1367
|
+
units: '',
|
|
1368
|
+
},
|
|
1369
|
+
106: {
|
|
1370
|
+
field: 'num_segment_laps',
|
|
1371
|
+
type: 'uint16',
|
|
1372
|
+
baseType: 'uint16',
|
|
1373
|
+
array: false,
|
|
1374
|
+
scale: 1,
|
|
1375
|
+
offset: 0,
|
|
1376
|
+
units: '',
|
|
1377
|
+
},
|
|
1378
|
+
107: {
|
|
1379
|
+
field: 'front_gear_shift_count',
|
|
1380
|
+
type: 'uint16',
|
|
1381
|
+
baseType: 'uint16',
|
|
1382
|
+
array: false,
|
|
1383
|
+
scale: 1,
|
|
1384
|
+
offset: 0,
|
|
1385
|
+
units: '',
|
|
1386
|
+
},
|
|
1387
|
+
108: {
|
|
1388
|
+
field: 'rear_gear_shift_count',
|
|
1389
|
+
type: 'uint16',
|
|
1390
|
+
baseType: 'uint16',
|
|
1391
|
+
array: false,
|
|
1392
|
+
scale: 1,
|
|
1393
|
+
offset: 0,
|
|
1394
|
+
units: '',
|
|
1395
|
+
},
|
|
1396
|
+
211: {
|
|
1397
|
+
field: 'avg_grade_adjusted_speed',
|
|
1398
|
+
type: 'uint32',
|
|
1399
|
+
baseType: 'uint32',
|
|
1400
|
+
array: false,
|
|
1401
|
+
scale: 1000,
|
|
1402
|
+
offset: 0,
|
|
1403
|
+
units: 'm/s',
|
|
1404
|
+
},
|
|
1351
1405
|
0: {
|
|
1352
1406
|
field: 'event',
|
|
1353
1407
|
type: 'event',
|
|
@@ -2808,6 +2862,96 @@ exports.PROFILE_MESSAGES = {
|
|
|
2808
2862
|
name: 'session',
|
|
2809
2863
|
},
|
|
2810
2864
|
19: {
|
|
2865
|
+
27: {
|
|
2866
|
+
field: 'nec_lat',
|
|
2867
|
+
type: 'sint32',
|
|
2868
|
+
baseType: 'sint32',
|
|
2869
|
+
array: false,
|
|
2870
|
+
scale: 1,
|
|
2871
|
+
offset: 0,
|
|
2872
|
+
units: 'semicircles',
|
|
2873
|
+
},
|
|
2874
|
+
28: {
|
|
2875
|
+
field: 'nec_long',
|
|
2876
|
+
type: 'sint32',
|
|
2877
|
+
baseType: 'sint32',
|
|
2878
|
+
array: false,
|
|
2879
|
+
scale: 1,
|
|
2880
|
+
offset: 0,
|
|
2881
|
+
units: 'semicircles',
|
|
2882
|
+
},
|
|
2883
|
+
29: {
|
|
2884
|
+
field: 'swc_lat',
|
|
2885
|
+
type: 'sint32',
|
|
2886
|
+
baseType: 'sint32',
|
|
2887
|
+
array: false,
|
|
2888
|
+
scale: 1,
|
|
2889
|
+
offset: 0,
|
|
2890
|
+
units: 'semicircles',
|
|
2891
|
+
},
|
|
2892
|
+
30: {
|
|
2893
|
+
field: 'swc_long',
|
|
2894
|
+
type: 'sint32',
|
|
2895
|
+
baseType: 'sint32',
|
|
2896
|
+
array: false,
|
|
2897
|
+
scale: 1,
|
|
2898
|
+
offset: 0,
|
|
2899
|
+
units: 'semicircles',
|
|
2900
|
+
},
|
|
2901
|
+
73: {
|
|
2902
|
+
field: 'avg_swolf',
|
|
2903
|
+
type: 'uint16',
|
|
2904
|
+
baseType: 'uint16',
|
|
2905
|
+
array: false,
|
|
2906
|
+
scale: 1,
|
|
2907
|
+
offset: 0,
|
|
2908
|
+
units: '',
|
|
2909
|
+
},
|
|
2910
|
+
90: {
|
|
2911
|
+
field: 'avg_strokes_per_length',
|
|
2912
|
+
type: 'uint16',
|
|
2913
|
+
baseType: 'uint16',
|
|
2914
|
+
array: false,
|
|
2915
|
+
scale: 10,
|
|
2916
|
+
offset: 0,
|
|
2917
|
+
units: 'strokes / length',
|
|
2918
|
+
},
|
|
2919
|
+
96: {
|
|
2920
|
+
field: 'front_gear_shift_count',
|
|
2921
|
+
type: 'uint16',
|
|
2922
|
+
baseType: 'uint16',
|
|
2923
|
+
array: false,
|
|
2924
|
+
scale: 1,
|
|
2925
|
+
offset: 0,
|
|
2926
|
+
units: '',
|
|
2927
|
+
},
|
|
2928
|
+
97: {
|
|
2929
|
+
field: 'rear_gear_shift_count',
|
|
2930
|
+
type: 'uint16',
|
|
2931
|
+
baseType: 'uint16',
|
|
2932
|
+
array: false,
|
|
2933
|
+
scale: 1,
|
|
2934
|
+
offset: 0,
|
|
2935
|
+
units: '',
|
|
2936
|
+
},
|
|
2937
|
+
155: {
|
|
2938
|
+
field: 'metabolic_calories',
|
|
2939
|
+
type: 'uint16',
|
|
2940
|
+
baseType: 'uint16',
|
|
2941
|
+
array: false,
|
|
2942
|
+
scale: 1,
|
|
2943
|
+
offset: 0,
|
|
2944
|
+
units: 'kcal',
|
|
2945
|
+
},
|
|
2946
|
+
161: {
|
|
2947
|
+
field: 'avg_grade_adjusted_speed',
|
|
2948
|
+
type: 'uint32',
|
|
2949
|
+
baseType: 'uint32',
|
|
2950
|
+
array: false,
|
|
2951
|
+
scale: 1000,
|
|
2952
|
+
offset: 0,
|
|
2953
|
+
units: 'm/s',
|
|
2954
|
+
},
|
|
2811
2955
|
0: {
|
|
2812
2956
|
field: 'event',
|
|
2813
2957
|
type: 'event',
|
|
@@ -6986,6 +7130,27 @@ exports.PROFILE_MESSAGES = {
|
|
|
6986
7130
|
},
|
|
6987
7131
|
name: 'monitoring_info',
|
|
6988
7132
|
},
|
|
7133
|
+
104: {
|
|
7134
|
+
2: {
|
|
7135
|
+
field: 'capacity',
|
|
7136
|
+
type: 'uint8',
|
|
7137
|
+
baseType: 'uint8',
|
|
7138
|
+
array: false,
|
|
7139
|
+
scale: 1,
|
|
7140
|
+
offset: 0,
|
|
7141
|
+
units: '%',
|
|
7142
|
+
},
|
|
7143
|
+
253: {
|
|
7144
|
+
field: 'timestamp',
|
|
7145
|
+
type: 'date_time',
|
|
7146
|
+
baseType: 'uint32',
|
|
7147
|
+
array: false,
|
|
7148
|
+
scale: 1,
|
|
7149
|
+
offset: 0,
|
|
7150
|
+
units: '',
|
|
7151
|
+
},
|
|
7152
|
+
name: 'battery',
|
|
7153
|
+
},
|
|
6989
7154
|
105: {
|
|
6990
7155
|
name: 'pad',
|
|
6991
7156
|
},
|
|
@@ -15266,6 +15431,7 @@ exports.PROFILE_TYPES = {
|
|
|
15266
15431
|
82: 'ant_channel_id',
|
|
15267
15432
|
101: 'length',
|
|
15268
15433
|
103: 'monitoring_info',
|
|
15434
|
+
104: 'battery',
|
|
15269
15435
|
105: 'pad',
|
|
15270
15436
|
106: 'slave_device',
|
|
15271
15437
|
127: 'connectivity',
|
|
@@ -261,6 +261,7 @@ function generateFitType() {
|
|
|
261
261
|
time_in_zone: 'parsed_time_in_zone',
|
|
262
262
|
activity_metrics: 'parsed_activity_metrics',
|
|
263
263
|
user_metrics: 'parsed_user_metrics',
|
|
264
|
+
batteries: 'parsed_battery',
|
|
264
265
|
};
|
|
265
266
|
const referenceProperties = {
|
|
266
267
|
file_creator: 'parsed_file_creator',
|
package/dist/fit-parser.d.ts
CHANGED
|
@@ -28,5 +28,13 @@ export default class FitParser {
|
|
|
28
28
|
private readonly options;
|
|
29
29
|
constructor(options?: FitParserOptions);
|
|
30
30
|
parseAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit>;
|
|
31
|
+
/**
|
|
32
|
+
* Parses every complete FIT file in an input, preserving file order and
|
|
33
|
+
* independent decoder state. Rejects incomplete files and trailing bytes;
|
|
34
|
+
* each file uses this parser's options, including its CRC policy.
|
|
35
|
+
* Rejects with an Error when any file cannot be parsed.
|
|
36
|
+
*/
|
|
37
|
+
parseFilesAsync(content: ArrayBuffer | Buffer<ArrayBuffer>): Promise<ParsedFit[]>;
|
|
31
38
|
parse(content: ArrayBuffer | Buffer<ArrayBuffer>, callback: FitParserCallback): void;
|
|
39
|
+
private parseFile;
|
|
32
40
|
}
|
package/dist/fit-parser.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { calculateCRC, readRecord } from './binary.js';
|
|
2
11
|
import { mapDataIntoLap, mapDataIntoSession } from './helper.js';
|
|
3
12
|
export { FitBaseType, FitEncoder } from './fit-encoder.js';
|
|
@@ -32,7 +41,71 @@ export default class FitParser {
|
|
|
32
41
|
});
|
|
33
42
|
});
|
|
34
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Parses every complete FIT file in an input, preserving file order and
|
|
46
|
+
* independent decoder state. Rejects incomplete files and trailing bytes;
|
|
47
|
+
* each file uses this parser's options, including its CRC policy.
|
|
48
|
+
* Rejects with an Error when any file cannot be parsed.
|
|
49
|
+
*/
|
|
50
|
+
parseFilesAsync(content) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const blob = content instanceof ArrayBuffer
|
|
53
|
+
? new Uint8Array(content)
|
|
54
|
+
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
55
|
+
const results = [];
|
|
56
|
+
let start = 0;
|
|
57
|
+
do {
|
|
58
|
+
const remaining = blob.length - start;
|
|
59
|
+
if (remaining < 12) {
|
|
60
|
+
throw new Error('File to small to be a FIT file');
|
|
61
|
+
}
|
|
62
|
+
const headerLength = blob[start];
|
|
63
|
+
if (headerLength !== 12 && headerLength !== 14) {
|
|
64
|
+
throw new Error('Incorrect header size');
|
|
65
|
+
}
|
|
66
|
+
if (remaining < headerLength) {
|
|
67
|
+
throw new Error('File to small to be a FIT file');
|
|
68
|
+
}
|
|
69
|
+
if (blob[start + 8] !== 0x2E
|
|
70
|
+
|| blob[start + 9] !== 0x46
|
|
71
|
+
|| blob[start + 10] !== 0x49
|
|
72
|
+
|| blob[start + 11] !== 0x54) {
|
|
73
|
+
throw new Error('Missing \'.FIT\' in header');
|
|
74
|
+
}
|
|
75
|
+
const dataLength = new DataView(blob.buffer, blob.byteOffset + start, remaining)
|
|
76
|
+
.getUint32(4, true);
|
|
77
|
+
const crcStart = start + headerLength + dataLength;
|
|
78
|
+
if (crcStart > blob.length) {
|
|
79
|
+
throw new Error('File data exceeds input length');
|
|
80
|
+
}
|
|
81
|
+
if (crcStart + 2 > blob.length) {
|
|
82
|
+
throw new Error('File CRC missing');
|
|
83
|
+
}
|
|
84
|
+
const end = crcStart + 2;
|
|
85
|
+
try {
|
|
86
|
+
results.push(yield new Promise((resolve, reject) => {
|
|
87
|
+
this.parseFile(blob.slice(start, end).buffer, (error, data) => {
|
|
88
|
+
if (error) {
|
|
89
|
+
reject(error);
|
|
90
|
+
}
|
|
91
|
+
else if (data) {
|
|
92
|
+
resolve(data);
|
|
93
|
+
}
|
|
94
|
+
}, true);
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
99
|
+
}
|
|
100
|
+
start = end;
|
|
101
|
+
} while (start < blob.length);
|
|
102
|
+
return results;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
35
105
|
parse(content, callback) {
|
|
106
|
+
this.parseFile(content, callback);
|
|
107
|
+
}
|
|
108
|
+
parseFile(content, callback, validateRecordBounds = false) {
|
|
36
109
|
var _a, _b, _c;
|
|
37
110
|
const blob = content instanceof ArrayBuffer
|
|
38
111
|
? new Uint8Array(content)
|
|
@@ -92,6 +165,7 @@ export default class FitParser {
|
|
|
92
165
|
protocolVersion,
|
|
93
166
|
};
|
|
94
167
|
const messages = {};
|
|
168
|
+
const batteries = [];
|
|
95
169
|
let sessions = [];
|
|
96
170
|
let laps = [];
|
|
97
171
|
const records = [];
|
|
@@ -141,6 +215,12 @@ export default class FitParser {
|
|
|
141
215
|
let pausedTime = 0;
|
|
142
216
|
while (loopIndex < crcStart) {
|
|
143
217
|
const { globalMessageNumber, littleEndian, message, messageType, nextIndex, compressedTimestamp, rawFields: recordRawFields, rawDeveloperFields: recordRawDeveloperFields, unmappedFields: recordUnmappedFields, unmappedDeveloperFields: recordUnmappedDeveloperFields, } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
|
|
218
|
+
// Complete-file parsing also validates bounds without requiring raw
|
|
219
|
+
// retention. The single-file API keeps its existing recovery behavior.
|
|
220
|
+
if (validateRecordBounds
|
|
221
|
+
&& (!Number.isInteger(nextIndex) || nextIndex <= loopIndex || nextIndex > crcStart)) {
|
|
222
|
+
throw new Error('Invalid FIT record bounds');
|
|
223
|
+
}
|
|
144
224
|
loopIndex = nextIndex;
|
|
145
225
|
if (globalMessageNumber !== undefined) {
|
|
146
226
|
const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
|
|
@@ -191,6 +271,9 @@ export default class FitParser {
|
|
|
191
271
|
((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
|
|
192
272
|
}
|
|
193
273
|
switch (messageType) {
|
|
274
|
+
case 'battery':
|
|
275
|
+
batteries.push(message);
|
|
276
|
+
break;
|
|
194
277
|
case 'lap':
|
|
195
278
|
laps.push(message);
|
|
196
279
|
break;
|
|
@@ -335,6 +418,7 @@ export default class FitParser {
|
|
|
335
418
|
fitObj.time_in_zone = time_in_zone;
|
|
336
419
|
fitObj.activity_metrics = activity_metrics;
|
|
337
420
|
fitObj.user_metrics = user_metrics;
|
|
421
|
+
fitObj.batteries = batteries;
|
|
338
422
|
fitObj.messages = messages;
|
|
339
423
|
if (rawDeveloperFields) {
|
|
340
424
|
fitObj.raw_developer_fields = rawDeveloperFields;
|
package/dist/fit_types.d.ts
CHANGED
|
@@ -156,7 +156,7 @@ export type MaxMetCategory = 'generic' | 'cycling' | number;
|
|
|
156
156
|
export type MaxMetHeartRateSource = 'whr' | 'hrm' | number;
|
|
157
157
|
export type MaxMetSpeedSource = 'onboard_gps' | 'connected_gps' | 'cadence' | number;
|
|
158
158
|
export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type' | number;
|
|
159
|
-
export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'training_settings' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
|
|
159
|
+
export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'training_settings' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'battery' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
|
|
160
160
|
export interface MessageIndex {
|
|
161
161
|
value: number;
|
|
162
162
|
reserved: boolean;
|
|
@@ -498,6 +498,8 @@ export interface ParsedSession {
|
|
|
498
498
|
best_lap_index?: number;
|
|
499
499
|
min_altitude?: number;
|
|
500
500
|
active_time?: number;
|
|
501
|
+
avg_strokes_per_length?: number;
|
|
502
|
+
avg_swolf?: number;
|
|
501
503
|
player_score?: number;
|
|
502
504
|
opponent_score?: number;
|
|
503
505
|
opponent_name?: string;
|
|
@@ -522,6 +524,9 @@ export interface ParsedSession {
|
|
|
522
524
|
avg_left_pedal_smoothness?: number;
|
|
523
525
|
avg_right_pedal_smoothness?: number;
|
|
524
526
|
avg_combined_pedal_smoothness?: number;
|
|
527
|
+
num_segment_laps?: number;
|
|
528
|
+
front_gear_shift_count?: number;
|
|
529
|
+
rear_gear_shift_count?: number;
|
|
525
530
|
sport_profile_name?: string;
|
|
526
531
|
sport_index?: number;
|
|
527
532
|
time_standing?: number;
|
|
@@ -588,6 +593,7 @@ export interface ParsedSession {
|
|
|
588
593
|
avg_core_temperature?: number;
|
|
589
594
|
min_core_temperature?: number;
|
|
590
595
|
max_core_temperature?: number;
|
|
596
|
+
avg_grade_adjusted_speed?: number;
|
|
591
597
|
timestamp?: Date;
|
|
592
598
|
message_index?: MessageIndex;
|
|
593
599
|
laps?: ParsedLap[];
|
|
@@ -620,6 +626,10 @@ export interface ParsedLap {
|
|
|
620
626
|
lap_trigger?: LapTrigger;
|
|
621
627
|
sport?: Sport;
|
|
622
628
|
event_group?: number;
|
|
629
|
+
nec_lat?: number;
|
|
630
|
+
nec_long?: number;
|
|
631
|
+
swc_lat?: number;
|
|
632
|
+
swc_long?: number;
|
|
623
633
|
num_lengths?: number;
|
|
624
634
|
normalized_power?: number;
|
|
625
635
|
left_right_balance?: LeftRightBalance100;
|
|
@@ -653,6 +663,7 @@ export interface ParsedLap {
|
|
|
653
663
|
min_heart_rate?: number;
|
|
654
664
|
active_time?: number;
|
|
655
665
|
wkt_step_index?: MessageIndex;
|
|
666
|
+
avg_swolf?: number;
|
|
656
667
|
opponent_score?: number;
|
|
657
668
|
stroke_count?: (number | null)[];
|
|
658
669
|
zone_count?: (number | null)[];
|
|
@@ -669,11 +680,14 @@ export interface ParsedLap {
|
|
|
669
680
|
avg_saturated_hemoglobin_percent?: (number | null)[];
|
|
670
681
|
min_saturated_hemoglobin_percent?: (number | null)[];
|
|
671
682
|
max_saturated_hemoglobin_percent?: (number | null)[];
|
|
683
|
+
avg_strokes_per_length?: number;
|
|
672
684
|
avg_left_torque_effectiveness?: number;
|
|
673
685
|
avg_right_torque_effectiveness?: number;
|
|
674
686
|
avg_left_pedal_smoothness?: number;
|
|
675
687
|
avg_right_pedal_smoothness?: number;
|
|
676
688
|
avg_combined_pedal_smoothness?: number;
|
|
689
|
+
front_gear_shift_count?: number;
|
|
690
|
+
rear_gear_shift_count?: number;
|
|
677
691
|
time_standing?: number;
|
|
678
692
|
stand_count?: number;
|
|
679
693
|
avg_left_pco?: number;
|
|
@@ -710,11 +724,13 @@ export interface ParsedLap {
|
|
|
710
724
|
jump_count?: number;
|
|
711
725
|
avg_grit?: number;
|
|
712
726
|
avg_flow?: number;
|
|
727
|
+
metabolic_calories?: number;
|
|
713
728
|
total_fractional_ascent?: number;
|
|
714
729
|
total_fractional_descent?: number;
|
|
715
730
|
avg_core_temperature?: number;
|
|
716
731
|
min_core_temperature?: number;
|
|
717
732
|
max_core_temperature?: number;
|
|
733
|
+
avg_grade_adjusted_speed?: number;
|
|
718
734
|
timestamp?: Date;
|
|
719
735
|
message_index?: MessageIndex;
|
|
720
736
|
records?: ParsedRecord[];
|
|
@@ -1119,6 +1135,10 @@ export interface ParsedMonitoringInfo {
|
|
|
1119
1135
|
resting_metabolic_rate?: number;
|
|
1120
1136
|
timestamp?: Date;
|
|
1121
1137
|
}
|
|
1138
|
+
export interface ParsedBattery {
|
|
1139
|
+
capacity?: number;
|
|
1140
|
+
timestamp?: Date;
|
|
1141
|
+
}
|
|
1122
1142
|
export interface ParsedPad {
|
|
1123
1143
|
}
|
|
1124
1144
|
export interface ParsedSlaveDevice {
|
|
@@ -2007,6 +2027,7 @@ export interface ParsedMessages {
|
|
|
2007
2027
|
ant_channel_id?: ParsedAntChannelId[];
|
|
2008
2028
|
length?: ParsedLength[];
|
|
2009
2029
|
monitoring_info?: ParsedMonitoringInfo[];
|
|
2030
|
+
battery?: ParsedBattery[];
|
|
2010
2031
|
pad?: ParsedPad[];
|
|
2011
2032
|
slave_device?: ParsedSlaveDevice[];
|
|
2012
2033
|
connectivity?: ParsedConnectivity[];
|
|
@@ -2134,6 +2155,7 @@ export interface ParsedFit {
|
|
|
2134
2155
|
time_in_zone?: ParsedTimeInZone[];
|
|
2135
2156
|
activity_metrics?: ParsedActivityMetrics[];
|
|
2136
2157
|
user_metrics?: ParsedUserMetrics[];
|
|
2158
|
+
batteries?: ParsedBattery[];
|
|
2137
2159
|
capabilities?: ParsedCapabilities;
|
|
2138
2160
|
hrm_profile?: ParsedHrmProfile;
|
|
2139
2161
|
sdm_profile?: ParsedSdmProfile;
|
package/dist/profile.js
CHANGED
|
@@ -1345,6 +1345,60 @@ export const PROFILE_MESSAGES = {
|
|
|
1345
1345
|
name: 'goal',
|
|
1346
1346
|
},
|
|
1347
1347
|
18: {
|
|
1348
|
+
79: {
|
|
1349
|
+
field: 'avg_strokes_per_length',
|
|
1350
|
+
type: 'uint16',
|
|
1351
|
+
baseType: 'uint16',
|
|
1352
|
+
array: false,
|
|
1353
|
+
scale: 10,
|
|
1354
|
+
offset: 0,
|
|
1355
|
+
units: 'strokes/length',
|
|
1356
|
+
},
|
|
1357
|
+
80: {
|
|
1358
|
+
field: 'avg_swolf',
|
|
1359
|
+
type: 'uint16',
|
|
1360
|
+
baseType: 'uint16',
|
|
1361
|
+
array: false,
|
|
1362
|
+
scale: 1,
|
|
1363
|
+
offset: 0,
|
|
1364
|
+
units: '',
|
|
1365
|
+
},
|
|
1366
|
+
106: {
|
|
1367
|
+
field: 'num_segment_laps',
|
|
1368
|
+
type: 'uint16',
|
|
1369
|
+
baseType: 'uint16',
|
|
1370
|
+
array: false,
|
|
1371
|
+
scale: 1,
|
|
1372
|
+
offset: 0,
|
|
1373
|
+
units: '',
|
|
1374
|
+
},
|
|
1375
|
+
107: {
|
|
1376
|
+
field: 'front_gear_shift_count',
|
|
1377
|
+
type: 'uint16',
|
|
1378
|
+
baseType: 'uint16',
|
|
1379
|
+
array: false,
|
|
1380
|
+
scale: 1,
|
|
1381
|
+
offset: 0,
|
|
1382
|
+
units: '',
|
|
1383
|
+
},
|
|
1384
|
+
108: {
|
|
1385
|
+
field: 'rear_gear_shift_count',
|
|
1386
|
+
type: 'uint16',
|
|
1387
|
+
baseType: 'uint16',
|
|
1388
|
+
array: false,
|
|
1389
|
+
scale: 1,
|
|
1390
|
+
offset: 0,
|
|
1391
|
+
units: '',
|
|
1392
|
+
},
|
|
1393
|
+
211: {
|
|
1394
|
+
field: 'avg_grade_adjusted_speed',
|
|
1395
|
+
type: 'uint32',
|
|
1396
|
+
baseType: 'uint32',
|
|
1397
|
+
array: false,
|
|
1398
|
+
scale: 1000,
|
|
1399
|
+
offset: 0,
|
|
1400
|
+
units: 'm/s',
|
|
1401
|
+
},
|
|
1348
1402
|
0: {
|
|
1349
1403
|
field: 'event',
|
|
1350
1404
|
type: 'event',
|
|
@@ -2805,6 +2859,96 @@ export const PROFILE_MESSAGES = {
|
|
|
2805
2859
|
name: 'session',
|
|
2806
2860
|
},
|
|
2807
2861
|
19: {
|
|
2862
|
+
27: {
|
|
2863
|
+
field: 'nec_lat',
|
|
2864
|
+
type: 'sint32',
|
|
2865
|
+
baseType: 'sint32',
|
|
2866
|
+
array: false,
|
|
2867
|
+
scale: 1,
|
|
2868
|
+
offset: 0,
|
|
2869
|
+
units: 'semicircles',
|
|
2870
|
+
},
|
|
2871
|
+
28: {
|
|
2872
|
+
field: 'nec_long',
|
|
2873
|
+
type: 'sint32',
|
|
2874
|
+
baseType: 'sint32',
|
|
2875
|
+
array: false,
|
|
2876
|
+
scale: 1,
|
|
2877
|
+
offset: 0,
|
|
2878
|
+
units: 'semicircles',
|
|
2879
|
+
},
|
|
2880
|
+
29: {
|
|
2881
|
+
field: 'swc_lat',
|
|
2882
|
+
type: 'sint32',
|
|
2883
|
+
baseType: 'sint32',
|
|
2884
|
+
array: false,
|
|
2885
|
+
scale: 1,
|
|
2886
|
+
offset: 0,
|
|
2887
|
+
units: 'semicircles',
|
|
2888
|
+
},
|
|
2889
|
+
30: {
|
|
2890
|
+
field: 'swc_long',
|
|
2891
|
+
type: 'sint32',
|
|
2892
|
+
baseType: 'sint32',
|
|
2893
|
+
array: false,
|
|
2894
|
+
scale: 1,
|
|
2895
|
+
offset: 0,
|
|
2896
|
+
units: 'semicircles',
|
|
2897
|
+
},
|
|
2898
|
+
73: {
|
|
2899
|
+
field: 'avg_swolf',
|
|
2900
|
+
type: 'uint16',
|
|
2901
|
+
baseType: 'uint16',
|
|
2902
|
+
array: false,
|
|
2903
|
+
scale: 1,
|
|
2904
|
+
offset: 0,
|
|
2905
|
+
units: '',
|
|
2906
|
+
},
|
|
2907
|
+
90: {
|
|
2908
|
+
field: 'avg_strokes_per_length',
|
|
2909
|
+
type: 'uint16',
|
|
2910
|
+
baseType: 'uint16',
|
|
2911
|
+
array: false,
|
|
2912
|
+
scale: 10,
|
|
2913
|
+
offset: 0,
|
|
2914
|
+
units: 'strokes / length',
|
|
2915
|
+
},
|
|
2916
|
+
96: {
|
|
2917
|
+
field: 'front_gear_shift_count',
|
|
2918
|
+
type: 'uint16',
|
|
2919
|
+
baseType: 'uint16',
|
|
2920
|
+
array: false,
|
|
2921
|
+
scale: 1,
|
|
2922
|
+
offset: 0,
|
|
2923
|
+
units: '',
|
|
2924
|
+
},
|
|
2925
|
+
97: {
|
|
2926
|
+
field: 'rear_gear_shift_count',
|
|
2927
|
+
type: 'uint16',
|
|
2928
|
+
baseType: 'uint16',
|
|
2929
|
+
array: false,
|
|
2930
|
+
scale: 1,
|
|
2931
|
+
offset: 0,
|
|
2932
|
+
units: '',
|
|
2933
|
+
},
|
|
2934
|
+
155: {
|
|
2935
|
+
field: 'metabolic_calories',
|
|
2936
|
+
type: 'uint16',
|
|
2937
|
+
baseType: 'uint16',
|
|
2938
|
+
array: false,
|
|
2939
|
+
scale: 1,
|
|
2940
|
+
offset: 0,
|
|
2941
|
+
units: 'kcal',
|
|
2942
|
+
},
|
|
2943
|
+
161: {
|
|
2944
|
+
field: 'avg_grade_adjusted_speed',
|
|
2945
|
+
type: 'uint32',
|
|
2946
|
+
baseType: 'uint32',
|
|
2947
|
+
array: false,
|
|
2948
|
+
scale: 1000,
|
|
2949
|
+
offset: 0,
|
|
2950
|
+
units: 'm/s',
|
|
2951
|
+
},
|
|
2808
2952
|
0: {
|
|
2809
2953
|
field: 'event',
|
|
2810
2954
|
type: 'event',
|
|
@@ -6983,6 +7127,27 @@ export const PROFILE_MESSAGES = {
|
|
|
6983
7127
|
},
|
|
6984
7128
|
name: 'monitoring_info',
|
|
6985
7129
|
},
|
|
7130
|
+
104: {
|
|
7131
|
+
2: {
|
|
7132
|
+
field: 'capacity',
|
|
7133
|
+
type: 'uint8',
|
|
7134
|
+
baseType: 'uint8',
|
|
7135
|
+
array: false,
|
|
7136
|
+
scale: 1,
|
|
7137
|
+
offset: 0,
|
|
7138
|
+
units: '%',
|
|
7139
|
+
},
|
|
7140
|
+
253: {
|
|
7141
|
+
field: 'timestamp',
|
|
7142
|
+
type: 'date_time',
|
|
7143
|
+
baseType: 'uint32',
|
|
7144
|
+
array: false,
|
|
7145
|
+
scale: 1,
|
|
7146
|
+
offset: 0,
|
|
7147
|
+
units: '',
|
|
7148
|
+
},
|
|
7149
|
+
name: 'battery',
|
|
7150
|
+
},
|
|
6986
7151
|
105: {
|
|
6987
7152
|
name: 'pad',
|
|
6988
7153
|
},
|
|
@@ -15263,6 +15428,7 @@ export const PROFILE_TYPES = {
|
|
|
15263
15428
|
82: 'ant_channel_id',
|
|
15264
15429
|
101: 'length',
|
|
15265
15430
|
103: 'monitoring_info',
|
|
15431
|
+
104: 'battery',
|
|
15266
15432
|
105: 'pad',
|
|
15267
15433
|
106: 'slave_device',
|
|
15268
15434
|
127: 'connectivity',
|
package/dist/type_generator.js
CHANGED
|
@@ -240,6 +240,7 @@ export function generateFitType() {
|
|
|
240
240
|
time_in_zone: 'parsed_time_in_zone',
|
|
241
241
|
activity_metrics: 'parsed_activity_metrics',
|
|
242
242
|
user_metrics: 'parsed_user_metrics',
|
|
243
|
+
batteries: 'parsed_battery',
|
|
243
244
|
};
|
|
244
245
|
const referenceProperties = {
|
|
245
246
|
file_creator: 'parsed_file_creator',
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fit-file-parser",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.2.0",
|
|
5
5
|
"private": false,
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "JavaScript and TypeScript library for parsing and encoding FIT files, with developer-field support, raw message access, and ESM/CommonJS builds.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "jimmykane"
|
|
9
9
|
},
|
|
@@ -24,13 +24,22 @@
|
|
|
24
24
|
"url": "git+ssh://git@github.com/jimmykane/fit-parser.git"
|
|
25
25
|
},
|
|
26
26
|
"bugs": {
|
|
27
|
-
"url": "https://github.com/jimmykane/fit-parser"
|
|
27
|
+
"url": "https://github.com/jimmykane/fit-parser/issues"
|
|
28
28
|
},
|
|
29
29
|
"keywords": [
|
|
30
30
|
"fit",
|
|
31
|
-
"
|
|
31
|
+
"fit-file",
|
|
32
|
+
"fit-parser",
|
|
33
|
+
"fit-file-parsing",
|
|
34
|
+
"fit-encoder",
|
|
35
|
+
"javascript",
|
|
36
|
+
"typescript",
|
|
37
|
+
"nodejs",
|
|
38
|
+
"fitness",
|
|
32
39
|
"garmin",
|
|
33
|
-
"
|
|
40
|
+
"polar",
|
|
41
|
+
"suunto",
|
|
42
|
+
"binary-parser"
|
|
34
43
|
],
|
|
35
44
|
"exports": {
|
|
36
45
|
".": {
|