fit-file-parser 2.2.5 → 2.3.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
@@ -187,8 +187,23 @@ function convertTo(data, unitsList, unitName) {
187
187
  const unit = options[unitName];
188
188
  return unit ? data * unit.multiplier + unit.offset : data;
189
189
  }
190
- function applyOptions(data, field, options) {
190
+ function applyOptions(data, field, options, fields) {
191
191
  switch (field) {
192
+ case 'device_type': {
193
+ const isLocal = fields.source_type === 'local' || fields.source_type === 5;
194
+ const isBLE = fields.source_type === 'bluetooth_low_energy' || fields.source_type === 3 || fields.source_type === 'bluetooth' || fields.source_type === 2;
195
+ const isANT = fields.source_type === 'antplus' || fields.source_type === 1 || fields.source_type === 'ant' || fields.source_type === 0;
196
+ if (isLocal) {
197
+ return FIT.types.local_device_type[data] || data;
198
+ }
199
+ if (isBLE) {
200
+ return FIT.types.ble_device_type[data] || data;
201
+ }
202
+ if (isANT) {
203
+ return FIT.types.antplus_device_type[data] || data;
204
+ }
205
+ return data;
206
+ }
192
207
  case 'speed':
193
208
  case 'enhanced_speed':
194
209
  case 'vertical_speed':
@@ -332,31 +347,41 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
332
347
  let readDataFromIndex = startIndex + 1;
333
348
  const fields = {};
334
349
  const message = getFitMessage(messageType.globalMessageNumber);
350
+ const rawFields = [];
335
351
  for (let i = 0; i < messageType.fieldDefs.length; i++) {
336
352
  const fDef = messageType.fieldDefs[i];
337
353
  const data = readData(blob, fDef, readDataFromIndex, options);
338
354
  if (!isInvalidValue(data, fDef.type)) {
339
- if (fDef.isDeveloperField) {
340
- const field = fDef.name;
341
- const { type } = fDef;
342
- const { scale } = fDef;
343
- const { offset } = fDef;
344
- fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options);
345
- }
346
- else {
347
- const { field, type, scale, offset } = message.getAttributes(fDef.fDefNo);
348
- if (field !== 'unknown' && field !== '' && field !== undefined) {
349
- fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options);
350
- }
351
- }
352
- if (message.name === 'record' && options.elapsedRecordField) {
353
- fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
354
- fields.timer_time = fields.elapsed_time - pausedTime;
355
- }
355
+ rawFields.push({ fDef, data });
356
356
  }
357
357
  readDataFromIndex += fDef.size;
358
358
  messageSize += fDef.size;
359
359
  }
360
+ for (const { fDef, data } of rawFields) {
361
+ const { field } = fDef.isDeveloperField ? { field: fDef.name } : message.getAttributes(fDef.fDefNo);
362
+ if (field !== 'unknown' && field !== '' && field !== undefined) {
363
+ fields[field] = data;
364
+ }
365
+ }
366
+ for (const { fDef, data } of rawFields) {
367
+ if (fDef.isDeveloperField) {
368
+ const field = fDef.name;
369
+ const { type } = fDef;
370
+ const { scale } = fDef;
371
+ const { offset } = fDef;
372
+ fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
373
+ }
374
+ else {
375
+ const { field, type, scale, offset } = message.getAttributes(fDef.fDefNo);
376
+ if (field !== 'unknown' && field !== '' && field !== undefined) {
377
+ fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
378
+ }
379
+ }
380
+ if (message.name === 'record' && options.elapsedRecordField) {
381
+ fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
382
+ fields.timer_time = fields.elapsed_time - pausedTime;
383
+ }
384
+ }
360
385
  if (message.name === 'field_description') {
361
386
  developerFields[fields.developer_data_index]
362
387
  = developerFields[fields.developer_data_index] || [];
@@ -193,8 +193,23 @@ function convertTo(data, unitsList, unitName) {
193
193
  const unit = options[unitName];
194
194
  return unit ? data * unit.multiplier + unit.offset : data;
195
195
  }
196
- function applyOptions(data, field, options) {
196
+ function applyOptions(data, field, options, fields) {
197
197
  switch (field) {
198
+ case 'device_type': {
199
+ const isLocal = fields.source_type === 'local' || fields.source_type === 5;
200
+ const isBLE = fields.source_type === 'bluetooth_low_energy' || fields.source_type === 3 || fields.source_type === 'bluetooth' || fields.source_type === 2;
201
+ const isANT = fields.source_type === 'antplus' || fields.source_type === 1 || fields.source_type === 'ant' || fields.source_type === 0;
202
+ if (isLocal) {
203
+ return fit_js_1.FIT.types.local_device_type[data] || data;
204
+ }
205
+ if (isBLE) {
206
+ return fit_js_1.FIT.types.ble_device_type[data] || data;
207
+ }
208
+ if (isANT) {
209
+ return fit_js_1.FIT.types.antplus_device_type[data] || data;
210
+ }
211
+ return data;
212
+ }
198
213
  case 'speed':
199
214
  case 'enhanced_speed':
200
215
  case 'vertical_speed':
@@ -338,31 +353,41 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
338
353
  let readDataFromIndex = startIndex + 1;
339
354
  const fields = {};
340
355
  const message = (0, messages_js_1.getFitMessage)(messageType.globalMessageNumber);
356
+ const rawFields = [];
341
357
  for (let i = 0; i < messageType.fieldDefs.length; i++) {
342
358
  const fDef = messageType.fieldDefs[i];
343
359
  const data = readData(blob, fDef, readDataFromIndex, options);
344
360
  if (!isInvalidValue(data, fDef.type)) {
345
- if (fDef.isDeveloperField) {
346
- const field = fDef.name;
347
- const { type } = fDef;
348
- const { scale } = fDef;
349
- const { offset } = fDef;
350
- fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options);
351
- }
352
- else {
353
- const { field, type, scale, offset } = message.getAttributes(fDef.fDefNo);
354
- if (field !== 'unknown' && field !== '' && field !== undefined) {
355
- fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options);
356
- }
357
- }
358
- if (message.name === 'record' && options.elapsedRecordField) {
359
- fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
360
- fields.timer_time = fields.elapsed_time - pausedTime;
361
- }
361
+ rawFields.push({ fDef, data });
362
362
  }
363
363
  readDataFromIndex += fDef.size;
364
364
  messageSize += fDef.size;
365
365
  }
366
+ for (const { fDef, data } of rawFields) {
367
+ const { field } = fDef.isDeveloperField ? { field: fDef.name } : message.getAttributes(fDef.fDefNo);
368
+ if (field !== 'unknown' && field !== '' && field !== undefined) {
369
+ fields[field] = data;
370
+ }
371
+ }
372
+ for (const { fDef, data } of rawFields) {
373
+ if (fDef.isDeveloperField) {
374
+ const field = fDef.name;
375
+ const { type } = fDef;
376
+ const { scale } = fDef;
377
+ const { offset } = fDef;
378
+ fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
379
+ }
380
+ else {
381
+ const { field, type, scale, offset } = message.getAttributes(fDef.fDefNo);
382
+ if (field !== 'unknown' && field !== '' && field !== undefined) {
383
+ fields[field] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
384
+ }
385
+ }
386
+ if (message.name === 'record' && options.elapsedRecordField) {
387
+ fields.elapsed_time = (fields.timestamp - (startDate || 0)) / 1000;
388
+ fields.timer_time = fields.elapsed_time - pausedTime;
389
+ }
390
+ }
366
391
  if (message.name === 'field_description') {
367
392
  developerFields[fields.developer_data_index]
368
393
  = developerFields[fields.developer_data_index] || [];
@@ -103,6 +103,7 @@ class FitParser {
103
103
  const tank_summaries = [];
104
104
  const jumps = [];
105
105
  const time_in_zone = [];
106
+ const activity_metrics = [];
106
107
  let loopIndex = headerLength;
107
108
  const messageTypes = [];
108
109
  const developerFields = [];
@@ -204,6 +205,9 @@ class FitParser {
204
205
  case 'time_in_zone':
205
206
  time_in_zone.push(message);
206
207
  break;
208
+ case 'activity_metrics':
209
+ activity_metrics.push(message);
210
+ break;
207
211
  default:
208
212
  if (messageType !== '') {
209
213
  fitObj[messageType] = message;
@@ -225,6 +229,7 @@ class FitParser {
225
229
  fitObj.tank_summaries = tank_summaries;
226
230
  fitObj.jumps = jumps;
227
231
  fitObj.time_in_zone = time_in_zone;
232
+ fitObj.activity_metrics = activity_metrics;
228
233
  if (isCascadeNeeded) {
229
234
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'records', records);
230
235
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'lengths', lengths);
package/dist/cjs/fit.js CHANGED
@@ -3106,7 +3106,7 @@ exports.FIT = {
3106
3106
  },
3107
3107
  1: {
3108
3108
  field: 'device_type',
3109
- type: 'antplus_device_type',
3109
+ type: 'uint8',
3110
3110
  scale: null,
3111
3111
  offset: 0,
3112
3112
  units: '',
@@ -4190,6 +4190,93 @@ exports.FIT = {
4190
4190
  },
4191
4191
  0: { field: 'enabled', type: 'byte', scale: null, offset: 0, units: '' },
4192
4192
  },
4193
+ 140: {
4194
+ name: 'activity_metrics',
4195
+ 1: {
4196
+ field: 'new_max_heart_rate',
4197
+ type: 'uint8',
4198
+ scale: null,
4199
+ offset: 0,
4200
+ units: 'bpm',
4201
+ },
4202
+ 4: {
4203
+ field: 'aerobic_training_effect',
4204
+ type: 'uint8',
4205
+ scale: 10,
4206
+ offset: 0,
4207
+ units: '',
4208
+ },
4209
+ 7: {
4210
+ field: 'vo2_max',
4211
+ type: 'uint32',
4212
+ scale: 65536 / 3.5,
4213
+ offset: 0,
4214
+ units: 'ml/kg/min',
4215
+ },
4216
+ 9: {
4217
+ field: 'recovery_time',
4218
+ type: 'uint16',
4219
+ scale: null,
4220
+ offset: 0,
4221
+ units: 'min',
4222
+ },
4223
+ 11: {
4224
+ field: 'sport',
4225
+ type: 'sport',
4226
+ scale: null,
4227
+ offset: 0,
4228
+ units: '',
4229
+ },
4230
+ 20: {
4231
+ field: 'anaerobic_training_effect',
4232
+ type: 'uint8',
4233
+ scale: 10,
4234
+ offset: 0,
4235
+ units: '',
4236
+ },
4237
+ 29: {
4238
+ field: 'first_vo2_max',
4239
+ type: 'uint32',
4240
+ scale: 65536 / 3.5,
4241
+ offset: 0,
4242
+ units: 'ml/kg/min',
4243
+ },
4244
+ 41: {
4245
+ field: 'primary_benefit',
4246
+ type: 'uint8',
4247
+ scale: null,
4248
+ offset: 0,
4249
+ units: '',
4250
+ },
4251
+ 60: {
4252
+ field: 'total_ascent',
4253
+ type: 'uint16',
4254
+ scale: null,
4255
+ offset: 0,
4256
+ units: 'm',
4257
+ },
4258
+ 61: {
4259
+ field: 'total_descent',
4260
+ type: 'uint16',
4261
+ scale: null,
4262
+ offset: 0,
4263
+ units: 'm',
4264
+ },
4265
+ 62: {
4266
+ field: 'avg_power',
4267
+ type: 'uint16',
4268
+ scale: null,
4269
+ offset: 0,
4270
+ units: 'watts',
4271
+ },
4272
+ 63: {
4273
+ field: 'avg_heart_rate',
4274
+ type: 'uint8',
4275
+ scale: null,
4276
+ offset: 0,
4277
+ units: 'bpm',
4278
+ },
4279
+ },
4193
4280
  206: {
4194
4281
  name: 'field_description',
4195
4282
  0: {
@@ -4705,11 +4792,11 @@ exports.FIT = {
4705
4792
  units: 's',
4706
4793
  },
4707
4794
  5: {
4708
- field: 'hr_zone_high_boundary_deprecated',
4709
- type: 'uint8_array',
4710
- scale: null,
4795
+ field: 'time_in_power_zone',
4796
+ type: 'uint32_array',
4797
+ scale: 1000,
4711
4798
  offset: 0,
4712
- units: '',
4799
+ units: 's',
4713
4800
  },
4714
4801
  6: {
4715
4802
  field: 'hr_zone_high_boundary',
@@ -4733,11 +4820,11 @@ exports.FIT = {
4733
4820
  units: 'watts',
4734
4821
  },
4735
4822
  9: {
4736
- field: 'hr_calc_type',
4737
- type: 'hr_zone_calc',
4823
+ field: 'power_zone_high_boundary',
4824
+ type: 'uint16_array',
4738
4825
  scale: null,
4739
4826
  offset: 0,
4740
- units: '',
4827
+ units: 'watts',
4741
4828
  },
4742
4829
  10: {
4743
4830
  field: 'max_heart_rate_deprecated',
@@ -4855,6 +4942,7 @@ exports.FIT = {
4855
4942
  129: 'weather_alert',
4856
4943
  131: 'cadence_zone',
4857
4944
  132: 'hr',
4945
+ 140: 'activity_metrics',
4858
4946
  142: 'segment_lap',
4859
4947
  145: 'memo_glob',
4860
4948
  148: 'segment_id',
@@ -5978,7 +6066,6 @@ exports.FIT = {
5978
6066
  65534: 'connect',
5979
6067
  },
5980
6068
  antplus_device_type: {
5981
- 0: 0,
5982
6069
  1: 'antfs',
5983
6070
  11: 'bike_power',
5984
6071
  12: 'environment_sensor_legacy',
@@ -5992,10 +6079,12 @@ exports.FIT = {
5992
6079
  26: 'racquet',
5993
6080
  27: 'control_hub',
5994
6081
  31: 'muscle_oxygen',
6082
+ 34: 'shifting',
5995
6083
  35: 'bike_light_main',
5996
6084
  36: 'bike_light_shared',
5997
6085
  38: 'exd',
5998
6086
  40: 'bike_radar',
6087
+ 46: 'bike_aero',
5999
6088
  119: 'weight_scale',
6000
6089
  120: 'heart_rate',
6001
6090
  121: 'bike_speed_cadence',
@@ -6003,6 +6092,26 @@ exports.FIT = {
6003
6092
  123: 'bike_speed',
6004
6093
  124: 'stride_speed_distance',
6005
6094
  },
6095
+ local_device_type: {
6096
+ 0: 'gps',
6097
+ 1: 'glonass',
6098
+ 2: 'gps_glonass',
6099
+ 3: 'accelerometer',
6100
+ 4: 'barometer',
6101
+ 5: 'temperature',
6102
+ 10: 'whr',
6103
+ 12: 'sensor_hub',
6104
+ },
6105
+ ble_device_type: {
6106
+ 0: 'connected_gps',
6107
+ 1: 'heart_rate',
6108
+ 2: 'bike_power',
6109
+ 3: 'bike_speed_cadence',
6110
+ 4: 'bike_speed',
6111
+ 5: 'bike_cadence',
6112
+ 6: 'footpod',
6113
+ 7: 'bike_trainer',
6114
+ },
6006
6115
  ant_network: {
6007
6116
  0: 'public',
6008
6117
  1: 'antplus',
@@ -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' | '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' | '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';
@@ -72,7 +72,9 @@ export type Schedule = 'workout' | 'course';
72
72
  export type CoursePoint = 'generic' | 'summit' | 'valley' | 'water' | 'food' | 'danger' | 'left' | 'right' | 'straight' | 'first_aid' | 'fourth_category' | 'third_category' | 'second_category' | 'first_category' | 'hors_category' | 'sprint' | 'left_fork' | 'right_fork' | 'middle_fork' | 'slight_left' | 'sharp_left' | 'slight_right' | 'sharp_right' | 'u_turn' | 'segment_start' | 'segment_end';
73
73
  export type Manufacturer = '0' | 'garmin' | 'garmin_fr405_antfs' | 'zephyr' | 'dayton' | 'idt' | 'srm' | 'quarq' | 'ibike' | 'saris' | 'spark_hk' | 'tanita' | 'echowell' | 'dynastream_oem' | 'nautilus' | 'dynastream' | 'timex' | 'metrigear' | 'xelic' | 'beurer' | 'cardiosport' | 'a_and_d' | 'hmm' | 'suunto' | 'thita_elektronik' | 'gpulse' | 'clean_mobile' | 'pedal_brain' | 'peaksware' | 'saxonar' | 'lemond_fitness' | 'dexcom' | 'wahoo_fitness' | 'octane_fitness' | 'archinoetics' | 'the_hurt_box' | 'citizen_systems' | 'magellan' | 'osynce' | 'holux' | 'concept2' | 'one_giant_leap' | 'ace_sensor' | 'brim_brothers' | 'xplova' | 'perception_digital' | 'bf1systems' | 'pioneer' | 'spantec' | 'metalogics' | '4iiiis' | 'seiko_epson' | 'seiko_epson_oem' | 'ifor_powell' | 'maxwell_guider' | 'star_trac' | 'breakaway' | 'alatech_technology_ltd' | 'mio_technology_europe' | 'rotor' | 'geonaute' | 'id_bike' | 'specialized' | 'wtek' | 'physical_enterprises' | 'north_pole_engineering' | 'bkool' | 'cateye' | 'stages_cycling' | 'sigmasport' | 'tomtom' | 'peripedal' | 'wattbike' | 'moxy' | 'ciclosport' | 'powerbahn' | 'acorn_projects_aps' | 'lifebeam' | 'bontrager' | 'wellgo' | 'scosche' | 'magura' | 'woodway' | 'elite' | 'nielsen_kellerman' | 'dk_city' | 'tacx' | 'direction_technology' | 'magtonic' | '1partcarbon' | 'inside_ride_technologies' | 'sound_of_motion' | 'stryd' | 'icg' | 'mipulse' | 'bsx_athletics' | 'look' | 'campagnolo_srl' | 'body_bike_smart' | 'praxisworks' | 'limits_technology' | 'topaction_technology' | 'cosinuss' | 'fitcare' | 'magene' | 'giant_manufacturing_co' | 'tigrasport' | 'salutron' | 'technogym' | 'bryton_sensors' | 'latitude_limited' | 'soaring_technology' | 'igpsport' | 'thinkrider' | 'gopher_sport' | 'waterrower' | 'orangetheory' | 'inpeak' | 'kinetic' | 'johnson_health_tech' | 'polar_electro' | 'seesense' | 'nci_technology' | 'development' | 'healthandlife' | 'lezyne' | 'scribe_labs' | 'zwift' | 'watteam' | 'recon' | 'favero_electronics' | 'dynovelo' | 'strava' | 'precor' | 'bryton' | 'sram' | 'navman' | 'cobi' | 'spivi' | 'mio_magellan' | 'evesports' | 'sensitivus_gauge' | 'podoon' | 'life_time_fitness' | 'falco_e_motors' | 'minoura' | 'cycliq' | 'luxottica' | 'trainer_road' | 'the_sufferfest' | 'fullspeedahead' | 'virtualtraining' | 'feedbacksports' | 'omata' | 'vdo' | 'magneticdays' | 'hammerhead' | 'kinetic_by_kurt' | 'shapelog' | 'dabuziduo' | 'jetblack' | 'coros' | 'virtugo' | 'velosense' | 'actigraphcorp';
74
74
  export type GarminProduct = 'hrm_bike' | 'hrm1' | 'axh01' | 'axb01' | 'axb02' | 'hrm2ss' | 'dsi_alf02' | 'hrm3ss' | 'hrm_run_single_byte_product_id' | 'bsm' | 'bcm' | 'axs01' | 'hrm_tri_single_byte_product_id' | 'fr225_single_byte_product_id' | 'fr301_china' | 'fr301_japan' | 'fr301_korea' | 'fr301_taiwan' | 'fr405' | 'fr50' | 'fr405_japan' | 'fr60' | 'dsi_alf01' | 'fr310xt' | 'edge500' | 'fr110' | 'edge800' | 'edge500_taiwan' | 'edge500_japan' | 'chirp' | 'fr110_japan' | 'edge200' | 'fr910xt' | 'edge800_taiwan' | 'edge800_japan' | 'alf04' | 'fr610' | 'fr210_japan' | 'vector_ss' | 'vector_cp' | 'edge800_china' | 'edge500_china' | 'fr610_japan' | 'edge500_korea' | 'fr70' | 'fr310xt_4t' | 'amx' | 'fr10' | 'edge800_korea' | 'swim' | 'fr910xt_china' | 'fenix' | 'edge200_taiwan' | 'edge510' | 'edge810' | 'tempe' | 'fr910xt_japan' | 'fr620' | 'fr220' | 'fr910xt_korea' | 'fr10_japan' | 'edge810_japan' | 'virb_elite' | 'edge_touring' | 'edge510_japan' | 'hrm_tri' | 'hrm_run' | 'fr920xt' | 'edge510_asia' | 'edge810_china' | 'edge810_taiwan' | 'edge1000' | 'vivo_fit' | 'virb_remote' | 'vivo_ki' | 'fr15' | 'vivo_active' | 'edge510_korea' | 'fr620_japan' | 'fr620_china' | 'fr220_japan' | 'fr220_china' | 'approach_s6' | 'vivo_smart' | 'fenix2' | 'epix' | 'fenix3' | 'edge1000_taiwan' | 'edge1000_japan' | 'fr15_japan' | 'edge520' | 'edge1000_china' | 'fr620_russia' | 'fr220_russia' | 'vector_s' | 'edge1000_korea' | 'fr920xt_taiwan' | 'fr920xt_china' | 'fr920xt_japan' | 'virbx' | 'vivo_smart_apac' | 'etrex_touch' | 'edge25' | 'fr25' | 'vivo_fit2' | 'fr225' | 'fr630' | 'fr230' | 'fr735xt' | 'vivo_active_apac' | 'vector_2' | 'vector_2s' | 'virbxe' | 'fr620_taiwan' | 'fr220_taiwan' | 'truswing' | 'fenix3_china' | 'fenix3_twn' | 'varia_headlight' | 'varia_taillight_old' | 'edge_explore_1000' | 'fr225_asia' | 'varia_radar_taillight' | 'varia_radar_display' | 'edge20' | 'd2_bravo' | 'approach_s20' | 'varia_remote' | 'hrm4_run' | 'vivo_active_hr' | 'vivo_smart_hr' | 'vivo_move' | 'varia_vision' | 'vivo_fit3' | 'fenix3_hr' | 'virb_ultra_30' | 'index_smart_scale' | 'fr235' | 'fenix3_chronos' | 'oregon7xx' | 'rino7xx' | 'nautix' | 'edge_820' | 'edge_explore_820' | 'fenix5s' | 'd2_bravo_titanium' | 'varia_ut800' | 'running_dynamics_pod' | 'fenix5x' | 'vivo_fit_jr' | 'fr935' | 'fenix5' | 'descent' | 'sdm4' | 'edge_remote' | 'training_center' | 'connectiq_simulator' | 'android_antplus_plugin' | 'connect';
75
- export type AntplusDeviceType = '0' | 'antfs' | 'bike_power' | 'environment_sensor_legacy' | 'multi_sport_speed_distance' | 'control' | 'fitness_equipment' | 'blood_pressure' | 'geocache_node' | 'light_electric_vehicle' | 'env_sensor' | 'racquet' | 'control_hub' | 'muscle_oxygen' | 'bike_light_main' | 'bike_light_shared' | 'exd' | 'bike_radar' | 'weight_scale' | 'heart_rate' | 'bike_speed_cadence' | 'bike_cadence' | 'bike_speed' | 'stride_speed_distance';
75
+ export type AntplusDeviceType = 'antfs' | 'bike_power' | 'environment_sensor_legacy' | 'multi_sport_speed_distance' | 'control' | 'fitness_equipment' | 'blood_pressure' | 'geocache_node' | 'light_electric_vehicle' | 'env_sensor' | 'racquet' | 'control_hub' | 'muscle_oxygen' | 'shifting' | 'bike_light_main' | 'bike_light_shared' | 'exd' | 'bike_radar' | 'bike_aero' | 'weight_scale' | 'heart_rate' | 'bike_speed_cadence' | 'bike_cadence' | 'bike_speed' | 'stride_speed_distance';
76
+ export type LocalDeviceType = 'gps' | 'glonass' | 'gps_glonass' | 'accelerometer' | 'barometer' | 'temperature' | 'whr' | 'sensor_hub';
77
+ export type BleDeviceType = 'connected_gps' | 'heart_rate' | 'bike_power' | 'bike_speed_cadence' | 'bike_speed' | 'bike_cadence' | 'footpod' | 'bike_trainer';
76
78
  export type AntNetwork = 'public' | 'antplus' | 'antfs' | 'private';
77
79
  export type WorkoutCapabilities = '0' | 'interval' | 'custom' | 'fitness_equipment' | 'firstbeat' | 'new_leaf' | 'tcx' | 'speed' | 'heart_rate' | 'distance' | 'cadence' | 'power' | 'grade' | 'resistance' | 'protected';
78
80
  export type BatteryStatus = '0' | 'new' | 'good' | 'ok' | 'low' | 'critical' | 'charging' | 'unknown';
@@ -665,7 +667,7 @@ export interface ParsedEvent {
665
667
  }
666
668
  export interface ParsedDeviceInfo {
667
669
  device_index?: number;
668
- device_type?: AntplusDeviceType;
670
+ device_type?: number;
669
671
  manufacturer?: Manufacturer;
670
672
  serial_number?: number;
671
673
  product?: number;
@@ -871,6 +873,20 @@ export interface ParsedOHrSettings {
871
873
  enabled?: number;
872
874
  timestamp: string;
873
875
  }
876
+ export interface ParsedActivityMetrics {
877
+ new_max_heart_rate?: number;
878
+ aerobic_training_effect?: number;
879
+ vo2_max?: number;
880
+ recovery_time?: number;
881
+ sport?: Sport;
882
+ anaerobic_training_effect?: number;
883
+ first_vo2_max?: number;
884
+ primary_benefit?: number;
885
+ total_ascent?: number;
886
+ total_descent?: number;
887
+ avg_power?: number;
888
+ avg_heart_rate?: number;
889
+ }
874
890
  export interface ParsedFieldDescription {
875
891
  developer_data_index?: number;
876
892
  field_definition_number?: number;
@@ -894,11 +910,9 @@ export interface ParsedTimeInZone {
894
910
  time_in_hr_zone?: number[];
895
911
  time_in_speed_zone?: number[];
896
912
  time_in_power_zone?: number[];
897
- hr_zone_high_boundary_deprecated?: number[];
898
913
  hr_zone_high_boundary?: number[];
899
914
  speed_zone_high_boundary?: number[];
900
915
  power_zone_high_boundary?: number[];
901
- hr_calc_type?: HrZoneCalc;
902
916
  max_heart_rate_deprecated?: number;
903
917
  max_heart_rate?: number;
904
918
  resting_heart_rate?: number;
@@ -1025,4 +1039,5 @@ export interface ParsedFit {
1025
1039
  tank_summaries?: ParsedTankSummary[];
1026
1040
  jumps?: ParsedJump[];
1027
1041
  time_in_zone?: ParsedTimeInZone[];
1042
+ activity_metrics?: ParsedActivityMetrics[];
1028
1043
  }
@@ -210,6 +210,7 @@ function generateFitType() {
210
210
  tank_summaries: 'parsed_tank_summary',
211
211
  jumps: 'parsed_jump',
212
212
  time_in_zone: 'parsed_time_in_zone',
213
+ activity_metrics: 'parsed_activity_metrics',
213
214
  };
214
215
  const referenceProperties = {
215
216
  file_creator: 'parsed_file_creator',
@@ -101,6 +101,7 @@ export default class FitParser {
101
101
  const tank_summaries = [];
102
102
  const jumps = [];
103
103
  const time_in_zone = [];
104
+ const activity_metrics = [];
104
105
  let loopIndex = headerLength;
105
106
  const messageTypes = [];
106
107
  const developerFields = [];
@@ -202,6 +203,9 @@ export default class FitParser {
202
203
  case 'time_in_zone':
203
204
  time_in_zone.push(message);
204
205
  break;
206
+ case 'activity_metrics':
207
+ activity_metrics.push(message);
208
+ break;
205
209
  default:
206
210
  if (messageType !== '') {
207
211
  fitObj[messageType] = message;
@@ -223,6 +227,7 @@ export default class FitParser {
223
227
  fitObj.tank_summaries = tank_summaries;
224
228
  fitObj.jumps = jumps;
225
229
  fitObj.time_in_zone = time_in_zone;
230
+ fitObj.activity_metrics = activity_metrics;
226
231
  if (isCascadeNeeded) {
227
232
  laps = mapDataIntoLap(laps, 'records', records);
228
233
  laps = mapDataIntoLap(laps, 'lengths', lengths);