fit-file-parser 2.1.0 → 2.2.1

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/deep_probe.js ADDED
@@ -0,0 +1,69 @@
1
+
2
+ import fs from 'fs';
3
+ import { createRequire } from 'module';
4
+ const require = createRequire(import.meta.url);
5
+ const FitParser = require('./dist/fit-parser.js').default;
6
+
7
+ const content = fs.readFileSync('/Users/dimitrios/Projects/sports-lib/samples/fit/jumps-mtb.fit');
8
+ const fitParser = new FitParser({
9
+ force: true,
10
+ speedUnit: 'km/h',
11
+ lengthUnit: 'm',
12
+ temperatureUnit: 'celsius',
13
+ elapsedRecordField: true,
14
+ mode: 'both',
15
+ });
16
+
17
+ function search(obj, path = []) {
18
+ if (!obj || typeof obj !== 'object') return;
19
+
20
+ Object.keys(obj).forEach(key => {
21
+ const val = obj[key];
22
+ const newPath = [...path, key];
23
+
24
+ // Check match 11 (Jump Count)
25
+ if (typeof val === 'number' && Math.abs(val - 11) < 0.001) {
26
+ console.log(`>>> FOUND 11 at: ${newPath.join('.')} (Value: ${val})`);
27
+ }
28
+ // Check match 159 (Resting Cals)
29
+ if (typeof val === 'number' && Math.abs(val - 159) < 0.1) {
30
+ console.log(`>>> FOUND 159 at: ${newPath.join('.')} (Value: ${val})`);
31
+ }
32
+ // Check match for Training Load Peak
33
+ if (typeof val === 'number' && Math.abs(val - 6079174) < 100) {
34
+ console.log(`>>> FOUND 6M at: ${newPath.join('.')} (Value: ${val})`);
35
+ }
36
+
37
+ if (typeof val === 'object') {
38
+ search(val, newPath);
39
+ }
40
+ });
41
+ }
42
+
43
+ fitParser.parse(content, (error, data) => {
44
+ if (error) {
45
+ console.error(error);
46
+ } else {
47
+ console.log("Starting deep search...");
48
+ search(data);
49
+
50
+
51
+ if (data.sessions && data.sessions.length > 0) {
52
+ console.log("\n=== SESSION OBJECT (First) ===");
53
+ const session = data.sessions[0];
54
+ Object.keys(session).forEach(key => {
55
+ console.log(`${key}: ${session[key]}`);
56
+ });
57
+ }
58
+ // Explicitly check for jump-related keys
59
+ if (data.jumps) {
60
+ console.log("=== JUMPS OBJECT === ");
61
+ console.log(JSON.stringify(data.jumps, null, 2));
62
+ }
63
+ // Also check for jump events in events
64
+ if (data.events) {
65
+ const jumpEvents = data.events.filter(e => JSON.stringify(e).includes('jump'));
66
+ console.log(`Found ${jumpEvents.length} jump events`);
67
+ }
68
+ }
69
+ });
package/dist/binary.js CHANGED
@@ -15,6 +15,13 @@ export function addEndian(littleEndian, bytes) {
15
15
  return result;
16
16
  }
17
17
  function readData(blob, fDef, startIndex, options) {
18
+ if (fDef.type === 'uint8_array') {
19
+ const array8 = [];
20
+ for (let i = 0; i < fDef.size; i++) {
21
+ array8.push(blob[startIndex + i]);
22
+ }
23
+ return array8;
24
+ }
18
25
  if (fDef.endianAbility) {
19
26
  const temp = [];
20
27
  for (let i = 0; i < fDef.size; i++) {
@@ -46,11 +53,11 @@ function readData(blob, fDef, startIndex, options) {
46
53
  return array32;
47
54
  }
48
55
  case 'uint16_array': {
49
- const array = [];
56
+ const array16 = [];
50
57
  for (let i = 0; i < fDef.size; i += 2) {
51
- array.push(dataView.getUint16(i, fDef.littleEndian));
58
+ array16.push(dataView.getUint16(i, fDef.littleEndian));
52
59
  }
53
- return array;
60
+ return array16;
54
61
  }
55
62
  }
56
63
  }
@@ -93,7 +100,11 @@ function formatByType(data, type, scale, offset) {
93
100
  return scale ? data / scale + offset : data;
94
101
  case 'uint32_array':
95
102
  case 'uint16_array':
96
- return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
103
+ case 'uint8_array':
104
+ if (Array.isArray(data)) {
105
+ return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
106
+ }
107
+ return scale ? data / scale + offset : data;
97
108
  default:
98
109
  {
99
110
  if (!FIT.types[type]) {
@@ -216,6 +227,13 @@ function applyOptions(data, field, options) {
216
227
  case 'start_pressure':
217
228
  case 'end_pressure':
218
229
  return convertTo(data, 'pressureUnits', options.pressureUnit);
230
+ case 'ant_id': {
231
+ const n1 = (data >>> 28) & 0xf;
232
+ const n2 = (data >>> 24) & 0xf;
233
+ const n3 = (data >>> 16) & 0xff;
234
+ const n4 = data & 0xffff;
235
+ return `${n1.toString(16).toUpperCase()}-${n2.toString(16).toUpperCase()}-${n3.toString(16).toUpperCase().padStart(2, '0')}-${n4.toString(16).toUpperCase().padStart(4, '0')}`;
236
+ }
219
237
  default:
220
238
  return data;
221
239
  }
@@ -21,6 +21,13 @@ function addEndian(littleEndian, bytes) {
21
21
  return result;
22
22
  }
23
23
  function readData(blob, fDef, startIndex, options) {
24
+ if (fDef.type === 'uint8_array') {
25
+ const array8 = [];
26
+ for (let i = 0; i < fDef.size; i++) {
27
+ array8.push(blob[startIndex + i]);
28
+ }
29
+ return array8;
30
+ }
24
31
  if (fDef.endianAbility) {
25
32
  const temp = [];
26
33
  for (let i = 0; i < fDef.size; i++) {
@@ -52,11 +59,11 @@ function readData(blob, fDef, startIndex, options) {
52
59
  return array32;
53
60
  }
54
61
  case 'uint16_array': {
55
- const array = [];
62
+ const array16 = [];
56
63
  for (let i = 0; i < fDef.size; i += 2) {
57
- array.push(dataView.getUint16(i, fDef.littleEndian));
64
+ array16.push(dataView.getUint16(i, fDef.littleEndian));
58
65
  }
59
- return array;
66
+ return array16;
60
67
  }
61
68
  }
62
69
  }
@@ -99,7 +106,11 @@ function formatByType(data, type, scale, offset) {
99
106
  return scale ? data / scale + offset : data;
100
107
  case 'uint32_array':
101
108
  case 'uint16_array':
102
- return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
109
+ case 'uint8_array':
110
+ if (Array.isArray(data)) {
111
+ return data.map((dataItem) => scale ? dataItem / scale + offset : dataItem);
112
+ }
113
+ return scale ? data / scale + offset : data;
103
114
  default:
104
115
  {
105
116
  if (!fit_js_1.FIT.types[type]) {
@@ -222,6 +233,13 @@ function applyOptions(data, field, options) {
222
233
  case 'start_pressure':
223
234
  case 'end_pressure':
224
235
  return convertTo(data, 'pressureUnits', options.pressureUnit);
236
+ case 'ant_id': {
237
+ const n1 = (data >>> 28) & 0xf;
238
+ const n2 = (data >>> 24) & 0xf;
239
+ const n3 = (data >>> 16) & 0xff;
240
+ const n4 = data & 0xffff;
241
+ return `${n1.toString(16).toUpperCase()}-${n2.toString(16).toUpperCase()}-${n3.toString(16).toUpperCase().padStart(2, '0')}-${n4.toString(16).toUpperCase().padStart(4, '0')}`;
242
+ }
225
243
  default:
226
244
  return data;
227
245
  }
@@ -101,6 +101,8 @@ class FitParser {
101
101
  const lengths = [];
102
102
  const tank_updates = [];
103
103
  const tank_summaries = [];
104
+ const jumps = [];
105
+ const time_in_zone = [];
104
106
  let loopIndex = headerLength;
105
107
  const messageTypes = [];
106
108
  const developerFields = [];
@@ -196,6 +198,12 @@ class FitParser {
196
198
  case 'tank_summary':
197
199
  tank_summaries.push(message);
198
200
  break;
201
+ case 'jump':
202
+ jumps.push(message);
203
+ break;
204
+ case 'time_in_zone':
205
+ time_in_zone.push(message);
206
+ break;
199
207
  default:
200
208
  if (messageType !== '') {
201
209
  fitObj[messageType] = message;
@@ -215,6 +223,8 @@ class FitParser {
215
223
  fitObj.definitions = definitions;
216
224
  fitObj.tank_updates = tank_updates;
217
225
  fitObj.tank_summaries = tank_summaries;
226
+ fitObj.jumps = jumps;
227
+ fitObj.time_in_zone = time_in_zone;
218
228
  if (isCascadeNeeded) {
219
229
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'records', records);
220
230
  laps = (0, helper_js_1.mapDataIntoLap)(laps, 'lengths', lengths);
package/dist/cjs/fit.js CHANGED
@@ -1083,6 +1083,20 @@ exports.FIT = {
1083
1083
  offset: 0,
1084
1084
  units: '%',
1085
1085
  },
1086
+ 38: {
1087
+ field: 'end_position_lat',
1088
+ type: 'sint32',
1089
+ scale: null,
1090
+ offset: 0,
1091
+ units: 'semicircles',
1092
+ },
1093
+ 39: {
1094
+ field: 'end_position_long',
1095
+ type: 'sint32',
1096
+ scale: null,
1097
+ offset: 0,
1098
+ units: 'semicircles',
1099
+ },
1086
1100
  41: {
1087
1101
  field: 'avg_stroke_count',
1088
1102
  type: 'uint32',
@@ -1637,7 +1651,7 @@ exports.FIT = {
1637
1651
  units: 'mm',
1638
1652
  },
1639
1653
  137: {
1640
- field: 'total_anaerobic_effect',
1654
+ field: 'total_anaerobic_training_effect',
1641
1655
  type: 'uint8',
1642
1656
  scale: 10,
1643
1657
  offset: 0,
@@ -1646,9 +1660,9 @@ exports.FIT = {
1646
1660
  139: {
1647
1661
  field: 'avg_vam',
1648
1662
  type: 'uint16',
1649
- scale: 1000,
1663
+ scale: 1, // Raw 100 -> 100 (User specific m/h)
1650
1664
  offset: 0,
1651
- units: 'm/s',
1665
+ units: 'm/h',
1652
1666
  },
1653
1667
  192: {
1654
1668
  field: 'workout_feel',
@@ -1664,6 +1678,111 @@ exports.FIT = {
1664
1678
  offset: 0,
1665
1679
  units: '',
1666
1680
  },
1681
+ 110: {
1682
+ field: 'sport_profile_name',
1683
+ type: 'string',
1684
+ scale: null,
1685
+ offset: 0,
1686
+ units: '',
1687
+ },
1688
+ 168: {
1689
+ field: 'training_load_peak',
1690
+ type: 'uint32',
1691
+ scale: 1,
1692
+ offset: 0,
1693
+ units: '',
1694
+ },
1695
+ 169: {
1696
+ field: 'enhanced_avg_respiration_rate',
1697
+ type: 'uint16',
1698
+ scale: 100,
1699
+ offset: 0,
1700
+ units: 'breaths/min',
1701
+ },
1702
+ 150: {
1703
+ field: 'min_temperature',
1704
+ type: 'sint8',
1705
+ scale: null,
1706
+ offset: 0,
1707
+ units: 'C',
1708
+ },
1709
+ 170: {
1710
+ field: 'enhanced_max_respiration_rate',
1711
+ type: 'uint16',
1712
+ scale: 100,
1713
+ offset: 0,
1714
+ units: 'breaths/min',
1715
+ },
1716
+ 178: {
1717
+ field: 'est_sweat_loss',
1718
+ type: 'uint16',
1719
+ scale: 1, // ml
1720
+ offset: 0,
1721
+ units: 'ml',
1722
+ },
1723
+ 180: {
1724
+ field: 'enhanced_min_respiration_rate',
1725
+ type: 'uint16',
1726
+ scale: 100,
1727
+ offset: 0,
1728
+ units: 'breaths/min',
1729
+ },
1730
+ 181: {
1731
+ field: 'total_grit',
1732
+ type: 'float32',
1733
+ scale: 1,
1734
+ offset: 0,
1735
+ units: 'kGrit',
1736
+ },
1737
+ 183: {
1738
+ field: 'jump_count',
1739
+ type: 'uint16',
1740
+ scale: 1,
1741
+ offset: 0,
1742
+ units: '',
1743
+ },
1744
+ 187: {
1745
+ field: 'avg_flow',
1746
+ type: 'float32',
1747
+ scale: 1,
1748
+ offset: 0,
1749
+ units: 'Flow',
1750
+ },
1751
+ 188: {
1752
+ field: 'primary_benefit',
1753
+ type: 'uint8',
1754
+ scale: 1,
1755
+ offset: 0,
1756
+ units: '',
1757
+ },
1758
+ 196: {
1759
+ field: 'resting_calories',
1760
+ type: 'uint16',
1761
+ scale: 1,
1762
+ offset: 0,
1763
+ units: 'kcal',
1764
+ },
1765
+ 214: {
1766
+ field: 'avg_grit',
1767
+ type: 'float32',
1768
+ scale: null,
1769
+ offset: 0,
1770
+ units: '',
1771
+ },
1772
+ 215: {
1773
+ field: 'avg_flow',
1774
+ type: 'float32',
1775
+ scale: null,
1776
+ offset: 0,
1777
+ units: '',
1778
+ },
1779
+ 140: {
1780
+ field: 'recovery_advisor',
1781
+ type: 'uint16', // Minutes?
1782
+ scale: 1,
1783
+ offset: 0,
1784
+ units: 'min',
1785
+ },
1667
1786
  },
1668
1787
  19: {
1669
1788
  name: 'lap',
@@ -2407,6 +2526,20 @@ exports.FIT = {
2407
2526
  offset: 0,
2408
2527
  units: 's',
2409
2528
  },
2529
+ 114: {
2530
+ field: 'grit',
2531
+ type: 'float32',
2532
+ scale: null,
2533
+ offset: 0,
2534
+ units: '',
2535
+ },
2536
+ 115: {
2537
+ field: 'flow',
2538
+ type: 'float32',
2539
+ scale: null,
2540
+ offset: 0,
2541
+ units: '',
2542
+ },
2410
2543
  0: {
2411
2544
  field: 'position_lat',
2412
2545
  type: 'sint32',
@@ -2858,6 +2991,14 @@ exports.FIT = {
2858
2991
  units: 'percent',
2859
2992
  },
2860
2993
  },
2994
+ 140: {
2995
+ name: 'jump',
2996
+ 253: { field: 'timestamp', type: 'date_time', scale: null, offset: 0, units: 's' },
2997
+ 9: { field: 'distance', type: 'uint16', scale: 100, offset: 0, units: 'm' },
2998
+ 10: { field: 'height', type: 'uint16', scale: 1000, offset: 0, units: 'm' },
2999
+ 37: { field: 'score', type: 'sint32', scale: null, offset: 0, units: '' },
3000
+ 7: { field: 'enhanced_mets', type: 'uint32', scale: 65536, offset: 0, units: 'METs' },
3001
+ },
2861
3002
  21: {
2862
3003
  name: 'event',
2863
3004
  253: {
@@ -3000,6 +3141,13 @@ exports.FIT = {
3000
3141
  offset: 0,
3001
3142
  units: 'V',
3002
3143
  },
3144
+ 32: {
3145
+ field: 'battery_level',
3146
+ type: 'uint8',
3147
+ scale: null,
3148
+ offset: 0,
3149
+ units: 'percent',
3150
+ },
3003
3151
  11: {
3004
3152
  field: 'battery_status',
3005
3153
  type: 'battery_status',
@@ -3042,6 +3190,13 @@ exports.FIT = {
3042
3190
  offset: 0,
3043
3191
  units: '',
3044
3192
  },
3193
+ 24: {
3194
+ field: 'ant_id',
3195
+ type: 'uint32z',
3196
+ scale: null,
3197
+ offset: 0,
3198
+ units: '',
3199
+ },
3045
3200
  25: {
3046
3201
  field: 'source_type',
3047
3202
  type: 'source_type',
@@ -4486,6 +4641,128 @@ exports.FIT = {
4486
4641
  units: 'cbar',
4487
4642
  },
4488
4643
  },
4644
+ 216: {
4645
+ name: 'time_in_zone',
4646
+ 253: {
4647
+ field: 'timestamp',
4648
+ type: 'date_time',
4649
+ scale: null,
4650
+ offset: 0,
4651
+ units: 's',
4652
+ },
4653
+ 0: {
4654
+ field: 'reference_mesg',
4655
+ type: 'uint16',
4656
+ scale: null,
4657
+ offset: 0,
4658
+ units: '',
4659
+ },
4660
+ 1: {
4661
+ field: 'reference_index',
4662
+ type: 'uint16',
4663
+ scale: null,
4664
+ offset: 0,
4665
+ units: '',
4666
+ },
4667
+ 2: {
4668
+ field: 'time_in_hr_zone',
4669
+ type: 'uint32_array',
4670
+ scale: 1000,
4671
+ offset: 0,
4672
+ units: 's',
4673
+ },
4674
+ 3: {
4675
+ field: 'time_in_speed_zone',
4676
+ type: 'uint32_array',
4677
+ scale: 1000,
4678
+ offset: 0,
4679
+ units: 's',
4680
+ },
4681
+ 4: {
4682
+ field: 'time_in_power_zone',
4683
+ type: 'uint32_array',
4684
+ scale: 1000,
4685
+ offset: 0,
4686
+ units: 's',
4687
+ },
4688
+ 5: {
4689
+ field: 'hr_zone_high_boundary_deprecated',
4690
+ type: 'uint8_array',
4691
+ scale: null,
4692
+ offset: 0,
4693
+ units: '',
4694
+ },
4695
+ 6: {
4696
+ field: 'hr_zone_high_boundary',
4697
+ type: 'uint8_array',
4698
+ scale: null,
4699
+ offset: 0,
4700
+ units: 'bpm',
4701
+ },
4702
+ 7: {
4703
+ field: 'speed_zone_high_boundary',
4704
+ type: 'uint16_array',
4705
+ scale: 1000,
4706
+ offset: 0,
4707
+ units: 'm/s',
4708
+ },
4709
+ 8: {
4710
+ field: 'power_zone_high_boundary',
4711
+ type: 'uint16_array',
4712
+ scale: null,
4713
+ offset: 0,
4714
+ units: 'watts',
4715
+ },
4716
+ 9: {
4717
+ field: 'hr_calc_type',
4718
+ type: 'hr_zone_calc',
4719
+ scale: null,
4720
+ offset: 0,
4721
+ units: '',
4722
+ },
4723
+ 10: {
4724
+ field: 'max_heart_rate_deprecated',
4725
+ type: 'uint8',
4726
+ scale: null,
4727
+ offset: 0,
4728
+ units: 'bpm',
4729
+ },
4730
+ 11: {
4731
+ field: 'max_heart_rate',
4732
+ type: 'uint8',
4733
+ scale: null,
4734
+ offset: 0,
4735
+ units: 'bpm',
4736
+ },
4737
+ 12: {
4738
+ field: 'resting_heart_rate',
4739
+ type: 'uint8',
4740
+ scale: null,
4741
+ offset: 0,
4742
+ units: 'bpm',
4743
+ },
4744
+ 13: {
4745
+ field: 'threshold_heart_rate',
4746
+ type: 'uint8',
4747
+ scale: null,
4748
+ offset: 0,
4749
+ units: 'bpm',
4750
+ },
4751
+ 14: {
4752
+ field: 'pwr_calc_type',
4753
+ type: 'pwr_zone_calc',
4754
+ scale: null,
4755
+ offset: 0,
4756
+ units: '',
4757
+ },
4758
+ 15: {
4759
+ field: 'functional_threshold_power',
4760
+ type: 'uint16',
4761
+ scale: null,
4762
+ offset: 0,
4763
+ units: 'watts',
4764
+ },
4765
+ },
4489
4766
  },
4490
4767
  types: {
4491
4768
  file: {
@@ -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' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_pressure' | 'tank_summary' | 'mfg_range_min' | 'mfg_range_max' | 'o_hr_settings' | 'tank_update' | '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' | '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' | 'set' | 'stress_level' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'jump' | 'climb_pro' | 'tank_pressure' | 'tank_summary' | 'time_in_zone' | 'mfg_range_min' | 'mfg_range_max' | 'o_hr_settings' | 'tank_update' | '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';
@@ -444,8 +444,12 @@ export interface ParsedSession {
444
444
  avg_step_length?: number;
445
445
  total_anaerobic_effect?: number;
446
446
  avg_vam?: number;
447
+ total_grit?: number;
448
+ total_flow?: number;
447
449
  workout_feel?: number;
448
450
  workout_rpe?: number;
451
+ avg_grit?: number;
452
+ avg_flow?: number;
449
453
  timestamp: string;
450
454
  message_index?: MessageIndex;
451
455
  laps?: ParsedLap[];
@@ -627,6 +631,8 @@ export interface ParsedRecord {
627
631
  ndl_time?: number;
628
632
  cns_load?: number;
629
633
  n2_load?: number;
634
+ grit?: number;
635
+ flow?: number;
630
636
  timestamp: string;
631
637
  }
632
638
  export interface ParsedEvent {
@@ -662,6 +668,7 @@ export interface ParsedDeviceInfo {
662
668
  ant_network?: AntNetwork;
663
669
  source_type?: SourceType;
664
670
  product_name?: string;
671
+ battery_level?: number;
665
672
  timestamp: string;
666
673
  }
667
674
  export interface ParsedWorkout {
@@ -850,6 +857,12 @@ export interface ParsedOHrSettings {
850
857
  enabled?: number;
851
858
  timestamp: string;
852
859
  }
860
+ export interface ParsedJump {
861
+ distance?: number;
862
+ height?: number;
863
+ score?: number;
864
+ timestamp: string;
865
+ }
853
866
  export interface ParsedFieldDescription {
854
867
  developer_data_index?: number;
855
868
  field_definition_number?: number;
@@ -939,6 +952,23 @@ export interface ParsedTankSummary {
939
952
  end_pressure?: number;
940
953
  volume_used?: number;
941
954
  }
955
+ export interface ParsedTimeInZone {
956
+ timestamp: string;
957
+ reference_mesg?: number;
958
+ reference_index?: number;
959
+ time_in_hr_zone?: number[];
960
+ time_in_speed_zone?: number[];
961
+ time_in_power_zone?: number[];
962
+ hr_zone_high_boundary?: number[];
963
+ speed_zone_high_boundary?: number[];
964
+ power_zone_high_boundary?: number[];
965
+ hr_calc_type?: HrZoneCalc;
966
+ max_heart_rate?: number;
967
+ resting_heart_rate?: number;
968
+ threshold_heart_rate?: number;
969
+ pwr_calc_type?: PwrZoneCalc;
970
+ functional_threshold_power?: number;
971
+ }
942
972
  export interface ParsedFit {
943
973
  protocolVersion?: number;
944
974
  profileVersion?: number;
@@ -971,4 +1001,6 @@ export interface ParsedFit {
971
1001
  definitions?: unknown[];
972
1002
  tank_updates?: ParsedTankUpdate[];
973
1003
  tank_summaries?: ParsedTankSummary[];
1004
+ jumps?: ParsedJump[];
1005
+ time_in_zone?: ParsedTimeInZone[];
974
1006
  }
@@ -6,18 +6,16 @@ function mapDataIntoLap(inputLaps, lapKey, data) {
6
6
  const laps = [...inputLaps];
7
7
  let index = 0;
8
8
  for (let i = 0; i < laps.length; i++) {
9
- const lap = laps[i];
10
9
  const nextLap = laps[i + 1];
11
10
  const tempData = [];
12
- const lapStartTime = new Date(lap.start_time).getTime();
13
11
  const nextLapStartTime = nextLap
14
12
  ? new Date(nextLap.start_time).getTime()
15
13
  : null;
16
14
  for (let j = index; j < data.length; j++) {
17
15
  const row = data[j];
18
16
  if (nextLap && nextLapStartTime) {
19
- const timestamp = new Date(row.timestamp).getTime();
20
- if (lapStartTime <= timestamp && nextLapStartTime > timestamp) {
17
+ const timestamp = new Date(row.timestamp || row.start_time).getTime();
18
+ if (nextLapStartTime > timestamp) {
21
19
  tempData.push(row);
22
20
  }
23
21
  else if (nextLapStartTime <= timestamp) {
@@ -39,10 +37,8 @@ function mapDataIntoSession(inputSessions, laps) {
39
37
  const sessions = [...inputSessions];
40
38
  let lapIndex = 0;
41
39
  for (let i = 0; i < sessions.length; i++) {
42
- const session = sessions[i];
43
40
  const nextSession = sessions[i + 1];
44
41
  const tempLaps = [];
45
- const sessionStartTime = new Date(session.start_time).getTime();
46
42
  const nextSessionStartTime = nextSession
47
43
  ? new Date(nextSession.start_time).getTime()
48
44
  : null;
@@ -50,8 +46,7 @@ function mapDataIntoSession(inputSessions, laps) {
50
46
  const lap = laps[j];
51
47
  if (nextSession && nextSessionStartTime) {
52
48
  const lapStartTime = new Date(lap.start_time).getTime();
53
- if (sessionStartTime <= lapStartTime
54
- && nextSessionStartTime > lapStartTime) {
49
+ if (nextSessionStartTime > lapStartTime) {
55
50
  tempLaps.push(lap);
56
51
  }
57
52
  else if (nextSessionStartTime <= lapStartTime) {