@garmin/fitsdk 21.168.0 → 21.170.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/src/encoder.js CHANGED
@@ -1,323 +1,333 @@
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.168.0Release
9
- // Tag = production/release/21.168.0-0-gb831b31
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 = fieldDefinition.components.length > 1 ? 1 : fieldDefinition.scale;
216
- const offset = fieldDefinition.components.length > 1 ? 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;
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.170.0Release
9
+ // Tag = production/release/21.170.0-0-g5991e72
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
+ const FIELD_DEFAULT_SCALE = 1;
24
+ const FIELD_DEFAULT_OFFSET = 0;
25
+
26
+ /**
27
+ * A class for encoding FIT files.
28
+ * @class
29
+ */
30
+ class Encoder {
31
+ /**
32
+ Creates a FIT File Encoder
33
+ * @param {Object} [options] - Encoder options (optional)
34
+ * @param {Object.<number,{object, object }} [options.fieldDescriptions=null] - (optional, default null) fieldDescriptions
35
+ * @constructor
36
+ */
37
+ constructor({ fieldDescriptions = null, } = {}) {
38
+ this.#fieldDescriptions = {};
39
+
40
+ for (const [key, {developerDataIdMesg, fieldDescriptionMesg}] of Object.entries(fieldDescriptions ?? {})) {
41
+ this.addDeveloperField(key, developerDataIdMesg, fieldDescriptionMesg);
42
+ }
43
+
44
+ this.#writeEmptyFileHeader();
45
+ }
46
+
47
+ /**
48
+ * Closes the encoder and returns the file data
49
+ * @returns {Uint8Array} A Uint8Array containing the file data
50
+ */
51
+ close() {
52
+ this.#updateFileHeader();
53
+ this.#writeFileCrc();
54
+
55
+ return this.#outputStream.uint8Array;
56
+ }
57
+
58
+ /**
59
+ * Encodes a mesg into the file.
60
+ * @param {Object} mesg - The message data
61
+ * @param {Number} mesg.mesgNum - The mesg number for this message
62
+ * @return {this}
63
+ */
64
+ writeMesg(mesg) {
65
+ return this.onMesg(mesg.mesgNum, mesg);
66
+ }
67
+
68
+ /**
69
+ * Encodes a mesg into the file.
70
+ * This method can be used as a Decoder~mesgListener callback.
71
+ * @param {Number} mesgNum - The message number for this message
72
+ * @param {Object} mesg - The message data
73
+ * @return {this}
74
+ */
75
+ onMesg(mesgNum, mesg) {
76
+ try {
77
+ const mesgDefinition = this.#createMesgDefinition(mesgNum, mesg);
78
+ this.#writeMesgDefinitionIfNotActive(mesgDefinition);
79
+
80
+ // Write Message Header
81
+ this.#outputStream.writeUInt8(mesgDefinition.localMesgNum);
82
+
83
+ // Write Field Values
84
+ mesgDefinition.fieldDefinitions.forEach((fieldDefinition) => {
85
+ const values = this.#transformValues(mesg[fieldDefinition.name], fieldDefinition);
86
+ const baseTypeDef = FIT.BaseTypeDefinitions[fieldDefinition.baseType];
87
+
88
+ this.#outputStream.write(values, baseTypeDef.type);
89
+ });
90
+
91
+ // Write Developer Field Values
92
+ mesgDefinition.developerFieldDefinitions.forEach((developerFieldDefinition) => {
93
+ const values = this.#transformValues(
94
+ mesg.developerFields[developerFieldDefinition.key],
95
+ developerFieldDefinition);
96
+
97
+ const baseTypeDef = FIT.BaseTypeDefinitions[developerFieldDefinition.baseType];
98
+
99
+ this.#outputStream.write(values, baseTypeDef.type);
100
+ });
101
+ }
102
+ catch (error) {
103
+ throw new Error(
104
+ "Could not write Message", {
105
+ cause: {
106
+ mesg,
107
+ cause: {
108
+ message: error.message,
109
+ cause: error.cause,
110
+ },
111
+ },
112
+ }
113
+ );
114
+ }
115
+
116
+ return this;
117
+ };
118
+
119
+ /**
120
+ * Adds a Developer Data Field Description and associated Developer Data Id Message to the Endoder
121
+ * This provides the Encoder with the context required to write Developer Fields to the output-stream.
122
+ * *** This method does not write the messages to the output-stream ***
123
+ * This method can be used as a Decoder~fieldDescriptionListener callback.
124
+ * @param {Number} key - The message number for this message
125
+ * @param {Object} developerDataIdMesg - The Developer Data Id mesg
126
+ * @param {Object} fieldDescriptionMesg - The Field Description mesg
127
+ * @return {this}
128
+ */
129
+ addDeveloperField(key, developerDataIdMesg, fieldDescriptionMesg) {
130
+ if(developerDataIdMesg.developerDataIndex == null || fieldDescriptionMesg.developerDataIndex == null) {
131
+ throw new Error("addDeveloperField() - one or more developerDataIndex values are null.", {
132
+ cause: {
133
+ key,
134
+ developerDataIdMesg,
135
+ fieldDescriptionMesg
136
+ }
137
+ });
138
+ }
139
+
140
+ if(developerDataIdMesg.developerDataIndex !== fieldDescriptionMesg.developerDataIndex) {
141
+ throw new Error("addDeveloperField() - developerDataIndex values do not match.", {
142
+ cause: {
143
+ key,
144
+ developerDataIdMesg,
145
+ fieldDescriptionMesg
146
+ }
147
+ });
148
+ }
149
+
150
+ this.#fieldDescriptions[key] = {
151
+ developerDataIdMesg,
152
+ fieldDescriptionMesg
153
+ }
154
+
155
+ return this;
156
+ }
157
+
158
+ #writeEmptyFileHeader() {
159
+ Array(HEADER_WITH_CRC_SIZE).fill(0).forEach((zero) => {
160
+ this.#outputStream.writeUInt8(zero);
161
+ });
162
+ }
163
+
164
+ #updateFileHeader() {
165
+ const arrayBuffer = new ArrayBuffer(HEADER_WITH_CRC_SIZE);
166
+ const dataView = new DataView(arrayBuffer);
167
+
168
+ // Header Size
169
+ dataView.setUint8(0, HEADER_WITH_CRC_SIZE);
170
+
171
+ // Protocol Version
172
+ dataView.setUint8(1, 2);
173
+
174
+ // Profile Version
175
+ dataView.setUint16(2, Profile.version.major * 1000 + Profile.version.minor, true);
176
+
177
+ // Data Size
178
+ dataView.setUint32(4, this.#outputStream.length - HEADER_WITH_CRC_SIZE, true);
179
+
180
+ // Data Type ".FIT"
181
+ dataView.setUint8(8, 0x2E);
182
+ dataView.setUint8(9, 0x46);
183
+ dataView.setUint8(10, 0x49);
184
+ dataView.setUint8(11, 0x54);
185
+
186
+ // Header CRC
187
+ const crc = CrcCalculator.calculateCRC(new Uint8Array(arrayBuffer), 0, HEADER_WITHOUT_CRC_SIZE);
188
+ dataView.setUint16(12, crc, true);
189
+
190
+ this.#outputStream.set(new Uint8Array(arrayBuffer));
191
+ }
192
+
193
+ #writeFileCrc() {
194
+ const crc = CrcCalculator.calculateCRC(this.#outputStream.uint8Array, 0, this.#outputStream.length);
195
+ this.#outputStream.writeUInt16(crc);
196
+ }
197
+
198
+ #transformValues(value, fieldDefinition) {
199
+ const values = Array.isArray(value) ? value : [value,];
200
+
201
+ return values.map((value) => {
202
+ return this.#transformValue(value, fieldDefinition);
203
+ });
204
+ }
205
+
206
+ #transformValue(value, fieldDefinition) {
207
+ try {
208
+ if (FIT.isNotNumberStringDateOrBoolean(value)) {
209
+ return FIT.BaseTypeDefinitions[fieldDefinition.baseType].invalid;
210
+ }
211
+
212
+ // Is this a numeric field?
213
+ if (FIT.NumericFieldTypes.includes(fieldDefinition.type)) {
214
+ if (!FIT.isNumeric(value)) {
215
+ throw new Error();
216
+ }
217
+
218
+ const scale = fieldDefinition.components.length > 1 ? FIELD_DEFAULT_SCALE : fieldDefinition.scale;
219
+ const offset = fieldDefinition.components.length > 1 ? FIELD_DEFAULT_OFFSET : fieldDefinition.offset;
220
+ const hasScaleOrOffset = (scale != FIELD_DEFAULT_SCALE || offset != FIELD_DEFAULT_OFFSET);
221
+
222
+ if (hasScaleOrOffset) {
223
+ const scaledValue = (value + offset) * scale;
224
+
225
+ return FIT.FloatingPointFieldTypes.includes(fieldDefinition.type) ? scaledValue : Math.round(scaledValue);
226
+ }
227
+
228
+ return value;
229
+ }
230
+
231
+ // Is this a date_time field?
232
+ if (fieldDefinition.type === "dateTime") {
233
+ if (FIT.isDate(value)) {
234
+ return Utils.convertDateToDateTime(value);
235
+ }
236
+
237
+ if (!FIT.isNumeric(value)) {
238
+ throw new Error();
239
+ }
240
+
241
+ return value;
242
+ }
243
+
244
+ // Is this a string field
245
+ if (fieldDefinition.type === "string") {
246
+ if (!FIT.isString(value)) {
247
+ throw new Error();
248
+ }
249
+
250
+ return value;
251
+ }
252
+
253
+ // Must be a FIT type field
254
+ if (FIT.isNumeric(value)) {
255
+ return value;
256
+ }
257
+
258
+ const profileType = Profile.types[fieldDefinition.type];
259
+
260
+ const [typeValue,] = Object.entries(profileType).find(([, typeValue,]) => {
261
+ return typeValue === value;
262
+ });
263
+
264
+ return typeValue;
265
+ }
266
+ catch {
267
+ throw new Error(
268
+ `Could not convert "${value}" to "${fieldDefinition.type}"`,
269
+ { cause: { value, fieldDefinition, }, });
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Creates a MesgDefinition from the mesgNum and mesg.
275
+ * @param {Number} mesgNum - The mesg number for this message
276
+ * @param {Object} [mesg] - The message data
277
+ * @return {MesgDefinition}
278
+ */
279
+ #createMesgDefinition = (mesgNum, mesg) => {
280
+ const mesgDefinition = new MesgDefinition(mesgNum, mesg, { fieldDescriptions: this.#fieldDescriptions, });
281
+ mesgDefinition.localMesgNum = this.#lookupLocalMesgNum(mesgDefinition);
282
+
283
+ return mesgDefinition;
284
+ };
285
+
286
+ /**
287
+ * Searches the #localMesgDefinitions for a matching mesgDefinition
288
+ * @param {Object} mesgDefinition - the mesg definition to match
289
+ * @return The localMesgNum to be used with mesgDefinition
290
+ */
291
+ #lookupLocalMesgNum = (mesgDefinition) => {
292
+ const localMesgNum = this.#localMesgDefinitions.findIndex((localMesgDefinition) => {
293
+ return localMesgDefinition?.equals(mesgDefinition) ?? false;
294
+ });
295
+
296
+ return (localMesgNum !== -1 ? localMesgNum : this.#nextLocalMesgNum++) & FIT.LOCAL_MESG_NUM_MASK;
297
+ };
298
+
299
+ /**
300
+ * Writes the mesgDefinition to the output stream, if it is not one of the currently active 16
301
+ * @param {Object} mesgDefinition - the mesg definition to match
302
+ * @return The localMesgNum to be used with mesgDefinition
303
+ */
304
+ #writeMesgDefinitionIfNotActive = (mesgDefinition) => {
305
+ const localMesgNum = mesgDefinition.localMesgNum;
306
+
307
+ if (this.#localMesgDefinitions[localMesgNum] == null
308
+ || !this.#localMesgDefinitions[localMesgNum].equals(mesgDefinition)) {
309
+ this.#writeMesgDefinition(mesgDefinition);
310
+ }
311
+
312
+ return localMesgNum;
313
+ };
314
+
315
+ /**
316
+ * Writes the mesgDefinition to the output stream
317
+ * @param {Object} mesgDefinition - the mesg definition to write
318
+ * @return {this}
319
+ */
320
+ #writeMesgDefinition(mesgDefinition) {
321
+ mesgDefinition.write(this.#outputStream);
322
+ this.#localMesgDefinitions[mesgDefinition.localMesgNum] = mesgDefinition;
323
+
324
+ return this;
325
+ }
326
+
327
+ #localMesgDefinitions = Array(16).fill(null);
328
+ #nextLocalMesgNum = 0;
329
+ #outputStream = new OutputStream();
330
+ #fieldDescriptions = null;
331
+ }
332
+
333
+ export default Encoder;