fit-file-parser 5.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +50 -11
- package/dist/binary.d.ts +8 -1
- package/dist/binary.js +15 -1
- package/dist/cjs/binary.d.ts +8 -1
- package/dist/cjs/binary.js +15 -1
- package/dist/cjs/fit-parser.d.ts +3 -0
- package/dist/cjs/fit-parser.js +27 -4
- package/dist/cjs/fit_types.d.ts +8 -0
- package/dist/cjs/type_generator.js +8 -0
- package/dist/fit-parser.d.ts +3 -0
- package/dist/fit-parser.js +27 -4
- package/dist/fit_types.d.ts +8 -0
- package/dist/type_generator.js +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 5.1.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add opt-in, message-filterable `raw_developer_fields` output with exact
|
|
10
|
+
developer-field bytes, global-message occurrence, developer-data index, and
|
|
11
|
+
field-definition number. Existing decoded output remains unchanged when the
|
|
12
|
+
option is disabled.
|
|
13
|
+
|
|
14
|
+
## 5.0.2
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Clarify the 5.0 migration for standard session field 196: use canonical
|
|
19
|
+
`metabolic_calories` rather than the old duplicate `resting_calories` label.
|
|
20
|
+
- Clarify that the removed guessed `recovery_advisor` label was standard
|
|
21
|
+
session field 140, which the SDK defines as `avg_depth` in meters.
|
|
22
|
+
|
|
5
23
|
## 5.0.1
|
|
6
24
|
|
|
7
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -54,14 +54,18 @@ SDK-backed names. Common migrations include:
|
|
|
54
54
|
| `repeat_dive_time` | `repeat_dive_interval` |
|
|
55
55
|
| `cadence_zone_high_boundary` | `cadence_zone_high_bondary` |
|
|
56
56
|
| `HipSwingExcerciseName` | `HipSwingExerciseName` |
|
|
57
|
+
| `resting_calories` | `metabolic_calories` |
|
|
57
58
|
|
|
58
59
|
The same alphanumeric-token rule applies to enum strings, for example
|
|
59
60
|
`camera_orientation_90` becomes `camera_orientation90`, `po_2_warn` becomes
|
|
60
61
|
`po2_warn`, and `power_3s` becomes `power3s`. The generated TypeScript
|
|
61
62
|
declarations are the exhaustive name and value reference for the pinned SDK.
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
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:
|
|
65
69
|
|
|
66
70
|
- `product_name` is emitted only when it exists in the FIT input. It is no
|
|
67
71
|
longer inferred from `manufacturer` and `product`.
|
|
@@ -131,20 +135,55 @@ callback API receives as its first argument.
|
|
|
131
135
|
|
|
132
136
|
All options are optional.
|
|
133
137
|
|
|
134
|
-
| Option
|
|
135
|
-
|
|
|
136
|
-
| `mode`
|
|
137
|
-
| `force`
|
|
138
|
-
| `speedUnit`
|
|
139
|
-
| `lengthUnit`
|
|
140
|
-
| `temperatureUnit`
|
|
141
|
-
| `pressureUnit`
|
|
142
|
-
| `elapsedRecordField`
|
|
138
|
+
| Option | Values | Default | Behavior |
|
|
139
|
+
| --------------------------- | --------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| `mode` | `list`, `cascade`, `both` | `list` | Controls whether primary activity collections are returned as root lists, nested data, or both. |
|
|
141
|
+
| `force` | `true`, `false` | `true` | Skips header and file CRC validation and enables supported best-effort field recovery. Structural header and data bounds are always validated. |
|
|
142
|
+
| `speedUnit` | `m/s`, `km/h`, `mph` | `m/s` | Converts speed-related fields. |
|
|
143
|
+
| `lengthUnit` | `m`, `km`, `mi` | `m` | Converts distance, altitude, and other length-related fields. |
|
|
144
|
+
| `temperatureUnit` | `celsius`, `°C`, `kelvin`, `fahrenheit` | `celsius` | Converts temperature fields. `°C` remains available as a legacy alias. |
|
|
145
|
+
| `pressureUnit` | `bar`, `cbar`, `psi` | `bar` | Converts pressure and tank-pressure fields. |
|
|
146
|
+
| `elapsedRecordField` | `true`, `false` | `false` | Adds `elapsed_time` and `timer_time`, in seconds, to records. |
|
|
147
|
+
| `includeRawDeveloperFields` | `true`, `false`, message-number array | `false` | Adds a lossless, message-associated view of developer-field bytes at `raw_developer_fields`. |
|
|
143
148
|
|
|
144
149
|
`force: true` does not make arbitrary bytes a valid FIT file. Inputs that are
|
|
145
150
|
too short, have an invalid header size or signature, or declare data beyond the
|
|
146
151
|
available bytes are rejected in both modes.
|
|
147
152
|
|
|
153
|
+
### Lossless developer fields
|
|
154
|
+
|
|
155
|
+
By default, developer fields retain the existing decoded, name-keyed output.
|
|
156
|
+
Set `includeRawDeveloperFields: true` when a consumer also needs exact field
|
|
157
|
+
bytes or must distinguish same-named fields from different developer-data
|
|
158
|
+
indexes:
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
const data = await new FitParser({
|
|
162
|
+
force: false,
|
|
163
|
+
includeRawDeveloperFields: [18],
|
|
164
|
+
}).parseAsync(content)
|
|
165
|
+
|
|
166
|
+
for (const field of data.raw_developer_fields ?? []) {
|
|
167
|
+
console.log({
|
|
168
|
+
globalMessageNumber: field.global_message_number,
|
|
169
|
+
messageIndex: field.message_index,
|
|
170
|
+
developerDataIndex: field.developer_data_index,
|
|
171
|
+
fieldDefinitionNumber: field.field_definition_number,
|
|
172
|
+
rawBytes: field.raw_value,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`message_index` is the zero-based occurrence of that global message number in
|
|
178
|
+
file order. `raw_value` is a defensive plain-number copy of the exact FIT field
|
|
179
|
+
bytes, including interior NUL bytes and invalid sentinels. Join
|
|
180
|
+
`developer_data_index` and `field_definition_number` to the existing
|
|
181
|
+
`developer_data_ids` and `field_descriptions` collections to interpret an
|
|
182
|
+
application and field. The parser deliberately does not infer application- or
|
|
183
|
+
provider-specific semantics. Pass `true` to retain developer fields from every
|
|
184
|
+
global message, or an array such as `[18]` to bound collection to specific FIT
|
|
185
|
+
message numbers.
|
|
186
|
+
|
|
148
187
|
## Output modes
|
|
149
188
|
|
|
150
189
|
The mode controls where sessions, laps, records, and related activity
|
package/dist/binary.d.ts
CHANGED
|
@@ -20,11 +20,18 @@ export interface DecoderState {
|
|
|
20
20
|
lastTimestamp?: number;
|
|
21
21
|
monitoringTimestamp?: number;
|
|
22
22
|
}
|
|
23
|
+
export interface RawDeveloperFieldValue {
|
|
24
|
+
developerDataIndex: number;
|
|
25
|
+
fieldDefinitionNumber: number;
|
|
26
|
+
rawValue: number[];
|
|
27
|
+
}
|
|
23
28
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
24
|
-
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState): {
|
|
29
|
+
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState, dataEnd?: number): {
|
|
25
30
|
messageType: MessageName | 'definition' | '';
|
|
26
31
|
nextIndex: number;
|
|
27
32
|
message?: any;
|
|
33
|
+
globalMessageNumber?: number;
|
|
34
|
+
rawDeveloperFields?: RawDeveloperFieldValue[];
|
|
28
35
|
};
|
|
29
36
|
export declare function getArrayBuffer(buffer: ArrayBuffer | Buffer): ArrayBuffer;
|
|
30
37
|
export declare function calculateCRC(blob: Uint8Array, start: number, end: number): number;
|
package/dist/binary.js
CHANGED
|
@@ -399,7 +399,7 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
399
399
|
developerFieldDef.resolvedFrom = description;
|
|
400
400
|
return resolvedFieldDef;
|
|
401
401
|
}
|
|
402
|
-
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), decoderState = {}) {
|
|
402
|
+
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), decoderState = {}, dataEnd = dataView.byteLength) {
|
|
403
403
|
var _a, _b, _c;
|
|
404
404
|
const recordHeader = blob[startIndex];
|
|
405
405
|
let localMessageType = recordHeader & 15;
|
|
@@ -479,6 +479,10 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
479
479
|
const message = getFitMessage(messageType.globalMessageNumber);
|
|
480
480
|
const developerFieldDefs = (_b = messageType.developerFieldDefs) !== null && _b !== void 0 ? _b : [];
|
|
481
481
|
const totalFieldCount = messageType.fieldDefs.length + developerFieldDefs.length;
|
|
482
|
+
const includeRawDeveloperFields = options.includeRawDeveloperFields === true
|
|
483
|
+
|| (Array.isArray(options.includeRawDeveloperFields)
|
|
484
|
+
&& options.includeRawDeveloperFields.includes(messageType.globalMessageNumber));
|
|
485
|
+
const rawDeveloperFields = includeRawDeveloperFields ? [] : undefined;
|
|
482
486
|
const rawData = (_c = messageType.rawData) !== null && _c !== void 0 ? _c : (messageType.rawData = Array.from({ length: totalFieldCount }, () => InvalidFieldData));
|
|
483
487
|
let validFieldCount = 0;
|
|
484
488
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
@@ -502,6 +506,14 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
502
506
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
503
507
|
const developerFieldDef = developerFieldDefs[i];
|
|
504
508
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
509
|
+
if (rawDeveloperFields
|
|
510
|
+
&& readDataFromIndex + developerFieldDef.size <= dataEnd) {
|
|
511
|
+
rawDeveloperFields.push({
|
|
512
|
+
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
513
|
+
fieldDefinitionNumber: developerFieldDef.fieldDefinitionNumber,
|
|
514
|
+
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + developerFieldDef.size)),
|
|
515
|
+
});
|
|
516
|
+
}
|
|
505
517
|
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
506
518
|
if (fDef) {
|
|
507
519
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
@@ -607,9 +619,11 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
607
619
|
}
|
|
608
620
|
}
|
|
609
621
|
return {
|
|
622
|
+
globalMessageNumber: messageType.globalMessageNumber,
|
|
610
623
|
messageType: message.name,
|
|
611
624
|
nextIndex: startIndex + messageSize + 1,
|
|
612
625
|
message: fields,
|
|
626
|
+
rawDeveloperFields,
|
|
613
627
|
};
|
|
614
628
|
}
|
|
615
629
|
export function getArrayBuffer(buffer) {
|
package/dist/cjs/binary.d.ts
CHANGED
|
@@ -20,11 +20,18 @@ export interface DecoderState {
|
|
|
20
20
|
lastTimestamp?: number;
|
|
21
21
|
monitoringTimestamp?: number;
|
|
22
22
|
}
|
|
23
|
+
export interface RawDeveloperFieldValue {
|
|
24
|
+
developerDataIndex: number;
|
|
25
|
+
fieldDefinitionNumber: number;
|
|
26
|
+
rawValue: number[];
|
|
27
|
+
}
|
|
23
28
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
24
|
-
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState): {
|
|
29
|
+
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState, dataEnd?: number): {
|
|
25
30
|
messageType: MessageName | 'definition' | '';
|
|
26
31
|
nextIndex: number;
|
|
27
32
|
message?: any;
|
|
33
|
+
globalMessageNumber?: number;
|
|
34
|
+
rawDeveloperFields?: RawDeveloperFieldValue[];
|
|
28
35
|
};
|
|
29
36
|
export declare function getArrayBuffer(buffer: ArrayBuffer | Buffer): ArrayBuffer;
|
|
30
37
|
export declare function calculateCRC(blob: Uint8Array, start: number, end: number): number;
|
package/dist/cjs/binary.js
CHANGED
|
@@ -405,7 +405,7 @@ function resolveDeveloperFieldDefinition(developerFieldDef, littleEndian, develo
|
|
|
405
405
|
developerFieldDef.resolvedFrom = description;
|
|
406
406
|
return resolvedFieldDef;
|
|
407
407
|
}
|
|
408
|
-
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), decoderState = {}) {
|
|
408
|
+
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime, dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength), decoderState = {}, dataEnd = dataView.byteLength) {
|
|
409
409
|
var _a, _b, _c;
|
|
410
410
|
const recordHeader = blob[startIndex];
|
|
411
411
|
let localMessageType = recordHeader & 15;
|
|
@@ -485,6 +485,10 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
485
485
|
const message = (0, messages_js_1.getFitMessage)(messageType.globalMessageNumber);
|
|
486
486
|
const developerFieldDefs = (_b = messageType.developerFieldDefs) !== null && _b !== void 0 ? _b : [];
|
|
487
487
|
const totalFieldCount = messageType.fieldDefs.length + developerFieldDefs.length;
|
|
488
|
+
const includeRawDeveloperFields = options.includeRawDeveloperFields === true
|
|
489
|
+
|| (Array.isArray(options.includeRawDeveloperFields)
|
|
490
|
+
&& options.includeRawDeveloperFields.includes(messageType.globalMessageNumber));
|
|
491
|
+
const rawDeveloperFields = includeRawDeveloperFields ? [] : undefined;
|
|
488
492
|
const rawData = (_c = messageType.rawData) !== null && _c !== void 0 ? _c : (messageType.rawData = Array.from({ length: totalFieldCount }, () => InvalidFieldData));
|
|
489
493
|
let validFieldCount = 0;
|
|
490
494
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
@@ -508,6 +512,14 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
508
512
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
509
513
|
const developerFieldDef = developerFieldDefs[i];
|
|
510
514
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
515
|
+
if (rawDeveloperFields
|
|
516
|
+
&& readDataFromIndex + developerFieldDef.size <= dataEnd) {
|
|
517
|
+
rawDeveloperFields.push({
|
|
518
|
+
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
519
|
+
fieldDefinitionNumber: developerFieldDef.fieldDefinitionNumber,
|
|
520
|
+
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + developerFieldDef.size)),
|
|
521
|
+
});
|
|
522
|
+
}
|
|
511
523
|
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
512
524
|
if (fDef) {
|
|
513
525
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
@@ -613,9 +625,11 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
613
625
|
}
|
|
614
626
|
}
|
|
615
627
|
return {
|
|
628
|
+
globalMessageNumber: messageType.globalMessageNumber,
|
|
616
629
|
messageType: message.name,
|
|
617
630
|
nextIndex: startIndex + messageSize + 1,
|
|
618
631
|
message: fields,
|
|
632
|
+
rawDeveloperFields,
|
|
619
633
|
};
|
|
620
634
|
}
|
|
621
635
|
function getArrayBuffer(buffer) {
|
package/dist/cjs/fit-parser.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Buffer } from 'buffer';
|
|
|
2
2
|
import type { ParsedFit } from './fit_types.js';
|
|
3
3
|
export { FitBaseType, FitEncoder } from './fit-encoder.js';
|
|
4
4
|
export type { FitEncoderField, FitEncoderOptions } from './fit-encoder.js';
|
|
5
|
+
export type { ParsedFit, ParsedRawDeveloperField } from './fit_types.js';
|
|
5
6
|
export interface FitParserOptions {
|
|
6
7
|
force?: boolean;
|
|
7
8
|
speedUnit?: string;
|
|
@@ -10,6 +11,8 @@ export interface FitParserOptions {
|
|
|
10
11
|
elapsedRecordField?: boolean;
|
|
11
12
|
pressureUnit?: string;
|
|
12
13
|
mode?: 'list' | 'cascade' | 'both';
|
|
14
|
+
/** Retains exact developer-field bytes for all or selected global message numbers. */
|
|
15
|
+
includeRawDeveloperFields?: boolean | readonly number[];
|
|
13
16
|
}
|
|
14
17
|
type FitParserCallback = (error: string | undefined, data: ParsedFit | undefined) => void;
|
|
15
18
|
export default class FitParser {
|
package/dist/cjs/fit-parser.js
CHANGED
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "FitBaseType", { enumerable: true, get: function
|
|
|
8
8
|
Object.defineProperty(exports, "FitEncoder", { enumerable: true, get: function () { return fit_encoder_js_1.FitEncoder; } });
|
|
9
9
|
class FitParser {
|
|
10
10
|
constructor(options = {}) {
|
|
11
|
+
var _a;
|
|
11
12
|
this.options = {
|
|
12
13
|
force: options.force != null ? options.force : true,
|
|
13
14
|
speedUnit: options.speedUnit || 'm/s',
|
|
@@ -16,6 +17,7 @@ class FitParser {
|
|
|
16
17
|
elapsedRecordField: options.elapsedRecordField || false,
|
|
17
18
|
pressureUnit: options.pressureUnit || 'bar',
|
|
18
19
|
mode: options.mode || 'list',
|
|
20
|
+
includeRawDeveloperFields: (_a = options.includeRawDeveloperFields) !== null && _a !== void 0 ? _a : false,
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
parseAsync(content) {
|
|
@@ -31,7 +33,7 @@ class FitParser {
|
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
35
|
parse(content, callback) {
|
|
34
|
-
var _a, _b;
|
|
36
|
+
var _a, _b, _c;
|
|
35
37
|
const blob = content instanceof ArrayBuffer
|
|
36
38
|
? new Uint8Array(content)
|
|
37
39
|
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
@@ -118,6 +120,11 @@ class FitParser {
|
|
|
118
120
|
const time_in_zone = [];
|
|
119
121
|
const activity_metrics = [];
|
|
120
122
|
const user_metrics = [];
|
|
123
|
+
const rawDeveloperFields = this.options.includeRawDeveloperFields === true
|
|
124
|
+
|| Array.isArray(this.options.includeRawDeveloperFields)
|
|
125
|
+
? []
|
|
126
|
+
: undefined;
|
|
127
|
+
const messageCountsByGlobalNumber = new Map();
|
|
121
128
|
let loopIndex = headerLength;
|
|
122
129
|
const messageTypes = [];
|
|
123
130
|
const developerFields = [];
|
|
@@ -128,12 +135,25 @@ class FitParser {
|
|
|
128
135
|
let lastStopTimestamp;
|
|
129
136
|
let pausedTime = 0;
|
|
130
137
|
while (loopIndex < crcStart) {
|
|
131
|
-
const {
|
|
138
|
+
const { globalMessageNumber, message, messageType, nextIndex, rawDeveloperFields: recordRawDeveloperFields, } = (0, binary_js_1.readRecord)(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
|
|
132
139
|
loopIndex = nextIndex;
|
|
140
|
+
if (globalMessageNumber !== undefined) {
|
|
141
|
+
const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
|
|
142
|
+
messageCountsByGlobalNumber.set(globalMessageNumber, messageIndex + 1);
|
|
143
|
+
recordRawDeveloperFields === null || recordRawDeveloperFields === void 0 ? void 0 : recordRawDeveloperFields.forEach((field) => {
|
|
144
|
+
rawDeveloperFields === null || rawDeveloperFields === void 0 ? void 0 : rawDeveloperFields.push({
|
|
145
|
+
global_message_number: globalMessageNumber,
|
|
146
|
+
message_index: messageIndex,
|
|
147
|
+
developer_data_index: field.developerDataIndex,
|
|
148
|
+
field_definition_number: field.fieldDefinitionNumber,
|
|
149
|
+
raw_value: field.rawValue,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
133
153
|
if (messageType !== ''
|
|
134
154
|
&& messageType !== 'definition'
|
|
135
155
|
&& message !== undefined) {
|
|
136
|
-
((
|
|
156
|
+
((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
|
|
137
157
|
}
|
|
138
158
|
switch (messageType) {
|
|
139
159
|
case 'lap':
|
|
@@ -268,11 +288,14 @@ class FitParser {
|
|
|
268
288
|
fitObj.activity_metrics = activity_metrics;
|
|
269
289
|
fitObj.user_metrics = user_metrics;
|
|
270
290
|
fitObj.messages = messages;
|
|
291
|
+
if (rawDeveloperFields) {
|
|
292
|
+
fitObj.raw_developer_fields = rawDeveloperFields;
|
|
293
|
+
}
|
|
271
294
|
if (isCascadeNeeded) {
|
|
272
295
|
laps = (0, helper_js_1.mapDataIntoLap)(laps, 'records', records);
|
|
273
296
|
laps = (0, helper_js_1.mapDataIntoLap)(laps, 'lengths', lengths);
|
|
274
297
|
sessions = (0, helper_js_1.mapDataIntoSession)(sessions, laps);
|
|
275
|
-
fitObj.activity = Object.assign(Object.assign({}, ((
|
|
298
|
+
fitObj.activity = Object.assign(Object.assign({}, ((_c = fitObj.activity) !== null && _c !== void 0 ? _c : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly
|
|
276
299
|
sessions,
|
|
277
300
|
events,
|
|
278
301
|
hrv,
|
package/dist/cjs/fit_types.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ export type Unit<T extends string> = Record<T, {
|
|
|
9
9
|
multiplier: number;
|
|
10
10
|
offset: number;
|
|
11
11
|
}>;
|
|
12
|
+
export interface ParsedRawDeveloperField {
|
|
13
|
+
global_message_number: number;
|
|
14
|
+
message_index: number;
|
|
15
|
+
developer_data_index: number;
|
|
16
|
+
field_definition_number: number;
|
|
17
|
+
raw_value: number[];
|
|
18
|
+
}
|
|
12
19
|
export type Activity = 'manual' | 'auto_multi_sport' | number;
|
|
13
20
|
export type ActivityClass = 'level_max' | 'level' | 'athlete' | number;
|
|
14
21
|
export type ActivityLevel = 'low' | 'medium' | 'high' | number;
|
|
@@ -2070,6 +2077,7 @@ export interface ParsedFit {
|
|
|
2070
2077
|
protocolVersion?: number;
|
|
2071
2078
|
profileVersion?: number;
|
|
2072
2079
|
messages?: ParsedMessages;
|
|
2080
|
+
raw_developer_fields?: ParsedRawDeveloperField[];
|
|
2073
2081
|
file_creator: ParsedFileCreator;
|
|
2074
2082
|
device_settings: ParsedDeviceSettings;
|
|
2075
2083
|
dive_summary?: ParsedDiveSummary;
|
|
@@ -175,6 +175,13 @@ function generateUtilities() {
|
|
|
175
175
|
]),
|
|
176
176
|
]));
|
|
177
177
|
nodes.push(unitType);
|
|
178
|
+
nodes.push(typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'ParsedRawDeveloperField', undefined, undefined, [
|
|
179
|
+
generateProperty('global_message_number', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
|
|
180
|
+
generateProperty('message_index', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
|
|
181
|
+
generateProperty('developer_data_index', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
|
|
182
|
+
generateProperty('field_definition_number', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
|
|
183
|
+
generateArrayProperty('raw_value', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
|
|
184
|
+
]));
|
|
178
185
|
return nodes;
|
|
179
186
|
}
|
|
180
187
|
function generateAdditionalFields(msg) {
|
|
@@ -258,6 +265,7 @@ function generateFitType() {
|
|
|
258
265
|
generateProperty('protocolVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
|
|
259
266
|
generateProperty('profileVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
|
|
260
267
|
generateProperty('messages', typescript_1.default.factory.createTypeReferenceNode('ParsedMessages')),
|
|
268
|
+
generateArrayProperty('raw_developer_fields', typescript_1.default.factory.createTypeReferenceNode('ParsedRawDeveloperField')),
|
|
261
269
|
...Object.keys(referenceProperties).map(prop => generateProperty(prop, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(referenceProperties[prop].replace('?', ''))), referenceProperties[prop].startsWith('?'))),
|
|
262
270
|
...Object.keys(collectionProperties).map(prop => generateArrayProperty(prop, collectionProperties[prop] === 'unknown'
|
|
263
271
|
? typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.UnknownKeyword)
|
package/dist/fit-parser.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Buffer } from 'buffer';
|
|
|
2
2
|
import type { ParsedFit } from './fit_types.js';
|
|
3
3
|
export { FitBaseType, FitEncoder } from './fit-encoder.js';
|
|
4
4
|
export type { FitEncoderField, FitEncoderOptions } from './fit-encoder.js';
|
|
5
|
+
export type { ParsedFit, ParsedRawDeveloperField } from './fit_types.js';
|
|
5
6
|
export interface FitParserOptions {
|
|
6
7
|
force?: boolean;
|
|
7
8
|
speedUnit?: string;
|
|
@@ -10,6 +11,8 @@ export interface FitParserOptions {
|
|
|
10
11
|
elapsedRecordField?: boolean;
|
|
11
12
|
pressureUnit?: string;
|
|
12
13
|
mode?: 'list' | 'cascade' | 'both';
|
|
14
|
+
/** Retains exact developer-field bytes for all or selected global message numbers. */
|
|
15
|
+
includeRawDeveloperFields?: boolean | readonly number[];
|
|
13
16
|
}
|
|
14
17
|
type FitParserCallback = (error: string | undefined, data: ParsedFit | undefined) => void;
|
|
15
18
|
export default class FitParser {
|
package/dist/fit-parser.js
CHANGED
|
@@ -3,6 +3,7 @@ import { mapDataIntoLap, mapDataIntoSession } from './helper.js';
|
|
|
3
3
|
export { FitBaseType, FitEncoder } from './fit-encoder.js';
|
|
4
4
|
export default class FitParser {
|
|
5
5
|
constructor(options = {}) {
|
|
6
|
+
var _a;
|
|
6
7
|
this.options = {
|
|
7
8
|
force: options.force != null ? options.force : true,
|
|
8
9
|
speedUnit: options.speedUnit || 'm/s',
|
|
@@ -11,6 +12,7 @@ export default class FitParser {
|
|
|
11
12
|
elapsedRecordField: options.elapsedRecordField || false,
|
|
12
13
|
pressureUnit: options.pressureUnit || 'bar',
|
|
13
14
|
mode: options.mode || 'list',
|
|
15
|
+
includeRawDeveloperFields: (_a = options.includeRawDeveloperFields) !== null && _a !== void 0 ? _a : false,
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
parseAsync(content) {
|
|
@@ -26,7 +28,7 @@ export default class FitParser {
|
|
|
26
28
|
});
|
|
27
29
|
}
|
|
28
30
|
parse(content, callback) {
|
|
29
|
-
var _a, _b;
|
|
31
|
+
var _a, _b, _c;
|
|
30
32
|
const blob = content instanceof ArrayBuffer
|
|
31
33
|
? new Uint8Array(content)
|
|
32
34
|
: new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
|
|
@@ -113,6 +115,11 @@ export default class FitParser {
|
|
|
113
115
|
const time_in_zone = [];
|
|
114
116
|
const activity_metrics = [];
|
|
115
117
|
const user_metrics = [];
|
|
118
|
+
const rawDeveloperFields = this.options.includeRawDeveloperFields === true
|
|
119
|
+
|| Array.isArray(this.options.includeRawDeveloperFields)
|
|
120
|
+
? []
|
|
121
|
+
: undefined;
|
|
122
|
+
const messageCountsByGlobalNumber = new Map();
|
|
116
123
|
let loopIndex = headerLength;
|
|
117
124
|
const messageTypes = [];
|
|
118
125
|
const developerFields = [];
|
|
@@ -123,12 +130,25 @@ export default class FitParser {
|
|
|
123
130
|
let lastStopTimestamp;
|
|
124
131
|
let pausedTime = 0;
|
|
125
132
|
while (loopIndex < crcStart) {
|
|
126
|
-
const {
|
|
133
|
+
const { globalMessageNumber, message, messageType, nextIndex, rawDeveloperFields: recordRawDeveloperFields, } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
|
|
127
134
|
loopIndex = nextIndex;
|
|
135
|
+
if (globalMessageNumber !== undefined) {
|
|
136
|
+
const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
|
|
137
|
+
messageCountsByGlobalNumber.set(globalMessageNumber, messageIndex + 1);
|
|
138
|
+
recordRawDeveloperFields === null || recordRawDeveloperFields === void 0 ? void 0 : recordRawDeveloperFields.forEach((field) => {
|
|
139
|
+
rawDeveloperFields === null || rawDeveloperFields === void 0 ? void 0 : rawDeveloperFields.push({
|
|
140
|
+
global_message_number: globalMessageNumber,
|
|
141
|
+
message_index: messageIndex,
|
|
142
|
+
developer_data_index: field.developerDataIndex,
|
|
143
|
+
field_definition_number: field.fieldDefinitionNumber,
|
|
144
|
+
raw_value: field.rawValue,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
128
148
|
if (messageType !== ''
|
|
129
149
|
&& messageType !== 'definition'
|
|
130
150
|
&& message !== undefined) {
|
|
131
|
-
((
|
|
151
|
+
((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
|
|
132
152
|
}
|
|
133
153
|
switch (messageType) {
|
|
134
154
|
case 'lap':
|
|
@@ -263,11 +283,14 @@ export default class FitParser {
|
|
|
263
283
|
fitObj.activity_metrics = activity_metrics;
|
|
264
284
|
fitObj.user_metrics = user_metrics;
|
|
265
285
|
fitObj.messages = messages;
|
|
286
|
+
if (rawDeveloperFields) {
|
|
287
|
+
fitObj.raw_developer_fields = rawDeveloperFields;
|
|
288
|
+
}
|
|
266
289
|
if (isCascadeNeeded) {
|
|
267
290
|
laps = mapDataIntoLap(laps, 'records', records);
|
|
268
291
|
laps = mapDataIntoLap(laps, 'lengths', lengths);
|
|
269
292
|
sessions = mapDataIntoSession(sessions, laps);
|
|
270
|
-
fitObj.activity = Object.assign(Object.assign({}, ((
|
|
293
|
+
fitObj.activity = Object.assign(Object.assign({}, ((_c = fitObj.activity) !== null && _c !== void 0 ? _c : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly
|
|
271
294
|
sessions,
|
|
272
295
|
events,
|
|
273
296
|
hrv,
|
package/dist/fit_types.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ export type Unit<T extends string> = Record<T, {
|
|
|
9
9
|
multiplier: number;
|
|
10
10
|
offset: number;
|
|
11
11
|
}>;
|
|
12
|
+
export interface ParsedRawDeveloperField {
|
|
13
|
+
global_message_number: number;
|
|
14
|
+
message_index: number;
|
|
15
|
+
developer_data_index: number;
|
|
16
|
+
field_definition_number: number;
|
|
17
|
+
raw_value: number[];
|
|
18
|
+
}
|
|
12
19
|
export type Activity = 'manual' | 'auto_multi_sport' | number;
|
|
13
20
|
export type ActivityClass = 'level_max' | 'level' | 'athlete' | number;
|
|
14
21
|
export type ActivityLevel = 'low' | 'medium' | 'high' | number;
|
|
@@ -2070,6 +2077,7 @@ export interface ParsedFit {
|
|
|
2070
2077
|
protocolVersion?: number;
|
|
2071
2078
|
profileVersion?: number;
|
|
2072
2079
|
messages?: ParsedMessages;
|
|
2080
|
+
raw_developer_fields?: ParsedRawDeveloperField[];
|
|
2073
2081
|
file_creator: ParsedFileCreator;
|
|
2074
2082
|
device_settings: ParsedDeviceSettings;
|
|
2075
2083
|
dive_summary?: ParsedDiveSummary;
|
package/dist/type_generator.js
CHANGED
|
@@ -154,6 +154,13 @@ export function generateUtilities() {
|
|
|
154
154
|
]),
|
|
155
155
|
]));
|
|
156
156
|
nodes.push(unitType);
|
|
157
|
+
nodes.push(ts.factory.createInterfaceDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], 'ParsedRawDeveloperField', undefined, undefined, [
|
|
158
|
+
generateProperty('global_message_number', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
159
|
+
generateProperty('message_index', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
160
|
+
generateProperty('developer_data_index', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
161
|
+
generateProperty('field_definition_number', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
162
|
+
generateArrayProperty('raw_value', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
163
|
+
]));
|
|
157
164
|
return nodes;
|
|
158
165
|
}
|
|
159
166
|
function generateAdditionalFields(msg) {
|
|
@@ -237,6 +244,7 @@ export function generateFitType() {
|
|
|
237
244
|
generateProperty('protocolVersion', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
|
|
238
245
|
generateProperty('profileVersion', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
|
|
239
246
|
generateProperty('messages', ts.factory.createTypeReferenceNode('ParsedMessages')),
|
|
247
|
+
generateArrayProperty('raw_developer_fields', ts.factory.createTypeReferenceNode('ParsedRawDeveloperField')),
|
|
240
248
|
...Object.keys(referenceProperties).map(prop => generateProperty(prop, ts.factory.createTypeReferenceNode(snakeToCamel(referenceProperties[prop].replace('?', ''))), referenceProperties[prop].startsWith('?'))),
|
|
241
249
|
...Object.keys(collectionProperties).map(prop => generateArrayProperty(prop, collectionProperties[prop] === 'unknown'
|
|
242
250
|
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
|