fit-file-parser 2.3.3 → 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
@@ -190,6 +190,13 @@ function isInvalidValue(data, type) {
190
190
  return false;
191
191
  }
192
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
+ }
193
200
  function convertTo(data, unitsList, unitName) {
194
201
  const options = FIT.options[unitsList];
195
202
  const unit = options[unitName];
@@ -266,6 +273,7 @@ function applyOptions(data, field, options, fields) {
266
273
  }
267
274
  }
268
275
  export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
276
+ var _a, _b;
269
277
  const recordHeader = blob[startIndex];
270
278
  let localMessageType = recordHeader & 15;
271
279
  if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
@@ -301,7 +309,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
301
309
  size: blob[fDefIndex + 1],
302
310
  endianAbility: (baseType & 128) === 128,
303
311
  littleEndian: lEnd,
304
- baseTypeNo: baseType & 15,
312
+ baseTypeNo: baseType,
305
313
  name: field,
306
314
  dataType: getFitMessageBaseType(baseType & 15),
307
315
  };
@@ -323,7 +331,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
323
331
  size,
324
332
  endianAbility: (baseType & 128) === 128,
325
333
  littleEndian: lEnd,
326
- baseTypeNo: baseType & 15,
334
+ baseTypeNo: baseType,
327
335
  name: devDef.field_name,
328
336
  dataType: getFitMessageBaseType(baseType & 15),
329
337
  scale: devDef.scale || 1,
@@ -359,7 +367,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
359
367
  for (let i = 0; i < messageType.fieldDefs.length; i++) {
360
368
  const fDef = messageType.fieldDefs[i];
361
369
  const data = readData(blob, fDef, readDataFromIndex, options);
362
- if (!isInvalidValue(data, fDef.type)) {
370
+ if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
363
371
  rawFields.push({ fDef, data });
364
372
  }
365
373
  readDataFromIndex += fDef.size;
@@ -375,8 +383,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
375
383
  if (fDef.isDeveloperField) {
376
384
  const field = fDef.name;
377
385
  const { type } = fDef;
378
- const { scale } = fDef;
379
- 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;
380
388
  fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
381
389
  }
382
390
  else {
@@ -196,6 +196,13 @@ function isInvalidValue(data, type) {
196
196
  return false;
197
197
  }
198
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
+ }
199
206
  function convertTo(data, unitsList, unitName) {
200
207
  const options = fit_js_1.FIT.options[unitsList];
201
208
  const unit = options[unitName];
@@ -272,6 +279,7 @@ function applyOptions(data, field, options, fields) {
272
279
  }
273
280
  }
274
281
  function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
282
+ var _a, _b;
275
283
  const recordHeader = blob[startIndex];
276
284
  let localMessageType = recordHeader & 15;
277
285
  if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
@@ -307,7 +315,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
307
315
  size: blob[fDefIndex + 1],
308
316
  endianAbility: (baseType & 128) === 128,
309
317
  littleEndian: lEnd,
310
- baseTypeNo: baseType & 15,
318
+ baseTypeNo: baseType,
311
319
  name: field,
312
320
  dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
313
321
  };
@@ -329,7 +337,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
329
337
  size,
330
338
  endianAbility: (baseType & 128) === 128,
331
339
  littleEndian: lEnd,
332
- baseTypeNo: baseType & 15,
340
+ baseTypeNo: baseType,
333
341
  name: devDef.field_name,
334
342
  dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
335
343
  scale: devDef.scale || 1,
@@ -365,7 +373,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
365
373
  for (let i = 0; i < messageType.fieldDefs.length; i++) {
366
374
  const fDef = messageType.fieldDefs[i];
367
375
  const data = readData(blob, fDef, readDataFromIndex, options);
368
- if (!isInvalidValue(data, fDef.type)) {
376
+ if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
369
377
  rawFields.push({ fDef, data });
370
378
  }
371
379
  readDataFromIndex += fDef.size;
@@ -381,8 +389,8 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
381
389
  if (fDef.isDeveloperField) {
382
390
  const field = fDef.name;
383
391
  const { type } = fDef;
384
- const { scale } = fDef;
385
- 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;
386
394
  fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
387
395
  }
388
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.3",
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": {