fit-file-parser 2.3.2 → 3.0.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/dist/binary.js CHANGED
@@ -128,7 +128,9 @@ function formatByType(data, type, scale, offset) {
128
128
  }
129
129
  }
130
130
  if (!values.includes('mask')) {
131
- return FIT.types[type][data];
131
+ const typeMap = FIT.types[type];
132
+ const mapped = typeMap[String(data)];
133
+ return mapped === undefined ? data : mapped;
132
134
  }
133
135
  const dataItem = {};
134
136
  for (const key in FIT.types[type]) {
@@ -188,6 +190,13 @@ function isInvalidValue(data, type) {
188
190
  return false;
189
191
  }
190
192
  }
193
+ function isInvalidBaseTypeValue(data, baseTypeNo) {
194
+ if (Array.isArray(data)) {
195
+ return false;
196
+ }
197
+ const baseType = FIT.types.fit_base_type[baseTypeNo];
198
+ return typeof baseType === 'string' ? isInvalidValue(data, baseType) : false;
199
+ }
191
200
  function convertTo(data, unitsList, unitName) {
192
201
  const options = FIT.options[unitsList];
193
202
  const unit = options[unitName];
@@ -264,6 +273,7 @@ function applyOptions(data, field, options, fields) {
264
273
  }
265
274
  }
266
275
  export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
276
+ var _a, _b;
267
277
  const recordHeader = blob[startIndex];
268
278
  let localMessageType = recordHeader & 15;
269
279
  if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
@@ -299,7 +309,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
299
309
  size: blob[fDefIndex + 1],
300
310
  endianAbility: (baseType & 128) === 128,
301
311
  littleEndian: lEnd,
302
- baseTypeNo: baseType & 15,
312
+ baseTypeNo: baseType,
303
313
  name: field,
304
314
  dataType: getFitMessageBaseType(baseType & 15),
305
315
  };
@@ -321,7 +331,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
321
331
  size,
322
332
  endianAbility: (baseType & 128) === 128,
323
333
  littleEndian: lEnd,
324
- baseTypeNo: baseType & 15,
334
+ baseTypeNo: baseType,
325
335
  name: devDef.field_name,
326
336
  dataType: getFitMessageBaseType(baseType & 15),
327
337
  scale: devDef.scale || 1,
@@ -357,7 +367,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
357
367
  for (let i = 0; i < messageType.fieldDefs.length; i++) {
358
368
  const fDef = messageType.fieldDefs[i];
359
369
  const data = readData(blob, fDef, readDataFromIndex, options);
360
- if (!isInvalidValue(data, fDef.type)) {
370
+ if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
361
371
  rawFields.push({ fDef, data });
362
372
  }
363
373
  readDataFromIndex += fDef.size;
@@ -373,8 +383,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
373
383
  if (fDef.isDeveloperField) {
374
384
  const field = fDef.name;
375
385
  const { type } = fDef;
376
- const { scale } = fDef;
377
- const { offset } = fDef;
386
+ const scale = (_a = fDef.scale) !== null && _a !== void 0 ? _a : null;
387
+ const offset = (_b = fDef.offset) !== null && _b !== void 0 ? _b : 0;
378
388
  fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
379
389
  }
380
390
  else {
@@ -134,7 +134,9 @@ function formatByType(data, type, scale, offset) {
134
134
  }
135
135
  }
136
136
  if (!values.includes('mask')) {
137
- return fit_js_1.FIT.types[type][data];
137
+ const typeMap = fit_js_1.FIT.types[type];
138
+ const mapped = typeMap[String(data)];
139
+ return mapped === undefined ? data : mapped;
138
140
  }
139
141
  const dataItem = {};
140
142
  for (const key in fit_js_1.FIT.types[type]) {
@@ -194,6 +196,13 @@ function isInvalidValue(data, type) {
194
196
  return false;
195
197
  }
196
198
  }
199
+ function isInvalidBaseTypeValue(data, baseTypeNo) {
200
+ if (Array.isArray(data)) {
201
+ return false;
202
+ }
203
+ const baseType = fit_js_1.FIT.types.fit_base_type[baseTypeNo];
204
+ return typeof baseType === 'string' ? isInvalidValue(data, baseType) : false;
205
+ }
197
206
  function convertTo(data, unitsList, unitName) {
198
207
  const options = fit_js_1.FIT.options[unitsList];
199
208
  const unit = options[unitName];
@@ -270,6 +279,7 @@ function applyOptions(data, field, options, fields) {
270
279
  }
271
280
  }
272
281
  function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
282
+ var _a, _b;
273
283
  const recordHeader = blob[startIndex];
274
284
  let localMessageType = recordHeader & 15;
275
285
  if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
@@ -305,7 +315,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
305
315
  size: blob[fDefIndex + 1],
306
316
  endianAbility: (baseType & 128) === 128,
307
317
  littleEndian: lEnd,
308
- baseTypeNo: baseType & 15,
318
+ baseTypeNo: baseType,
309
319
  name: field,
310
320
  dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
311
321
  };
@@ -327,7 +337,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
327
337
  size,
328
338
  endianAbility: (baseType & 128) === 128,
329
339
  littleEndian: lEnd,
330
- baseTypeNo: baseType & 15,
340
+ baseTypeNo: baseType,
331
341
  name: devDef.field_name,
332
342
  dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
333
343
  scale: devDef.scale || 1,
@@ -363,7 +373,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
363
373
  for (let i = 0; i < messageType.fieldDefs.length; i++) {
364
374
  const fDef = messageType.fieldDefs[i];
365
375
  const data = readData(blob, fDef, readDataFromIndex, options);
366
- if (!isInvalidValue(data, fDef.type)) {
376
+ if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
367
377
  rawFields.push({ fDef, data });
368
378
  }
369
379
  readDataFromIndex += fDef.size;
@@ -379,8 +389,8 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
379
389
  if (fDef.isDeveloperField) {
380
390
  const field = fDef.name;
381
391
  const { type } = fDef;
382
- const { scale } = fDef;
383
- const { offset } = fDef;
392
+ const scale = (_a = fDef.scale) !== null && _a !== void 0 ? _a : null;
393
+ const offset = (_b = fDef.offset) !== null && _b !== void 0 ? _b : 0;
384
394
  fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
385
395
  }
386
396
  else {
@@ -104,6 +104,7 @@ class FitParser {
104
104
  const jumps = [];
105
105
  const time_in_zone = [];
106
106
  const activity_metrics = [];
107
+ const user_metrics = [];
107
108
  let loopIndex = headerLength;
108
109
  const messageTypes = [];
109
110
  const developerFields = [];
@@ -208,6 +209,9 @@ class FitParser {
208
209
  case 'activity_metrics':
209
210
  activity_metrics.push(message);
210
211
  break;
212
+ case 'user_metrics':
213
+ user_metrics.push(message);
214
+ break;
211
215
  default:
212
216
  if (messageType !== '') {
213
217
  fitObj[messageType] = message;
@@ -230,6 +234,7 @@ class FitParser {
230
234
  fitObj.jumps = jumps;
231
235
  fitObj.time_in_zone = time_in_zone;
232
236
  fitObj.activity_metrics = activity_metrics;
237
+ fitObj.user_metrics = user_metrics;
233
238
  if (isCascadeNeeded) {
234
239
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'records', records);
235
240
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'lengths', lengths);
package/dist/cjs/fit.js CHANGED
@@ -4134,6 +4134,109 @@ exports.FIT = {
4134
4134
  units: 'counts',
4135
4135
  },
4136
4136
  },
4137
+ // Undocumented Garmin user metrics message. Observed in activity FIT files exported
4138
+ // from Garmin Connect and displayed by fitfileviewer as "User Metrics".
4139
+ 79: {
4140
+ name: 'user_metrics',
4141
+ 253: {
4142
+ field: 'timestamp',
4143
+ type: 'date_time',
4144
+ scale: null,
4145
+ offset: 0,
4146
+ units: '',
4147
+ },
4148
+ 0: {
4149
+ field: 'vo2_max',
4150
+ type: 'uint16',
4151
+ scale: 1024 / 3.5,
4152
+ offset: 0,
4153
+ units: 'ml/kg/min',
4154
+ },
4155
+ 1: {
4156
+ field: 'age',
4157
+ type: 'uint8',
4158
+ scale: null,
4159
+ offset: 0,
4160
+ units: 'years',
4161
+ },
4162
+ 2: {
4163
+ field: 'height',
4164
+ type: 'uint8',
4165
+ scale: 100,
4166
+ offset: 0,
4167
+ units: 'm',
4168
+ },
4169
+ 3: {
4170
+ field: 'weight',
4171
+ type: 'uint16',
4172
+ scale: 10,
4173
+ offset: 0,
4174
+ units: 'kg',
4175
+ },
4176
+ 4: {
4177
+ field: 'gender',
4178
+ type: 'gender',
4179
+ scale: null,
4180
+ offset: 0,
4181
+ units: '',
4182
+ },
4183
+ 6: {
4184
+ field: 'max_heart_rate',
4185
+ type: 'uint8',
4186
+ scale: null,
4187
+ offset: 0,
4188
+ units: 'bpm',
4189
+ },
4190
+ 8: {
4191
+ field: 'remaining_recovery_time',
4192
+ type: 'uint16',
4193
+ scale: null,
4194
+ offset: 0,
4195
+ units: '',
4196
+ },
4197
+ 11: {
4198
+ field: 'lthr',
4199
+ type: 'uint16',
4200
+ scale: null,
4201
+ offset: 0,
4202
+ units: 'bpm',
4203
+ },
4204
+ 12: {
4205
+ field: 'ltpower',
4206
+ type: 'uint16',
4207
+ scale: null,
4208
+ offset: 0,
4209
+ units: 'watts',
4210
+ },
4211
+ 13: {
4212
+ field: 'ltspeed',
4213
+ type: 'uint16',
4214
+ scale: 1000,
4215
+ offset: 0,
4216
+ units: 'm/s',
4217
+ },
4218
+ 16: {
4219
+ field: 'start_of_activity',
4220
+ type: 'date_time',
4221
+ scale: null,
4222
+ offset: 0,
4223
+ units: '',
4224
+ },
4225
+ 19: {
4226
+ field: 'first_vo2_max',
4227
+ type: 'uint32',
4228
+ scale: 65536 / 3.5,
4229
+ offset: 0,
4230
+ units: 'ml/kg/min',
4231
+ },
4232
+ 35: {
4233
+ field: 'end_of_previous_activity',
4234
+ type: 'date_time',
4235
+ scale: null,
4236
+ offset: 0,
4237
+ units: '',
4238
+ },
4239
+ },
4137
4240
  103: {
4138
4241
  name: 'monitoring_info',
4139
4242
  253: {
@@ -4930,6 +5033,7 @@ exports.FIT = {
4930
5033
  55: 'monitoring',
4931
5034
  72: 'training_file',
4932
5035
  78: 'hrv',
5036
+ 79: 'user_metrics',
4933
5037
  80: 'ant_rx',
4934
5038
  81: 'ant_tx',
4935
5039
  82: 'ant_channel_id',
@@ -16,7 +16,7 @@ export interface MessageIndex {
16
16
  selected: boolean;
17
17
  }
18
18
  export type File = 'device' | 'settings' | 'sport' | 'activity' | 'workout' | 'course' | 'schedules' | 'weight' | 'totals' | 'goals' | 'blood_pressure' | 'monitoring_a' | 'activity_summary' | 'monitoring_daily' | 'monitoring_b' | 'segment' | 'segment_list' | 'exd_configuration' | 'mfg_range_min' | 'mfg_range_max';
19
- export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
19
+ export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
20
20
  export type Checksum = 'clear' | 'ok';
21
21
  export type FileFlags = '0' | 'read' | 'write' | 'erase';
22
22
  export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type';
@@ -841,6 +841,22 @@ export interface ParsedMonitoring {
841
841
  export interface ParsedHrv {
842
842
  time?: number[];
843
843
  }
844
+ export interface ParsedUserMetrics {
845
+ vo2_max?: number;
846
+ age?: number;
847
+ height?: number;
848
+ weight?: number;
849
+ gender?: Gender;
850
+ max_heart_rate?: number;
851
+ remaining_recovery_time?: number;
852
+ lthr?: number;
853
+ ltpower?: number;
854
+ ltspeed?: number;
855
+ start_of_activity?: string;
856
+ first_vo2_max?: number;
857
+ end_of_previous_activity?: string;
858
+ timestamp: string;
859
+ }
844
860
  export interface ParsedLength {
845
861
  event?: Event;
846
862
  event_type?: EventType;
@@ -1040,4 +1056,5 @@ export interface ParsedFit {
1040
1056
  jumps?: ParsedJump[];
1041
1057
  time_in_zone?: ParsedTimeInZone[];
1042
1058
  activity_metrics?: ParsedActivityMetrics[];
1059
+ user_metrics?: ParsedUserMetrics[];
1043
1060
  }
@@ -211,6 +211,7 @@ function generateFitType() {
211
211
  jumps: 'parsed_jump',
212
212
  time_in_zone: 'parsed_time_in_zone',
213
213
  activity_metrics: 'parsed_activity_metrics',
214
+ user_metrics: 'parsed_user_metrics',
214
215
  };
215
216
  const referenceProperties = {
216
217
  file_creator: 'parsed_file_creator',
@@ -102,6 +102,7 @@ export default class FitParser {
102
102
  const jumps = [];
103
103
  const time_in_zone = [];
104
104
  const activity_metrics = [];
105
+ const user_metrics = [];
105
106
  let loopIndex = headerLength;
106
107
  const messageTypes = [];
107
108
  const developerFields = [];
@@ -206,6 +207,9 @@ export default class FitParser {
206
207
  case 'activity_metrics':
207
208
  activity_metrics.push(message);
208
209
  break;
210
+ case 'user_metrics':
211
+ user_metrics.push(message);
212
+ break;
209
213
  default:
210
214
  if (messageType !== '') {
211
215
  fitObj[messageType] = message;
@@ -228,6 +232,7 @@ export default class FitParser {
228
232
  fitObj.jumps = jumps;
229
233
  fitObj.time_in_zone = time_in_zone;
230
234
  fitObj.activity_metrics = activity_metrics;
235
+ fitObj.user_metrics = user_metrics;
231
236
  if (isCascadeNeeded) {
232
237
  laps = mapDataIntoLap(laps, 'records', records);
233
238
  laps = mapDataIntoLap(laps, 'lengths', lengths);
package/dist/fit.js CHANGED
@@ -4131,6 +4131,109 @@ export const FIT = {
4131
4131
  units: 'counts',
4132
4132
  },
4133
4133
  },
4134
+ // Undocumented Garmin user metrics message. Observed in activity FIT files exported
4135
+ // from Garmin Connect and displayed by fitfileviewer as "User Metrics".
4136
+ 79: {
4137
+ name: 'user_metrics',
4138
+ 253: {
4139
+ field: 'timestamp',
4140
+ type: 'date_time',
4141
+ scale: null,
4142
+ offset: 0,
4143
+ units: '',
4144
+ },
4145
+ 0: {
4146
+ field: 'vo2_max',
4147
+ type: 'uint16',
4148
+ scale: 1024 / 3.5,
4149
+ offset: 0,
4150
+ units: 'ml/kg/min',
4151
+ },
4152
+ 1: {
4153
+ field: 'age',
4154
+ type: 'uint8',
4155
+ scale: null,
4156
+ offset: 0,
4157
+ units: 'years',
4158
+ },
4159
+ 2: {
4160
+ field: 'height',
4161
+ type: 'uint8',
4162
+ scale: 100,
4163
+ offset: 0,
4164
+ units: 'm',
4165
+ },
4166
+ 3: {
4167
+ field: 'weight',
4168
+ type: 'uint16',
4169
+ scale: 10,
4170
+ offset: 0,
4171
+ units: 'kg',
4172
+ },
4173
+ 4: {
4174
+ field: 'gender',
4175
+ type: 'gender',
4176
+ scale: null,
4177
+ offset: 0,
4178
+ units: '',
4179
+ },
4180
+ 6: {
4181
+ field: 'max_heart_rate',
4182
+ type: 'uint8',
4183
+ scale: null,
4184
+ offset: 0,
4185
+ units: 'bpm',
4186
+ },
4187
+ 8: {
4188
+ field: 'remaining_recovery_time',
4189
+ type: 'uint16',
4190
+ scale: null,
4191
+ offset: 0,
4192
+ units: '',
4193
+ },
4194
+ 11: {
4195
+ field: 'lthr',
4196
+ type: 'uint16',
4197
+ scale: null,
4198
+ offset: 0,
4199
+ units: 'bpm',
4200
+ },
4201
+ 12: {
4202
+ field: 'ltpower',
4203
+ type: 'uint16',
4204
+ scale: null,
4205
+ offset: 0,
4206
+ units: 'watts',
4207
+ },
4208
+ 13: {
4209
+ field: 'ltspeed',
4210
+ type: 'uint16',
4211
+ scale: 1000,
4212
+ offset: 0,
4213
+ units: 'm/s',
4214
+ },
4215
+ 16: {
4216
+ field: 'start_of_activity',
4217
+ type: 'date_time',
4218
+ scale: null,
4219
+ offset: 0,
4220
+ units: '',
4221
+ },
4222
+ 19: {
4223
+ field: 'first_vo2_max',
4224
+ type: 'uint32',
4225
+ scale: 65536 / 3.5,
4226
+ offset: 0,
4227
+ units: 'ml/kg/min',
4228
+ },
4229
+ 35: {
4230
+ field: 'end_of_previous_activity',
4231
+ type: 'date_time',
4232
+ scale: null,
4233
+ offset: 0,
4234
+ units: '',
4235
+ },
4236
+ },
4134
4237
  103: {
4135
4238
  name: 'monitoring_info',
4136
4239
  253: {
@@ -4927,6 +5030,7 @@ export const FIT = {
4927
5030
  55: 'monitoring',
4928
5031
  72: 'training_file',
4929
5032
  78: 'hrv',
5033
+ 79: 'user_metrics',
4930
5034
  80: 'ant_rx',
4931
5035
  81: 'ant_tx',
4932
5036
  82: 'ant_channel_id',
@@ -16,7 +16,7 @@ export interface MessageIndex {
16
16
  selected: boolean;
17
17
  }
18
18
  export type File = 'device' | 'settings' | 'sport' | 'activity' | 'workout' | 'course' | 'schedules' | 'weight' | 'totals' | 'goals' | 'blood_pressure' | 'monitoring_a' | 'activity_summary' | 'monitoring_daily' | 'monitoring_b' | 'segment' | 'segment_list' | 'exd_configuration' | 'mfg_range_min' | 'mfg_range_max';
19
- export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
19
+ export type MesgNum = 'file_id' | 'capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'zones_target' | 'hr_zone' | 'power_zone' | 'met_zone' | 'sport' | 'goal' | 'session' | 'lap' | 'record' | 'event' | 'device_info' | 'workout' | 'workout_step' | 'schedule' | 'weight_scale' | 'course' | 'course_point' | 'totals' | 'activity' | 'software' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'file_creator' | 'blood_pressure' | 'speed_zone' | 'monitoring' | 'training_file' | 'hrv' | 'user_metrics' | 'ant_rx' | 'ant_tx' | 'ant_channel_id' | 'length' | 'monitoring_info' | 'pad' | 'slave_device' | 'connectivity' | 'weather_conditions' | 'weather_alert' | 'cadence_zone' | 'hr' | 'activity_metrics' | 'segment_lap' | 'memo_glob' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_file' | 'workout_session' | 'watchface_settings' | 'gps_metadata' | 'camera_event' | 'timestamp_correlation' | 'gyroscope_data' | 'accelerometer_data' | 'three_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'time_in_zone' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'o_hr_settings' | 'mfg_range_min' | 'mfg_range_max' | 'definition';
20
20
  export type Checksum = 'clear' | 'ok';
21
21
  export type FileFlags = '0' | 'read' | 'write' | 'erase';
22
22
  export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type';
@@ -841,6 +841,22 @@ export interface ParsedMonitoring {
841
841
  export interface ParsedHrv {
842
842
  time?: number[];
843
843
  }
844
+ export interface ParsedUserMetrics {
845
+ vo2_max?: number;
846
+ age?: number;
847
+ height?: number;
848
+ weight?: number;
849
+ gender?: Gender;
850
+ max_heart_rate?: number;
851
+ remaining_recovery_time?: number;
852
+ lthr?: number;
853
+ ltpower?: number;
854
+ ltspeed?: number;
855
+ start_of_activity?: string;
856
+ first_vo2_max?: number;
857
+ end_of_previous_activity?: string;
858
+ timestamp: string;
859
+ }
844
860
  export interface ParsedLength {
845
861
  event?: Event;
846
862
  event_type?: EventType;
@@ -1040,4 +1056,5 @@ export interface ParsedFit {
1040
1056
  jumps?: ParsedJump[];
1041
1057
  time_in_zone?: ParsedTimeInZone[];
1042
1058
  activity_metrics?: ParsedActivityMetrics[];
1059
+ user_metrics?: ParsedUserMetrics[];
1043
1060
  }
@@ -191,6 +191,7 @@ export function generateFitType() {
191
191
  jumps: 'parsed_jump',
192
192
  time_in_zone: 'parsed_time_in_zone',
193
193
  activity_metrics: 'parsed_activity_metrics',
194
+ user_metrics: 'parsed_user_metrics',
194
195
  };
195
196
  const referenceProperties = {
196
197
  file_creator: 'parsed_file_creator',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fit-file-parser",
3
3
  "type": "module",
4
- "version": "2.3.2",
4
+ "version": "3.0.0",
5
5
  "private": false,
6
6
  "description": "Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)",
7
7
  "author": {