@garmin/fitsdk 21.158.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 +3 -3
- package/src/bit-stream.js +3 -3
- package/src/crc-calculator.js +3 -3
- package/src/decoder.js +36 -3
- 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 +1491 -4
- package/src/stream.js +3 -3
- 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
|
|
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
|
|
|
@@ -48,6 +48,8 @@ class Decoder {
|
|
|
48
48
|
#decodeMode = DecodeMode.NORMAL;
|
|
49
49
|
|
|
50
50
|
#mesgListener = null;
|
|
51
|
+
#mesgDefinitionListener = null;
|
|
52
|
+
#fieldDescriptionListener = null;
|
|
51
53
|
#optExpandSubFields = true;
|
|
52
54
|
#optExpandComponents = true;
|
|
53
55
|
#optApplyScaleAndOffset = true;
|
|
@@ -149,11 +151,28 @@ class Decoder {
|
|
|
149
151
|
* @param {Object} message - The message
|
|
150
152
|
*/
|
|
151
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
|
+
*/
|
|
152
169
|
|
|
153
170
|
/**
|
|
154
171
|
* Read the messages from the stream.
|
|
155
172
|
* @param {Object=} [options] - Read options (optional)
|
|
156
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)
|
|
157
176
|
* @param {Boolean} [options.expandSubFields=true] - (optional, default true)
|
|
158
177
|
* @param {Boolean} [options.expandComponents=true] - (optional, default true)
|
|
159
178
|
* @param {Boolean} [options.applyScaleAndOffset=true] - (optional, default true)
|
|
@@ -167,6 +186,8 @@ class Decoder {
|
|
|
167
186
|
*/
|
|
168
187
|
read({
|
|
169
188
|
mesgListener = null,
|
|
189
|
+
mesgDefinitionListener = null,
|
|
190
|
+
fieldDescriptionListener = null,
|
|
170
191
|
expandSubFields = true,
|
|
171
192
|
expandComponents = true,
|
|
172
193
|
applyScaleAndOffset = true,
|
|
@@ -178,6 +199,8 @@ class Decoder {
|
|
|
178
199
|
dataOnly = false,} = {}) {
|
|
179
200
|
|
|
180
201
|
this.#mesgListener = mesgListener;
|
|
202
|
+
this.#mesgDefinitionListener = mesgDefinitionListener;
|
|
203
|
+
this.#fieldDescriptionListener = fieldDescriptionListener;
|
|
181
204
|
this.#optExpandSubFields = expandSubFields
|
|
182
205
|
this.#optExpandComponents = expandComponents;
|
|
183
206
|
this.#optApplyScaleAndOffset = applyScaleAndOffset;
|
|
@@ -310,6 +333,8 @@ class Decoder {
|
|
|
310
333
|
}
|
|
311
334
|
}
|
|
312
335
|
|
|
336
|
+
this.#mesgDefinitionListener?.({...messageDefinition});
|
|
337
|
+
|
|
313
338
|
let messageProfile = Profile.messages[messageDefinition.globalMessageNumber];
|
|
314
339
|
|
|
315
340
|
if (messageProfile == null && this.#optIncludeUnknownData) {
|
|
@@ -414,6 +439,14 @@ class Decoder {
|
|
|
414
439
|
|
|
415
440
|
this.#messages[messageDefinition.messagesKey].push(message);
|
|
416
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
|
+
}
|
|
417
450
|
}
|
|
418
451
|
}
|
|
419
452
|
|
package/src/encoder.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright 2025 Garmin International, Inc.
|
|
3
|
+
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
|
|
4
|
+
// may not use this file except in compliance with the Flexible and Interoperable Data
|
|
5
|
+
// Transfer (FIT) Protocol License.
|
|
6
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
7
|
+
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
|
|
8
|
+
// Profile Version = 21.161.0Release
|
|
9
|
+
// Tag = production/release/21.161.0-0-g58854c0
|
|
10
|
+
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import CrcCalculator from "./crc-calculator.js";
|
|
14
|
+
import FIT from "./fit.js";
|
|
15
|
+
import MesgDefinition from "./mesg-definition.js";
|
|
16
|
+
import OutputStream from "./output-stream.js";
|
|
17
|
+
import Profile from "./profile.js";
|
|
18
|
+
import Utils from "./utils.js";
|
|
19
|
+
|
|
20
|
+
const HEADER_WITH_CRC_SIZE = 14;
|
|
21
|
+
const HEADER_WITHOUT_CRC_SIZE = 12;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A class for encoding FIT files.
|
|
25
|
+
* @class
|
|
26
|
+
*/
|
|
27
|
+
class Encoder {
|
|
28
|
+
/**
|
|
29
|
+
Creates a FIT File Encoder
|
|
30
|
+
* @param {Object} [options] - Encoder options (optional)
|
|
31
|
+
* @param {Object.<number,{object, object }} [options.fieldDescriptions=null] - (optional, default null) fieldDescriptions
|
|
32
|
+
* @constructor
|
|
33
|
+
*/
|
|
34
|
+
constructor({ fieldDescriptions = null, } = {}) {
|
|
35
|
+
this.#fieldDescriptions = {};
|
|
36
|
+
|
|
37
|
+
for (const [key, {developerDataIdMesg, fieldDescriptionMesg}] of Object.entries(fieldDescriptions ?? {})) {
|
|
38
|
+
this.addDeveloperField(key, developerDataIdMesg, fieldDescriptionMesg);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.#writeEmptyFileHeader();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Closes the encoder and returns the file data
|
|
46
|
+
* @returns {Uint8Array} A Uint8Array containing the file data
|
|
47
|
+
*/
|
|
48
|
+
close() {
|
|
49
|
+
this.#updateFileHeader();
|
|
50
|
+
this.#writeFileCrc();
|
|
51
|
+
|
|
52
|
+
return this.#outputStream.uint8Array;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Encodes a mesg into the file.
|
|
57
|
+
* @param {Object} mesg - The message data
|
|
58
|
+
* @param {Number} mesg.mesgNum - The mesg number for this message
|
|
59
|
+
* @return {this}
|
|
60
|
+
*/
|
|
61
|
+
writeMesg(mesg) {
|
|
62
|
+
return this.onMesg(mesg.mesgNum, mesg);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Encodes a mesg into the file.
|
|
67
|
+
* This method can be used as a Decoder~mesgListener callback.
|
|
68
|
+
* @param {Number} mesgNum - The message number for this message
|
|
69
|
+
* @param {Object} mesg - The message data
|
|
70
|
+
* @return {this}
|
|
71
|
+
*/
|
|
72
|
+
onMesg(mesgNum, mesg) {
|
|
73
|
+
try {
|
|
74
|
+
const mesgDefinition = this.#createMesgDefinition(mesgNum, mesg);
|
|
75
|
+
this.#writeMesgDefinitionIfNotActive(mesgDefinition);
|
|
76
|
+
|
|
77
|
+
// Write Message Header
|
|
78
|
+
this.#outputStream.writeUInt8(mesgDefinition.localMesgNum);
|
|
79
|
+
|
|
80
|
+
// Write Field Values
|
|
81
|
+
mesgDefinition.fieldDefinitions.forEach((fieldDefinition) => {
|
|
82
|
+
const values = this.#transformValues(mesg[fieldDefinition.name], fieldDefinition);
|
|
83
|
+
const baseTypeDef = FIT.BaseTypeDefinitions[fieldDefinition.baseType];
|
|
84
|
+
|
|
85
|
+
this.#outputStream.write(values, baseTypeDef.type);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Write Developer Field Values
|
|
89
|
+
mesgDefinition.developerFieldDefinitions.forEach((developerFieldDefinition) => {
|
|
90
|
+
const values = this.#transformValues(
|
|
91
|
+
mesg.developerFields[developerFieldDefinition.key],
|
|
92
|
+
developerFieldDefinition);
|
|
93
|
+
|
|
94
|
+
const baseTypeDef = FIT.BaseTypeDefinitions[developerFieldDefinition.baseType];
|
|
95
|
+
|
|
96
|
+
this.#outputStream.write(values, baseTypeDef.type);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Could not write Message", {
|
|
102
|
+
cause: {
|
|
103
|
+
mesg,
|
|
104
|
+
cause: {
|
|
105
|
+
message: error.message,
|
|
106
|
+
cause: error.cause,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Adds a Developer Data Field Description and associated Developer Data Id Message to the Endoder
|
|
118
|
+
* This provides the Encoder with the context required to write Developer Fields to the output-stream.
|
|
119
|
+
* *** This method does not write the messages to the output-stream ***
|
|
120
|
+
* This method can be used as a Decoder~fieldDescriptionListener callback.
|
|
121
|
+
* @param {Number} key - The message number for this message
|
|
122
|
+
* @param {Object} developerDataIdMesg - The Developer Data Id mesg
|
|
123
|
+
* @param {Object} fieldDescriptionMesg - The Field Description mesg
|
|
124
|
+
* @return {this}
|
|
125
|
+
*/
|
|
126
|
+
addDeveloperField(key, developerDataIdMesg, fieldDescriptionMesg) {
|
|
127
|
+
if(developerDataIdMesg.developerDataIndex == null || fieldDescriptionMesg.developerDataIndex == null) {
|
|
128
|
+
throw new Error("addDeveloperField() - one or more developerDataIndex values are null.", {
|
|
129
|
+
cause: {
|
|
130
|
+
key,
|
|
131
|
+
developerDataIdMesg,
|
|
132
|
+
fieldDescriptionMesg
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if(developerDataIdMesg.developerDataIndex !== fieldDescriptionMesg.developerDataIndex) {
|
|
138
|
+
throw new Error("addDeveloperField() - developerDataIndex values do not match.", {
|
|
139
|
+
cause: {
|
|
140
|
+
key,
|
|
141
|
+
developerDataIdMesg,
|
|
142
|
+
fieldDescriptionMesg
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.#fieldDescriptions[key] = {
|
|
148
|
+
developerDataIdMesg,
|
|
149
|
+
fieldDescriptionMesg
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#writeEmptyFileHeader() {
|
|
156
|
+
Array(HEADER_WITH_CRC_SIZE).fill(0).forEach((zero) => {
|
|
157
|
+
this.#outputStream.writeUInt8(zero);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#updateFileHeader() {
|
|
162
|
+
const arrayBuffer = new ArrayBuffer(HEADER_WITH_CRC_SIZE);
|
|
163
|
+
const dataView = new DataView(arrayBuffer);
|
|
164
|
+
|
|
165
|
+
// Header Size
|
|
166
|
+
dataView.setUint8(0, HEADER_WITH_CRC_SIZE);
|
|
167
|
+
|
|
168
|
+
// Protocol Version
|
|
169
|
+
dataView.setUint8(1, 2);
|
|
170
|
+
|
|
171
|
+
// Profile Version
|
|
172
|
+
dataView.setUint16(2, Profile.version.major * 1000 + Profile.version.minor, true);
|
|
173
|
+
|
|
174
|
+
// Data Size
|
|
175
|
+
dataView.setUint32(4, this.#outputStream.length - HEADER_WITH_CRC_SIZE, true);
|
|
176
|
+
|
|
177
|
+
// Data Type ".FIT"
|
|
178
|
+
dataView.setUint8(8, 0x2E);
|
|
179
|
+
dataView.setUint8(9, 0x46);
|
|
180
|
+
dataView.setUint8(10, 0x49);
|
|
181
|
+
dataView.setUint8(11, 0x54);
|
|
182
|
+
|
|
183
|
+
// Header CRC
|
|
184
|
+
const crc = CrcCalculator.calculateCRC(new Uint8Array(arrayBuffer), 0, HEADER_WITHOUT_CRC_SIZE);
|
|
185
|
+
dataView.setUint16(12, crc, true);
|
|
186
|
+
|
|
187
|
+
this.#outputStream.set(new Uint8Array(arrayBuffer));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#writeFileCrc() {
|
|
191
|
+
const crc = CrcCalculator.calculateCRC(this.#outputStream.uint8Array, 0, this.#outputStream.length);
|
|
192
|
+
this.#outputStream.writeUInt16(crc);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#transformValues(value, fieldDefinition) {
|
|
196
|
+
const values = Array.isArray(value) ? value : [value,];
|
|
197
|
+
|
|
198
|
+
return values.map((value) => {
|
|
199
|
+
return this.#transformValue(value, fieldDefinition);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#transformValue(value, fieldDefinition) {
|
|
204
|
+
try {
|
|
205
|
+
if (FIT.isNotNumberStringDateOrBoolean(value)) {
|
|
206
|
+
return FIT.BaseTypeDefinitions[fieldDefinition.baseType].invalid;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Is this a numeric field?
|
|
210
|
+
if (FIT.NumericFieldTypes.includes(fieldDefinition.type)) {
|
|
211
|
+
if (!FIT.isNumeric(value)) {
|
|
212
|
+
throw new Error();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const scale = Array.isArray(fieldDefinition.scale) ? fieldDefinition.scale[0] : fieldDefinition.scale;
|
|
216
|
+
const offset = Array.isArray(fieldDefinition.offset) ? fieldDefinition.offset[0] : fieldDefinition.offset;
|
|
217
|
+
|
|
218
|
+
return (value + offset) * scale;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Is this a date_time field?
|
|
222
|
+
if (fieldDefinition.type === "dateTime") {
|
|
223
|
+
if (FIT.isDate(value)) {
|
|
224
|
+
return Utils.convertDateToDateTime(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!FIT.isNumeric(value)) {
|
|
228
|
+
throw new Error();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Is this a string field
|
|
235
|
+
if (fieldDefinition.type === "string") {
|
|
236
|
+
if (!FIT.isString(value)) {
|
|
237
|
+
throw new Error();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Must be a FIT type field
|
|
244
|
+
if (FIT.isNumeric(value)) {
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const profileType = Profile.types[fieldDefinition.type];
|
|
249
|
+
|
|
250
|
+
const [typeValue,] = Object.entries(profileType).find(([, typeValue,]) => {
|
|
251
|
+
return typeValue === value;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return typeValue;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Could not convert "${value}" to "${fieldDefinition.type}"`,
|
|
259
|
+
{ cause: { value, fieldDefinition, }, });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Creates a MesgDefinition from the mesgNum and mesg.
|
|
265
|
+
* @param {Number} mesgNum - The mesg number for this message
|
|
266
|
+
* @param {Object} [mesg] - The message data
|
|
267
|
+
* @return {MesgDefinition}
|
|
268
|
+
*/
|
|
269
|
+
#createMesgDefinition = (mesgNum, mesg) => {
|
|
270
|
+
const mesgDefinition = new MesgDefinition(mesgNum, mesg, { fieldDescriptions: this.#fieldDescriptions, });
|
|
271
|
+
mesgDefinition.localMesgNum = this.#lookupLocalMesgNum(mesgDefinition);
|
|
272
|
+
|
|
273
|
+
return mesgDefinition;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Searches the #localMesgDefinitions for a matching mesgDefinition
|
|
278
|
+
* @param {Object} mesgDefinition - the mesg definition to match
|
|
279
|
+
* @return The localMesgNum to be used with mesgDefinition
|
|
280
|
+
*/
|
|
281
|
+
#lookupLocalMesgNum = (mesgDefinition) => {
|
|
282
|
+
const localMesgNum = this.#localMesgDefinitions.findIndex((localMesgDefinition) => {
|
|
283
|
+
return localMesgDefinition?.equals(mesgDefinition) ?? false;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
return (localMesgNum !== -1 ? localMesgNum : this.#nextLocalMesgNum++) & FIT.LOCAL_MESG_NUM_MASK;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Writes the mesgDefinition to the output stream, if it is not one of the currently active 16
|
|
291
|
+
* @param {Object} mesgDefinition - the mesg definition to match
|
|
292
|
+
* @return The localMesgNum to be used with mesgDefinition
|
|
293
|
+
*/
|
|
294
|
+
#writeMesgDefinitionIfNotActive = (mesgDefinition) => {
|
|
295
|
+
const localMesgNum = mesgDefinition.localMesgNum;
|
|
296
|
+
|
|
297
|
+
if (this.#localMesgDefinitions[localMesgNum] == null
|
|
298
|
+
|| !this.#localMesgDefinitions[localMesgNum].equals(mesgDefinition)) {
|
|
299
|
+
this.#writeMesgDefinition(mesgDefinition);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return localMesgNum;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Writes the mesgDefinition to the output stream
|
|
307
|
+
* @param {Object} mesgDefinition - the mesg definition to write
|
|
308
|
+
* @return {this}
|
|
309
|
+
*/
|
|
310
|
+
#writeMesgDefinition(mesgDefinition) {
|
|
311
|
+
mesgDefinition.write(this.#outputStream);
|
|
312
|
+
this.#localMesgDefinitions[mesgDefinition.localMesgNum] = mesgDefinition;
|
|
313
|
+
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#localMesgDefinitions = Array(16).fill(null);
|
|
318
|
+
#nextLocalMesgNum = 0;
|
|
319
|
+
#outputStream = new OutputStream();
|
|
320
|
+
#fieldDescriptions = null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export default Encoder;
|
package/src/fit.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
|
|
|
@@ -69,6 +69,7 @@ const NumericFieldTypes = [
|
|
|
69
69
|
];
|
|
70
70
|
|
|
71
71
|
const FieldTypeToBaseType = {
|
|
72
|
+
"enum": BaseType.UINT8,
|
|
72
73
|
"sint8": BaseType.SINT8,
|
|
73
74
|
"uint8": BaseType.UINT8,
|
|
74
75
|
"sint16": BaseType.SINT16,
|
|
@@ -87,9 +88,83 @@ const FieldTypeToBaseType = {
|
|
|
87
88
|
"uint64z": BaseType.UINT64Z
|
|
88
89
|
};
|
|
89
90
|
|
|
91
|
+
const BaseTypeToFieldType = {
|
|
92
|
+
[BaseType.ENUM]: "enum",
|
|
93
|
+
[BaseType.SINT8]: "sint8",
|
|
94
|
+
[BaseType.UINT8]: "uint8",
|
|
95
|
+
[BaseType.SINT16]: "sint16",
|
|
96
|
+
[BaseType.UINT16]: "uint16",
|
|
97
|
+
[BaseType.SINT32]: "sint32",
|
|
98
|
+
[BaseType.UINT32]: "uint32",
|
|
99
|
+
[BaseType.STRING]: "string",
|
|
100
|
+
[BaseType.FLOAT32]: "float32",
|
|
101
|
+
[BaseType.FLOAT64]: "float64",
|
|
102
|
+
[BaseType.UINT8Z]: "uint8z",
|
|
103
|
+
[BaseType.UINT16Z]: "uint16z",
|
|
104
|
+
[BaseType.UINT32Z]: "uint32z",
|
|
105
|
+
[BaseType.BYTE]: "byte",
|
|
106
|
+
[BaseType.SINT64]: "sint64",
|
|
107
|
+
[BaseType.UINT64]: "uint64",
|
|
108
|
+
[BaseType.UINT64Z]: "uint64z",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const isNullOrUndefined = (obj) => {
|
|
112
|
+
return obj == null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const isObject = (obj) => {
|
|
116
|
+
return typeof obj === "object";
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const isBoolean = (obj) => {
|
|
120
|
+
return "boolean" === typeof obj;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const isDate = (obj) => {
|
|
124
|
+
return typeof obj === "object" && obj instanceof Date;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const isString = (obj) => {
|
|
128
|
+
return typeof obj === "string";
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const isNumeric = (obj) => {
|
|
132
|
+
return !isNaN(parseFloat(obj)) && isFinite(obj);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const isNotNumberStringDateOrBoolean = (obj) => {
|
|
136
|
+
return !isNumberStringDateOrBoolean(obj);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const isNumberStringDateOrBoolean = (obj) => {
|
|
140
|
+
if (isNullOrUndefined(obj)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!isDate(obj) && !isString(obj) && !isNumeric(obj) && !isBoolean(obj)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return true;
|
|
149
|
+
};
|
|
150
|
+
|
|
90
151
|
export default {
|
|
91
152
|
BaseType,
|
|
92
153
|
BaseTypeDefinitions,
|
|
93
154
|
NumericFieldTypes,
|
|
94
|
-
FieldTypeToBaseType
|
|
155
|
+
FieldTypeToBaseType,
|
|
156
|
+
BaseTypeToFieldType,
|
|
157
|
+
isNullOrUndefined,
|
|
158
|
+
isObject,
|
|
159
|
+
isBoolean,
|
|
160
|
+
isDate,
|
|
161
|
+
isString,
|
|
162
|
+
isNumeric,
|
|
163
|
+
isNumberStringDateOrBoolean,
|
|
164
|
+
isNotNumberStringDateOrBoolean,
|
|
165
|
+
MAX_FIELD_SIZE: 255,
|
|
166
|
+
MESG_DEFINITION_MASK: 0x40,
|
|
167
|
+
LOCAL_MESG_NUM_MASK: 0x0F,
|
|
168
|
+
ARCH_LITTLE_ENDIAN: 0x00,
|
|
169
|
+
DEV_DATA_MASK: 0x20,
|
|
95
170
|
};
|
package/src/index.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
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
|
|
|
13
|
+
import CrcCalculator from "./crc-calculator.js";
|
|
13
14
|
import Decoder from "./decoder.js";
|
|
15
|
+
import Encoder from "./encoder.js";
|
|
14
16
|
import Profile from "./profile.js";
|
|
15
17
|
import Stream from "./stream.js";
|
|
16
18
|
import Utils from "./utils.js";
|
|
17
19
|
|
|
18
|
-
export { Decoder, Stream, Profile, Utils };
|
|
20
|
+
export { CrcCalculator, Decoder, Encoder, Stream, Profile, Utils };
|