@garmin/fitsdk 21.141.0 → 21.161.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/README.md +6 -0
- package/package.json +4 -7
- package/src/accumulator.js +16 -6
- package/src/bit-stream.js +3 -3
- package/src/crc-calculator.js +3 -3
- package/src/decoder.js +114 -25
- package/src/encoder.js +323 -0
- package/src/fit.js +79 -4
- package/src/index.js +6 -4
- package/src/mesg-definition.js +269 -0
- package/src/output-stream.js +220 -0
- package/src/profile.js +1546 -16
- package/src/stream.js +8 -8
- package/src/utils-hr-mesg.js +3 -3
- package/src/utils-internal.js +3 -3
- package/src/utils.js +38 -3
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ The Read method accepts an optional options object that can be used to customize
|
|
|
68
68
|
````js
|
|
69
69
|
const { messages, errors } = decoder.read({
|
|
70
70
|
mesgListener: (messageNumber, message) => {},
|
|
71
|
+
mesgDefinitionListener: (mesgDefinition) => {},
|
|
72
|
+
fieldDescriptionListener: (key, developerDataIdMesg, fieldDescriptionMesg) => {},
|
|
71
73
|
applyScaleAndOffset: true,
|
|
72
74
|
expandSubFields: true,
|
|
73
75
|
expandComponents: true,
|
|
@@ -97,6 +99,10 @@ const { messages, errors } = decoder.read({
|
|
|
97
99
|
|
|
98
100
|
console.log(recordFields);
|
|
99
101
|
````
|
|
102
|
+
#### mesgDefinitionListener: (mesgDefinition) => {}
|
|
103
|
+
Optional callback function that can be used to inspect message defintions as they are decoded from the file.
|
|
104
|
+
#### fieldDescriptionListener: (key, developerDataIdMesg, fieldDescriptionMesg) => {}
|
|
105
|
+
Optional callback function that can be used to inspect developer field descriptions as they are decoded from the file.
|
|
100
106
|
#### applyScaleAndOffset: true | false
|
|
101
107
|
When true the scale and offset values as defined in the FIT Profile are applied to the raw field values.
|
|
102
108
|
````js
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@garmin/fitsdk",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.161.0",
|
|
4
4
|
"description": "FIT JavaScript SDK",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "node .",
|
|
9
|
-
"test": "
|
|
9
|
+
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"author": "Garmin International, Inc.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
@@ -18,9 +18,6 @@
|
|
|
18
18
|
"src/"
|
|
19
19
|
],
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"
|
|
22
|
-
},
|
|
23
|
-
"jest": {
|
|
24
|
-
"transform": {}
|
|
21
|
+
"vitest": "^2.1.8"
|
|
25
22
|
}
|
|
26
|
-
}
|
|
23
|
+
}
|
package/src/accumulator.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 Garmin International, Inc.
|
|
3
3
|
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
|
4
4
|
// may not use this file except in compliance with the Flexible and Interoperable Data
|
|
5
5
|
// Transfer (FIT) Protocol License.
|
|
6
6
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
7
7
|
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
|
8
|
-
// Profile Version = 21.
|
|
9
|
-
// Tag = production/release/21.
|
|
8
|
+
// Profile Version = 21.161.0Release
|
|
9
|
+
// Tag = production/release/21.161.0-0-g58854c0
|
|
10
10
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
11
11
|
|
|
12
12
|
|
|
@@ -32,16 +32,26 @@ class AccumulatedField {
|
|
|
32
32
|
class Accumulator {
|
|
33
33
|
#messages = {};
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
createAccumulatedField(mesgNum, fieldNum, value) {
|
|
36
|
+
const accumualtedField = new AccumulatedField(value);
|
|
37
|
+
|
|
36
38
|
if (this.#messages[mesgNum] == null) {
|
|
37
39
|
this.#messages[mesgNum] = {};
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
this.#messages[mesgNum][fieldNum] =
|
|
42
|
+
this.#messages[mesgNum][fieldNum] = accumualtedField;
|
|
43
|
+
|
|
44
|
+
return accumualtedField;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
accumulate(mesgNum, fieldNum, value, bits) {
|
|
44
|
-
|
|
48
|
+
let accumualtedField = this.#messages[mesgNum]?.[fieldNum];
|
|
49
|
+
|
|
50
|
+
if(accumualtedField == null) {
|
|
51
|
+
accumualtedField = this.createAccumulatedField(mesgNum, fieldNum, value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return accumualtedField.accumulate(value, bits);
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
package/src/bit-stream.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 Garmin International, Inc.
|
|
3
3
|
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
|
4
4
|
// may not use this file except in compliance with the Flexible and Interoperable Data
|
|
5
5
|
// Transfer (FIT) Protocol License.
|
|
6
6
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
7
7
|
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
|
8
|
-
// Profile Version = 21.
|
|
9
|
-
// Tag = production/release/21.
|
|
8
|
+
// Profile Version = 21.161.0Release
|
|
9
|
+
// Tag = production/release/21.161.0-0-g58854c0
|
|
10
10
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
11
11
|
|
|
12
12
|
|
package/src/crc-calculator.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 Garmin International, Inc.
|
|
3
3
|
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
|
4
4
|
// may not use this file except in compliance with the Flexible and Interoperable Data
|
|
5
5
|
// Transfer (FIT) Protocol License.
|
|
6
6
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
7
7
|
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
|
8
|
-
// Profile Version = 21.
|
|
9
|
-
// Tag = production/release/21.
|
|
8
|
+
// Profile Version = 21.161.0Release
|
|
9
|
+
// Tag = production/release/21.161.0-0-g58854c0
|
|
10
10
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
11
11
|
|
|
12
12
|
|
package/src/decoder.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2025 Garmin International, Inc.
|
|
3
3
|
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
|
4
4
|
// may not use this file except in compliance with the Flexible and Interoperable Data
|
|
5
5
|
// Transfer (FIT) Protocol License.
|
|
6
6
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
7
7
|
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
|
8
|
-
// Profile Version = 21.
|
|
9
|
-
// Tag = production/release/21.
|
|
8
|
+
// Profile Version = 21.161.0Release
|
|
9
|
+
// Tag = production/release/21.161.0-0-g58854c0
|
|
10
10
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
11
11
|
|
|
12
12
|
|
|
@@ -25,8 +25,17 @@ const MESG_DEFINITION_MASK = 0x40;
|
|
|
25
25
|
const DEV_DATA_MASK = 0x20;
|
|
26
26
|
const MESG_HEADER_MASK = 0x00;
|
|
27
27
|
const LOCAL_MESG_NUM_MASK = 0x0F;
|
|
28
|
+
|
|
29
|
+
const HEADER_WITH_CRC_SIZE = 14;
|
|
30
|
+
const HEADER_WITHOUT_CRC_SIZE = 12;
|
|
28
31
|
const CRC_SIZE = 2;
|
|
29
32
|
|
|
33
|
+
const DecodeMode = Object.freeze({
|
|
34
|
+
NORMAL: "normal",
|
|
35
|
+
SKIP_HEADER: "skipHeader",
|
|
36
|
+
DATA_ONLY: "dataOnly"
|
|
37
|
+
});
|
|
38
|
+
|
|
30
39
|
class Decoder {
|
|
31
40
|
#localMessageDefinitions = [];
|
|
32
41
|
#developerDataDefinitions = {};
|
|
@@ -36,7 +45,11 @@ class Decoder {
|
|
|
36
45
|
#fieldsWithSubFields = [];
|
|
37
46
|
#fieldsToExpand = [];
|
|
38
47
|
|
|
48
|
+
#decodeMode = DecodeMode.NORMAL;
|
|
49
|
+
|
|
39
50
|
#mesgListener = null;
|
|
51
|
+
#mesgDefinitionListener = null;
|
|
52
|
+
#fieldDescriptionListener = null;
|
|
40
53
|
#optExpandSubFields = true;
|
|
41
54
|
#optExpandComponents = true;
|
|
42
55
|
#optApplyScaleAndOffset = true;
|
|
@@ -67,7 +80,7 @@ class Decoder {
|
|
|
67
80
|
static isFIT(stream) {
|
|
68
81
|
try {
|
|
69
82
|
const fileHeaderSize = stream.peekByte();
|
|
70
|
-
if ([
|
|
83
|
+
if ([HEADER_WITH_CRC_SIZE, HEADER_WITHOUT_CRC_SIZE].includes(fileHeaderSize) != true) {
|
|
71
84
|
return false;
|
|
72
85
|
}
|
|
73
86
|
|
|
@@ -75,7 +88,7 @@ class Decoder {
|
|
|
75
88
|
return false;
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
const fileHeader = Decoder.#readFileHeader(stream, true);
|
|
91
|
+
const fileHeader = Decoder.#readFileHeader(stream, { resetPosition: true, });
|
|
79
92
|
if (fileHeader.dataType !== ".FIT") {
|
|
80
93
|
return false;
|
|
81
94
|
}
|
|
@@ -105,7 +118,7 @@ class Decoder {
|
|
|
105
118
|
return false;
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
const fileHeader = Decoder.#readFileHeader(this.#stream, true);
|
|
121
|
+
const fileHeader = Decoder.#readFileHeader(this.#stream, { resetPosition: true, });
|
|
109
122
|
|
|
110
123
|
if (this.#stream.length < fileHeader.headerSize + fileHeader.dataSize + CRC_SIZE) {
|
|
111
124
|
return false;
|
|
@@ -113,7 +126,7 @@ class Decoder {
|
|
|
113
126
|
|
|
114
127
|
const buf = new Uint8Array(this.#stream.slice(0, this.#stream.length))
|
|
115
128
|
|
|
116
|
-
if (fileHeader.headerSize ===
|
|
129
|
+
if (fileHeader.headerSize === HEADER_WITH_CRC_SIZE && fileHeader.headerCRC !== 0x0000
|
|
117
130
|
&& fileHeader.headerCRC != CrcCalculator.calculateCRC(buf, 0, 12)) {
|
|
118
131
|
return false;
|
|
119
132
|
}
|
|
@@ -138,11 +151,28 @@ class Decoder {
|
|
|
138
151
|
* @param {Object} message - The message
|
|
139
152
|
*/
|
|
140
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Message Definition Listener Callback
|
|
156
|
+
*
|
|
157
|
+
* @callback Decoder~mesgDefinitionListener
|
|
158
|
+
* @param {Object} messageDefinition - The message Definition
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Developer Field Description Listener Callback
|
|
163
|
+
*
|
|
164
|
+
* @callback Decoder~fieldDescriptionListener
|
|
165
|
+
* @param {Number} key - The key associated with this pairing of of Developer Data Id and Field Description Mesgs
|
|
166
|
+
* @param {Object} developerDataIdMesg - The Developer Data Id Mesg associated with this pairing
|
|
167
|
+
* @param {Object} fieldDescriptionMesg - The Field Description Mesg associated with this pairing
|
|
168
|
+
*/
|
|
141
169
|
|
|
142
170
|
/**
|
|
143
171
|
* Read the messages from the stream.
|
|
144
172
|
* @param {Object=} [options] - Read options (optional)
|
|
145
173
|
* @param {Decoder~mesgListener} [options.mesgListener=null] - (optional, default null) mesgListener(mesgNum, message)
|
|
174
|
+
* @param {Decoder~mesgDefinitionListener} [options.mesgDefinitionListener=null] - (optional, default null) mesgDefinitionListener(mesgDefinition)
|
|
175
|
+
* @param {Decoder~fieldDescriptionListener} [options.fieldDescriptionListener=null] - (optional, default null) fieldDescriptionListener(key, developerDataIdMesg, fieldDescriptionMesg)
|
|
146
176
|
* @param {Boolean} [options.expandSubFields=true] - (optional, default true)
|
|
147
177
|
* @param {Boolean} [options.expandComponents=true] - (optional, default true)
|
|
148
178
|
* @param {Boolean} [options.applyScaleAndOffset=true] - (optional, default true)
|
|
@@ -150,19 +180,27 @@ class Decoder {
|
|
|
150
180
|
* @param {boolean} [options.convertDateTimesToDates=true] - (optional, default true)
|
|
151
181
|
* @param {Boolean} [options.includeUnknownData=false] - (optional, default false)
|
|
152
182
|
* @param {boolean} [options.mergeHeartRates=true] - (optional, default false)
|
|
183
|
+
* @param {boolean} [options.skipHeader=false] - (optional, default false)
|
|
184
|
+
* @param {boolean} [options.dataOnly=false] - (optional, default false)
|
|
153
185
|
* @return {Object} result - {messages:Array, errors:Array}
|
|
154
186
|
*/
|
|
155
187
|
read({
|
|
156
188
|
mesgListener = null,
|
|
189
|
+
mesgDefinitionListener = null,
|
|
190
|
+
fieldDescriptionListener = null,
|
|
157
191
|
expandSubFields = true,
|
|
158
192
|
expandComponents = true,
|
|
159
193
|
applyScaleAndOffset = true,
|
|
160
194
|
convertTypesToStrings = true,
|
|
161
195
|
convertDateTimesToDates = true,
|
|
162
196
|
includeUnknownData = false,
|
|
163
|
-
mergeHeartRates = true
|
|
197
|
+
mergeHeartRates = true,
|
|
198
|
+
skipHeader = false,
|
|
199
|
+
dataOnly = false,} = {}) {
|
|
164
200
|
|
|
165
201
|
this.#mesgListener = mesgListener;
|
|
202
|
+
this.#mesgDefinitionListener = mesgDefinitionListener;
|
|
203
|
+
this.#fieldDescriptionListener = fieldDescriptionListener;
|
|
166
204
|
this.#optExpandSubFields = expandSubFields
|
|
167
205
|
this.#optExpandComponents = expandComponents;
|
|
168
206
|
this.#optApplyScaleAndOffset = applyScaleAndOffset;
|
|
@@ -182,7 +220,11 @@ class Decoder {
|
|
|
182
220
|
this.#throwError("mergeHeartRates requires applyScaleAndOffset and expandComponents to be enabled");
|
|
183
221
|
}
|
|
184
222
|
|
|
185
|
-
|
|
223
|
+
if (dataOnly && skipHeader) {
|
|
224
|
+
this.#throwError("dataOnly and skipHeader cannot both be enabled")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.#decodeMode = skipHeader ? DecodeMode.SKIP_HEADER : dataOnly ? DecodeMode.DATA_ONLY : DecodeMode.NORMAL;
|
|
186
228
|
|
|
187
229
|
while (this.#stream.position < this.#stream.length) {
|
|
188
230
|
this.#decodeNextFile();
|
|
@@ -203,23 +245,23 @@ class Decoder {
|
|
|
203
245
|
#decodeNextFile() {
|
|
204
246
|
const position = this.#stream.position;
|
|
205
247
|
|
|
206
|
-
if (!this.isFIT()) {
|
|
248
|
+
if (this.#decodeMode === DecodeMode.NORMAL && !this.isFIT()) {
|
|
207
249
|
this.#throwError("input is not a FIT file");
|
|
208
250
|
}
|
|
209
251
|
|
|
210
252
|
this.#stream.crcCalculator = new CrcCalculator();
|
|
211
253
|
|
|
212
|
-
const
|
|
254
|
+
const { headerSize, dataSize } = Decoder.#readFileHeader(this.#stream, { decodeMode: this.#decodeMode });
|
|
213
255
|
|
|
214
256
|
// Read data messages and definitions
|
|
215
|
-
while (this.#stream.position < (position +
|
|
257
|
+
while (this.#stream.position < (position + headerSize + dataSize)) {
|
|
216
258
|
this.#decodeNextRecord();
|
|
217
259
|
}
|
|
218
260
|
|
|
219
261
|
// Check the CRC
|
|
220
262
|
const calculatedCrc = this.#stream.crcCalculator.crc;
|
|
221
263
|
const crc = this.#stream.readUInt16();
|
|
222
|
-
if (crc !== calculatedCrc) {
|
|
264
|
+
if (this.#decodeMode === DecodeMode.NORMAL && crc !== calculatedCrc) {
|
|
223
265
|
this.#throwError("CRC error");
|
|
224
266
|
}
|
|
225
267
|
}
|
|
@@ -291,6 +333,8 @@ class Decoder {
|
|
|
291
333
|
}
|
|
292
334
|
}
|
|
293
335
|
|
|
336
|
+
this.#mesgDefinitionListener?.({...messageDefinition});
|
|
337
|
+
|
|
294
338
|
let messageProfile = Profile.messages[messageDefinition.globalMessageNumber];
|
|
295
339
|
|
|
296
340
|
if (messageProfile == null && this.#optIncludeUnknownData) {
|
|
@@ -341,7 +385,7 @@ class Decoder {
|
|
|
341
385
|
}
|
|
342
386
|
|
|
343
387
|
if (field?.isAccumulated) {
|
|
344
|
-
this.#
|
|
388
|
+
this.#setAccumulatedField(messageDefinition, message, field, rawFieldValue);
|
|
345
389
|
}
|
|
346
390
|
}
|
|
347
391
|
});
|
|
@@ -395,6 +439,14 @@ class Decoder {
|
|
|
395
439
|
|
|
396
440
|
this.#messages[messageDefinition.messagesKey].push(message);
|
|
397
441
|
this.#mesgListener?.(messageDefinition.globalMessageNumber, message);
|
|
442
|
+
|
|
443
|
+
if (mesgNum === Profile.MesgNum.FIELD_DESCRIPTION && this.#fieldDescriptionListener != null) {
|
|
444
|
+
const developerDataIdMesg = this.#messages.developerDataIdMesgs?.find((developerDataIdMesg) => {
|
|
445
|
+
return developerDataIdMesg.developerDataIndex === message.developerDataIndex;
|
|
446
|
+
}) ?? {};
|
|
447
|
+
|
|
448
|
+
this.#fieldDescriptionListener(message.key, {...developerDataIdMesg}, {...message});
|
|
449
|
+
}
|
|
398
450
|
}
|
|
399
451
|
}
|
|
400
452
|
|
|
@@ -530,7 +582,7 @@ class Decoder {
|
|
|
530
582
|
while (this.#fieldsToExpand.length > 0) {
|
|
531
583
|
const name = this.#fieldsToExpand.shift();
|
|
532
584
|
|
|
533
|
-
const { rawFieldValue, fieldDefinitionNumber, isSubField } = message[name];
|
|
585
|
+
const { rawFieldValue, fieldDefinitionNumber, isSubField } = message[name] ?? mesg[name];
|
|
534
586
|
let field = Profile.messages[mesgNum].fields[fieldDefinitionNumber];
|
|
535
587
|
field = isSubField ? this.#lookupSubfield(field, name) : field;
|
|
536
588
|
const baseType = FIT.FieldTypeToBaseType[field.type];
|
|
@@ -546,6 +598,10 @@ class Decoder {
|
|
|
546
598
|
const bitStream = new BitStream(rawFieldValue, baseType);
|
|
547
599
|
|
|
548
600
|
for (let j = 0; j < field.components.length; j++) {
|
|
601
|
+
if (bitStream.bitsAvailable < field.bits[j]) {
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
|
|
549
605
|
const targetField = fields[field.components[j]];
|
|
550
606
|
if (mesg[targetField.name] == null) {
|
|
551
607
|
const baseType = FIT.FieldTypeToBaseType[targetField.type];
|
|
@@ -560,22 +616,22 @@ class Decoder {
|
|
|
560
616
|
};
|
|
561
617
|
}
|
|
562
618
|
|
|
563
|
-
if (bitStream.bitsAvailable < field.bits[j]) {
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
619
|
let value = bitStream.readBits(field.bits[j]);
|
|
568
620
|
|
|
569
|
-
|
|
621
|
+
if (targetField.isAccumulated) {
|
|
622
|
+
value = this.#accumulator.accumulate(mesgNum, targetField.num, value, field.bits[j]);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Undo component scale and offset before applying the destination field's scale and offset
|
|
626
|
+
value = (value / field.scale[j] - field.offset[j]);
|
|
570
627
|
|
|
571
|
-
|
|
628
|
+
const rawValue = (value + targetField.offset) * targetField.scale;
|
|
629
|
+
mesg[targetField.name].rawFieldValue.push(rawValue);
|
|
572
630
|
|
|
573
|
-
if (
|
|
631
|
+
if (rawValue === mesg[targetField.name].invalidValue) {
|
|
574
632
|
mesg[targetField.name].fieldValue.push(null);
|
|
575
633
|
}
|
|
576
634
|
else {
|
|
577
|
-
value = value / field.scale[j] - field.offset[j];
|
|
578
|
-
|
|
579
635
|
if (this.#optConvertTypesToStrings) {
|
|
580
636
|
value = this.#convertTypeToString(mesg, targetField, value);
|
|
581
637
|
}
|
|
@@ -681,6 +737,26 @@ class Decoder {
|
|
|
681
737
|
}
|
|
682
738
|
}
|
|
683
739
|
|
|
740
|
+
#setAccumulatedField(messageDefinition, message, field, rawFieldValue) {
|
|
741
|
+
const rawFieldValues = Array.isArray(rawFieldValue) ? rawFieldValue : [rawFieldValue];
|
|
742
|
+
|
|
743
|
+
rawFieldValues.forEach((value) => {
|
|
744
|
+
Object.values(message).forEach((containingField) => {
|
|
745
|
+
let components = messageDefinition.fields[containingField.fieldDefinitionNumber].components ?? []
|
|
746
|
+
|
|
747
|
+
components.forEach((componentFieldNum, i) => {
|
|
748
|
+
const targetField = messageDefinition.fields[componentFieldNum];
|
|
749
|
+
|
|
750
|
+
if(targetField?.num == field.num && targetField?.isAccumulated) {
|
|
751
|
+
value = (((value / field.scale) - field.offset) + containingField.offset[i]) * containingField.scale[i];
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
this.#accumulator.createAccumulatedField(messageDefinition.num, field.num, value);
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
684
760
|
#convertTypeToString(messageDefinition, field, rawFieldValue) {
|
|
685
761
|
if ([Profile.MesgNum.DEVELOPER_DATA_ID, Profile.MesgNum.FIELD_DESCRIPTION].includes(messageDefinition.globalMessageNumber)) {
|
|
686
762
|
return rawFieldValue;
|
|
@@ -711,9 +787,22 @@ class Decoder {
|
|
|
711
787
|
return subField != null ? subField : {};
|
|
712
788
|
}
|
|
713
789
|
|
|
714
|
-
static #readFileHeader(stream, resetPosition = false) {
|
|
790
|
+
static #readFileHeader(stream, { resetPosition = false, decodeMode = DecodeMode.NORMAL }) {
|
|
715
791
|
const position = stream.position;
|
|
716
792
|
|
|
793
|
+
if(decodeMode !== DecodeMode.NORMAL) {
|
|
794
|
+
if(decodeMode === DecodeMode.SKIP_HEADER) {
|
|
795
|
+
stream.seek(HEADER_WITH_CRC_SIZE);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const headerSize = decodeMode === DecodeMode.SKIP_HEADER ? HEADER_WITH_CRC_SIZE : 0;
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
headerSize,
|
|
802
|
+
dataSize: stream.length - headerSize - CRC_SIZE,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
717
806
|
const fileHeader = {
|
|
718
807
|
headerSize: stream.readByte(),
|
|
719
808
|
protocolVersion: stream.readByte(),
|