fit-file-parser 4.0.1 → 4.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.
@@ -19,6 +19,9 @@ export declare function generateOptions(options: {
19
19
  }): ts.Node[];
20
20
  export declare function generateUtilities(): Statement[];
21
21
  export declare function generateFitType(): Statement;
22
+ export declare function generateParsedMessages(messages: {
23
+ [messageId: number]: Message;
24
+ }): Statement;
22
25
  export declare function generateMessages(messages: {
23
26
  [messageId: number]: Message;
24
27
  }): Statement[];
@@ -16,6 +16,7 @@ exports.generateTypes = generateTypes;
16
16
  exports.generateOptions = generateOptions;
17
17
  exports.generateUtilities = generateUtilities;
18
18
  exports.generateFitType = generateFitType;
19
+ exports.generateParsedMessages = generateParsedMessages;
19
20
  exports.generateMessages = generateMessages;
20
21
  exports.main = main;
21
22
  const typescript_1 = __importDefault(require("typescript"));
@@ -59,6 +60,9 @@ function generateArrayProperty(name, type, optional = true) {
59
60
  return generateProperty(name, typescript_1.default.factory.createArrayTypeNode(type), optional);
60
61
  }
61
62
  function generateTypeFromField(def) {
63
+ if (def.array === true) {
64
+ return typescript_1.default.factory.createArrayTypeNode(generateTypeFromField(Object.assign(Object.assign({}, def), { array: false })));
65
+ }
62
66
  switch (def.type) {
63
67
  case 'uint32_array':
64
68
  case 'uint16_array':
@@ -68,6 +72,8 @@ function generateTypeFromField(def) {
68
72
  case 'sint8_array':
69
73
  case 'byte_array':
70
74
  return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword));
75
+ case 'exercise_category_array':
76
+ return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createTypeReferenceNode('ExerciseCategory'));
71
77
  case 'bool':
72
78
  return typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword);
73
79
  case 'date_time':
@@ -109,11 +115,13 @@ function generateTypes(types) {
109
115
  const names = Object.values(type);
110
116
  const typeAlias = typescript_1.default.factory.createTypeAliasDeclaration([
111
117
  typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
112
- ], snakeToCamel(name), undefined, typescript_1.default.factory.createUnionTypeNode([...names.map(n => typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
113
- ? [
114
- typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('definition')),
115
- ]
116
- : [])]));
118
+ ], snakeToCamel(name), undefined, names.length === 0
119
+ ? typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)
120
+ : typescript_1.default.factory.createUnionTypeNode([...names.map(n => typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
121
+ ? [
122
+ typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('definition')),
123
+ ]
124
+ : [])]));
117
125
  nodes.push(typeAlias);
118
126
  });
119
127
  return nodes;
@@ -180,6 +188,7 @@ function generateAdditionalFields(msg) {
180
188
  developer_data_ids: 'parsed_developer_data_id',
181
189
  field_descriptions: 'parsed_field_description',
182
190
  sports: 'parsed_sport',
191
+ sets: 'parsed_set',
183
192
  splits: 'parsed_split',
184
193
  split_summaries: 'parsed_split_summary',
185
194
  };
@@ -211,6 +220,7 @@ function generateFitType() {
211
220
  tank_updates: 'parsed_tank_update',
212
221
  tank_summaries: 'parsed_tank_summary',
213
222
  jumps: 'parsed_jump',
223
+ sets: 'parsed_set',
214
224
  splits: 'parsed_split',
215
225
  split_summaries: 'parsed_split_summary',
216
226
  time_in_zone: 'parsed_time_in_zone',
@@ -227,15 +237,27 @@ function generateFitType() {
227
237
  activity: 'parsed_activity',
228
238
  zones_target: '?parsed_zones_target',
229
239
  };
240
+ const existingMessageNames = new Set([
241
+ ...Object.values(collectionProperties),
242
+ ...Object.values(referenceProperties),
243
+ ].map(name => name.replace('?', '').replace(/^parsed_/, '')));
244
+ const additionalReferenceProperties = Object.values(fit_js_1.FIT.messages)
245
+ .filter(message => !existingMessageNames.has(message.name))
246
+ .map(message => generateProperty(message.name, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(`parsed_${message.name}`))));
230
247
  return typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'ParsedFit', undefined, undefined, [
231
248
  generateProperty('protocolVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
232
249
  generateProperty('profileVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
250
+ generateProperty('messages', typescript_1.default.factory.createTypeReferenceNode('ParsedMessages')),
233
251
  ...Object.keys(referenceProperties).map(prop => generateProperty(prop, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(referenceProperties[prop].replace('?', ''))), referenceProperties[prop].startsWith('?'))),
234
252
  ...Object.keys(collectionProperties).map(prop => generateArrayProperty(prop, collectionProperties[prop] === 'unknown'
235
253
  ? typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.UnknownKeyword)
236
254
  : typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(collectionProperties[prop])))),
255
+ ...additionalReferenceProperties,
237
256
  ]);
238
257
  }
258
+ function generateParsedMessages(messages) {
259
+ return typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'ParsedMessages', undefined, undefined, Object.values(messages).map(message => generateArrayProperty(message.name, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(`parsed_${message.name}`)))));
260
+ }
239
261
  function generateMessages(messages) {
240
262
  const nodes = [];
241
263
  Object.keys(messages).forEach((name) => {
@@ -245,11 +267,15 @@ function generateMessages(messages) {
245
267
  typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
246
268
  ], snakeToCamel(`parsed_${msg.name}`), undefined, undefined, [
247
269
  ...Object.keys(msg).filter(n => n !== 'name').reduce((acc, id) => {
270
+ var _a;
248
271
  const def = msg[Number(id)];
249
- if (!usedFields.has(def.field)) {
250
- usedFields.add(def.field);
251
- acc.push(generateProperty(def.field, generateTypeFromField(def), !['start_time', 'timestamp'].includes(def.field)));
252
- }
272
+ const definitions = [def, ...((_a = def.aliases) !== null && _a !== void 0 ? _a : [])];
273
+ definitions.forEach((definition) => {
274
+ if (!usedFields.has(definition.field)) {
275
+ usedFields.add(definition.field);
276
+ acc.push(generateProperty(definition.field, generateTypeFromField(definition), !['start_time', 'timestamp'].includes(definition.field)));
277
+ }
278
+ });
253
279
  return acc;
254
280
  }, []),
255
281
  ...generateAdditionalFields(msg),
@@ -271,6 +297,8 @@ function main() {
271
297
  nodes.push(...generateOptions(fit_js_1.FIT.options));
272
298
  nodes.push(...comment('parsed from Fit.messages'));
273
299
  nodes.push(...generateMessages(fit_js_1.FIT.messages));
300
+ nodes.push(...comment('all recognized messages, retained in file order'));
301
+ nodes.push(generateParsedMessages(fit_js_1.FIT.messages));
274
302
  nodes.push(...comment('the returned type after parsing a .fit file'));
275
303
  nodes.push(generateFitType());
276
304
  const nodesArray = typescript_1.default.factory.createNodeArray(nodes);
@@ -26,23 +26,23 @@ export default class FitParser {
26
26
  });
27
27
  }
28
28
  parse(content, callback) {
29
- var _a;
29
+ var _a, _b;
30
30
  const blob = content instanceof ArrayBuffer
31
31
  ? new Uint8Array(content)
32
32
  : new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
33
33
  const dataView = new DataView(blob.buffer, blob.byteOffset, blob.byteLength);
34
34
  if (blob.length < 12) {
35
35
  callback('File to small to be a FIT file', undefined);
36
- if (!this.options.force) {
37
- return;
38
- }
36
+ return;
39
37
  }
40
38
  const headerLength = blob[0];
41
39
  if (headerLength !== 14 && headerLength !== 12) {
42
40
  callback('Incorrect header size', undefined);
43
- if (!this.options.force) {
44
- return;
45
- }
41
+ return;
42
+ }
43
+ if (blob.length < headerLength) {
44
+ callback('File to small to be a FIT file', undefined);
45
+ return;
46
46
  }
47
47
  let fileTypeString = '';
48
48
  for (let i = 8; i < 12; i++) {
@@ -50,29 +50,33 @@ export default class FitParser {
50
50
  }
51
51
  if (fileTypeString !== '.FIT') {
52
52
  callback('Missing \'.FIT\' in header', undefined);
53
- if (!this.options.force) {
54
- return;
55
- }
53
+ return;
56
54
  }
57
55
  if (headerLength === 14 && !this.options.force) {
58
56
  const crcHeader = blob[12] + (blob[13] << 8);
59
57
  const crcHeaderCalc = calculateCRC(blob, 0, 12);
60
- if (crcHeader !== crcHeaderCalc) {
61
- // callback('Header CRC mismatch', {});
62
- // TODO: fix Header CRC check
58
+ if (crcHeader !== 0 && crcHeader !== crcHeaderCalc) {
59
+ callback('Header CRC mismatch', undefined);
63
60
  return;
64
61
  }
65
62
  }
66
63
  const protocolVersion = blob[1];
67
64
  const profileVersion = blob[2] + (blob[3] << 8);
68
- const dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);
65
+ const dataLength = dataView.getUint32(4, true);
69
66
  const crcStart = dataLength + headerLength;
67
+ if (crcStart > blob.length) {
68
+ callback('File data exceeds input length', undefined);
69
+ return;
70
+ }
71
+ if (!this.options.force && crcStart + 2 > blob.length) {
72
+ callback('File CRC missing', undefined);
73
+ return;
74
+ }
70
75
  if (!this.options.force) {
71
76
  const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
72
- const crcFileCalc = calculateCRC(blob, headerLength === 12 ? 0 : headerLength, crcStart);
77
+ const crcFileCalc = calculateCRC(blob, 0, crcStart);
73
78
  if (crcFile !== crcFileCalc) {
74
- // callback('File CRC mismatch', {});
75
- // TODO: fix File CRC check
79
+ callback('File CRC mismatch', undefined);
76
80
  return;
77
81
  }
78
82
  }
@@ -80,6 +84,7 @@ export default class FitParser {
80
84
  profileVersion,
81
85
  protocolVersion,
82
86
  };
87
+ const messages = {};
83
88
  let sessions = [];
84
89
  let laps = [];
85
90
  const records = [];
@@ -102,6 +107,7 @@ export default class FitParser {
102
107
  const tank_updates = [];
103
108
  const tank_summaries = [];
104
109
  const jumps = [];
110
+ const sets = [];
105
111
  const splits = [];
106
112
  const split_summaries = [];
107
113
  const time_in_zone = [];
@@ -110,14 +116,20 @@ export default class FitParser {
110
116
  let loopIndex = headerLength;
111
117
  const messageTypes = [];
112
118
  const developerFields = [];
119
+ const decoderState = {};
113
120
  const isModeCascade = this.options.mode === 'cascade';
114
121
  const isCascadeNeeded = isModeCascade || this.options.mode === 'both';
115
122
  let startDate;
116
123
  let lastStopTimestamp;
117
124
  let pausedTime = 0;
118
125
  while (loopIndex < crcStart) {
119
- const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView);
126
+ const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState);
120
127
  loopIndex = nextIndex;
128
+ if (messageType !== ''
129
+ && messageType !== 'definition'
130
+ && message !== undefined) {
131
+ ((_a = messages[messageType]) !== null && _a !== void 0 ? _a : (messages[messageType] = [])).push(message);
132
+ }
121
133
  switch (messageType) {
122
134
  case 'lap':
123
135
  laps.push(message);
@@ -205,6 +217,9 @@ export default class FitParser {
205
217
  case 'jump':
206
218
  jumps.push(message);
207
219
  break;
220
+ case 'set':
221
+ sets.push(message);
222
+ break;
208
223
  case 'split':
209
224
  splits.push(message);
210
225
  break;
@@ -245,15 +260,17 @@ export default class FitParser {
245
260
  fitObj.time_in_zone = time_in_zone;
246
261
  fitObj.activity_metrics = activity_metrics;
247
262
  fitObj.user_metrics = user_metrics;
263
+ fitObj.messages = messages;
248
264
  if (isCascadeNeeded) {
249
265
  laps = mapDataIntoLap(laps, 'records', records);
250
266
  laps = mapDataIntoLap(laps, 'lengths', lengths);
251
267
  sessions = mapDataIntoSession(sessions, laps);
252
- fitObj.activity = Object.assign(Object.assign({}, ((_a = fitObj.activity) !== null && _a !== void 0 ? _a : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly
268
+ fitObj.activity = Object.assign(Object.assign({}, ((_b = fitObj.activity) !== null && _b !== void 0 ? _b : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly
253
269
  sessions,
254
270
  events,
255
271
  hrv,
256
272
  device_infos, developer_data_ids: applications, field_descriptions: fieldDescriptions, sports,
273
+ sets,
257
274
  splits,
258
275
  split_summaries });
259
276
  }
@@ -267,6 +284,7 @@ export default class FitParser {
267
284
  fitObj.developer_data_ids = applications;
268
285
  fitObj.field_descriptions = fieldDescriptions;
269
286
  fitObj.hrv = hrv;
287
+ fitObj.sets = sets;
270
288
  }
271
289
  callback(undefined, fitObj);
272
290
  }
package/dist/fit.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type { FitOptions, MesgNum } from './fit_types.js';
2
2
  export interface FieldDefinition {
3
3
  type: string | number;
4
+ rawType?: string | number;
4
5
  fDefNo: number;
5
6
  size: number;
7
+ array?: boolean;
6
8
  endianAbility: boolean;
7
9
  littleEndian: boolean;
8
10
  baseTypeNo: number;
@@ -13,13 +15,17 @@ export interface FieldDefinition {
13
15
  requiresBoundedDataView?: boolean;
14
16
  developerDataIndex?: number;
15
17
  isDeveloperField?: boolean;
18
+ aliases?: MessageObject[];
16
19
  }
17
20
  export interface MessageObject {
18
21
  field: string;
19
22
  type: string;
23
+ baseType?: string;
24
+ array?: boolean;
20
25
  scale: number | null;
21
26
  offset: number;
22
27
  units: string;
28
+ aliases?: MessageObject[];
23
29
  }
24
30
  export interface Message {
25
31
  name: MesgNum;
package/dist/fit.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { GARMIN_MESSAGES, GARMIN_TYPES } from './garmin_profile.generated.js';
1
2
  const metersInOneKilometer = 1000;
2
3
  const secondsInOneHour = 3600;
3
4
  // according to https://en.wikipedia.org/wiki/Mile
@@ -8,7 +9,7 @@ const psiInOneBar = 14.5037738;
8
9
  // which contains all the types and values
9
10
  // i would wish for some kind of inference mechanism here but for now we leave it as any
10
11
  // maybe zod could help here
11
- export const FIT = {
12
+ const FIT_OVERRIDES = {
12
13
  scConst: 180 / Math.pow(2, 31),
13
14
  options: {
14
15
  speedUnits: {
@@ -46,6 +47,11 @@ export const FIT = {
46
47
  },
47
48
  },
48
49
  temperatureUnits: {
50
+ // canonical native temperature unit: degree Celsius [°C]
51
+ 'celsius': {
52
+ multiplier: 1,
53
+ offset: 0,
54
+ },
49
55
  // native temperature unit: degree Celsius [°C]
50
56
  '°C': {
51
57
  multiplier: 1,
@@ -54,7 +60,7 @@ export const FIT = {
54
60
  // kelvin [K]
55
61
  'kelvin': {
56
62
  multiplier: 1,
57
- offset: -273.15,
63
+ offset: 273.15,
58
64
  },
59
65
  // degree fahrenheit [°F]
60
66
  'fahrenheit': {
@@ -1794,20 +1800,6 @@ export const FIT = {
1794
1800
  offset: 0,
1795
1801
  units: 'percent',
1796
1802
  },
1797
- 214: {
1798
- field: 'avg_grit',
1799
- type: 'float32',
1800
- scale: null,
1801
- offset: 0,
1802
- units: '',
1803
- },
1804
- 215: {
1805
- field: 'avg_flow',
1806
- type: 'float32',
1807
- scale: null,
1808
- offset: 0,
1809
- units: '',
1810
- },
1811
1803
  140: {
1812
1804
  field: 'recovery_advisor',
1813
1805
  type: 'uint16', // Minutes?
@@ -4132,22 +4124,25 @@ export const FIT = {
4132
4124
  },
4133
4125
  2: {
4134
4126
  field: 'distance',
4135
- type: 'float32',
4136
- scale: null,
4127
+ type: 'uint32',
4128
+ baseType: 'uint32',
4129
+ scale: 100,
4137
4130
  offset: 0,
4138
4131
  units: 'm',
4139
4132
  },
4140
4133
  3: {
4141
4134
  field: 'cycles',
4142
- type: 'float32',
4143
- scale: null,
4135
+ type: 'uint32',
4136
+ baseType: 'uint32',
4137
+ scale: 2,
4144
4138
  offset: 0,
4145
4139
  units: 'cycles',
4146
4140
  },
4147
4141
  4: {
4148
4142
  field: 'active_time',
4149
- type: 'float32',
4150
- scale: null,
4143
+ type: 'uint32',
4144
+ baseType: 'uint32',
4145
+ scale: 1000,
4151
4146
  offset: 0,
4152
4147
  units: 's',
4153
4148
  },
@@ -4202,28 +4197,33 @@ export const FIT = {
4202
4197
  },
4203
4198
  12: {
4204
4199
  field: 'temperature',
4205
- type: 'float32',
4206
- scale: null,
4200
+ type: 'sint16',
4201
+ baseType: 'sint16',
4202
+ scale: 100,
4207
4203
  offset: 0,
4208
4204
  units: 'C',
4209
4205
  },
4210
4206
  14: {
4211
4207
  field: 'temperature_min',
4212
- type: 'float32',
4213
- scale: null,
4208
+ type: 'sint16',
4209
+ baseType: 'sint16',
4210
+ scale: 100,
4214
4211
  offset: 0,
4215
4212
  units: 'C',
4216
4213
  },
4217
4214
  15: {
4218
4215
  field: 'temperature_max',
4219
- type: 'float32',
4220
- scale: null,
4216
+ type: 'sint16',
4217
+ baseType: 'sint16',
4218
+ scale: 100,
4221
4219
  offset: 0,
4222
4220
  units: 'C',
4223
4221
  },
4224
4222
  16: {
4225
4223
  field: 'activity_time',
4226
- type: 'int32',
4224
+ type: 'uint16',
4225
+ baseType: 'uint16',
4226
+ array: true,
4227
4227
  scale: null,
4228
4228
  offset: 0,
4229
4229
  units: '',
@@ -4594,17 +4594,6 @@ export const FIT = {
4594
4594
  units: '',
4595
4595
  },
4596
4596
  },
4597
- 108: {
4598
- name: 'o_hr_settings',
4599
- 253: {
4600
- field: 'timestamp',
4601
- type: 'date_time',
4602
- scale: null,
4603
- offset: 0,
4604
- units: '',
4605
- },
4606
- 0: { field: 'enabled', type: 'byte', scale: null, offset: 0, units: '' },
4607
- },
4608
4597
  140: {
4609
4598
  name: 'activity_metrics',
4610
4599
  1: {
@@ -4739,6 +4728,86 @@ export const FIT = {
4739
4728
  units: '',
4740
4729
  },
4741
4730
  },
4731
+ 225: {
4732
+ name: 'set',
4733
+ 254: {
4734
+ field: 'timestamp',
4735
+ type: 'date_time',
4736
+ scale: null,
4737
+ offset: 0,
4738
+ units: '',
4739
+ },
4740
+ 0: {
4741
+ field: 'duration',
4742
+ type: 'uint32',
4743
+ scale: 1000,
4744
+ offset: 0,
4745
+ units: 's',
4746
+ },
4747
+ 3: {
4748
+ field: 'repetitions',
4749
+ type: 'uint16',
4750
+ scale: null,
4751
+ offset: 0,
4752
+ units: '',
4753
+ },
4754
+ 4: {
4755
+ field: 'weight',
4756
+ type: 'uint16',
4757
+ scale: 16,
4758
+ offset: 0,
4759
+ units: 'kg',
4760
+ },
4761
+ 5: {
4762
+ field: 'set_type',
4763
+ type: 'set_type',
4764
+ scale: null,
4765
+ offset: 0,
4766
+ units: '',
4767
+ },
4768
+ 6: {
4769
+ field: 'start_time',
4770
+ type: 'date_time',
4771
+ scale: null,
4772
+ offset: 0,
4773
+ units: '',
4774
+ },
4775
+ 7: {
4776
+ field: 'category',
4777
+ type: 'exercise_category_array',
4778
+ scale: null,
4779
+ offset: 0,
4780
+ units: '',
4781
+ },
4782
+ 8: {
4783
+ field: 'category_subtype',
4784
+ type: 'uint16_array',
4785
+ scale: null,
4786
+ offset: 0,
4787
+ units: '',
4788
+ },
4789
+ 9: {
4790
+ field: 'weight_display_unit',
4791
+ type: 'fit_base_unit',
4792
+ scale: null,
4793
+ offset: 0,
4794
+ units: '',
4795
+ },
4796
+ 10: {
4797
+ field: 'message_index',
4798
+ type: 'message_index',
4799
+ scale: null,
4800
+ offset: 0,
4801
+ units: '',
4802
+ },
4803
+ 11: {
4804
+ field: 'wkt_step_index',
4805
+ type: 'message_index',
4806
+ scale: null,
4807
+ offset: 0,
4808
+ units: '',
4809
+ },
4810
+ },
4742
4811
  227: {
4743
4812
  name: 'stress_level',
4744
4813
  0: {
@@ -5200,7 +5269,7 @@ export const FIT = {
5200
5269
  units: 's',
5201
5270
  },
5202
5271
  4: {
5203
- field: 'time_in_power_zone',
5272
+ field: 'time_in_cadence_zone',
5204
5273
  type: 'uint32_array',
5205
5274
  scale: 1000,
5206
5275
  offset: 0,
@@ -5228,11 +5297,11 @@ export const FIT = {
5228
5297
  units: 'm/s',
5229
5298
  },
5230
5299
  8: {
5231
- field: 'power_zone_high_boundary',
5232
- type: 'uint16_array',
5300
+ field: 'cadence_zone_high_boundary',
5301
+ type: 'uint8_array',
5233
5302
  scale: null,
5234
5303
  offset: 0,
5235
- units: 'watts',
5304
+ units: 'rpm',
5236
5305
  },
5237
5306
  9: {
5238
5307
  field: 'power_zone_high_boundary',
@@ -9085,3 +9154,57 @@ export const FIT = {
9085
9154
  },
9086
9155
  },
9087
9156
  };
9157
+ function equivalentProfileName(left, right) {
9158
+ return String(left).replace(/_/g, '').toLowerCase()
9159
+ === String(right).replace(/_/g, '').toLowerCase();
9160
+ }
9161
+ function mergeMessages() {
9162
+ const messages = Object.assign({}, FIT_OVERRIDES.messages);
9163
+ Object.entries(GARMIN_MESSAGES).forEach(([messageId, generatedMessage]) => {
9164
+ const overrideMessage = FIT_OVERRIDES.messages[Number(messageId)];
9165
+ if (!overrideMessage) {
9166
+ messages[Number(messageId)] = generatedMessage;
9167
+ return;
9168
+ }
9169
+ const mergedMessage = Object.assign(Object.assign({}, overrideMessage), generatedMessage);
9170
+ Object.entries(generatedMessage).forEach(([fieldId, generatedField]) => {
9171
+ var _a;
9172
+ if (fieldId === 'name') {
9173
+ return;
9174
+ }
9175
+ const overrideField = overrideMessage[Number(fieldId)];
9176
+ const hasCompatibleName = overrideField
9177
+ && equivalentProfileName(overrideField.field, generatedField.field);
9178
+ mergedMessage[Number(fieldId)] = hasCompatibleName
9179
+ ? Object.assign(Object.assign(Object.assign({}, generatedField), overrideField), { baseType: generatedField.baseType, array: (_a = overrideField.array) !== null && _a !== void 0 ? _a : generatedField.array }) : Object.assign(Object.assign(Object.assign({}, overrideField), generatedField), { aliases: overrideField ? [overrideField] : undefined });
9180
+ });
9181
+ messages[Number(messageId)] = mergedMessage;
9182
+ });
9183
+ return messages;
9184
+ }
9185
+ function mergeTypes() {
9186
+ const typeNames = new Set([
9187
+ ...Object.keys(GARMIN_TYPES),
9188
+ ...Object.keys(FIT_OVERRIDES.types),
9189
+ ]);
9190
+ return Object.fromEntries([...typeNames].map((typeName) => {
9191
+ var _a, _b;
9192
+ const generatedValues = (_a = GARMIN_TYPES[typeName]) !== null && _a !== void 0 ? _a : {};
9193
+ const overrideValues = (_b = FIT_OVERRIDES.types[typeName]) !== null && _b !== void 0 ? _b : {};
9194
+ const values = Object.assign({}, generatedValues);
9195
+ Object.entries(overrideValues).forEach(([valueId, overrideValue]) => {
9196
+ const generatedValue = generatedValues[Number(valueId)];
9197
+ if (generatedValue === undefined
9198
+ || equivalentProfileName(overrideValue, generatedValue)) {
9199
+ values[Number(valueId)] = overrideValue;
9200
+ }
9201
+ });
9202
+ return [typeName, values];
9203
+ }));
9204
+ }
9205
+ export const FIT = {
9206
+ scConst: FIT_OVERRIDES.scConst,
9207
+ options: FIT_OVERRIDES.options,
9208
+ messages: mergeMessages(),
9209
+ types: mergeTypes(),
9210
+ };