fit-file-parser 2.1.1 → 2.2.2

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,70 @@
1
+ import fs from 'fs'
2
+ import { createRequire } from 'module'
3
+
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')
19
+ return
20
+
21
+ Object.keys(obj).forEach((key) => {
22
+ const val = obj[key]
23
+ const newPath = [...path, key]
24
+
25
+ // Check match 11 (Jump Count)
26
+ if (typeof val === 'number' && Math.abs(val - 11) < 0.001) {
27
+ console.log(`>>> FOUND 11 at: ${newPath.join('.')} (Value: ${val})`)
28
+ }
29
+ // Check match 159 (Resting Cals)
30
+ if (typeof val === 'number' && Math.abs(val - 159) < 0.1) {
31
+ console.log(`>>> FOUND 159 at: ${newPath.join('.')} (Value: ${val})`)
32
+ }
33
+ // Check match for Training Load Peak
34
+ if (typeof val === 'number' && Math.abs(val - 6079174) < 100) {
35
+ console.log(`>>> FOUND 6M at: ${newPath.join('.')} (Value: ${val})`)
36
+ }
37
+
38
+ if (typeof val === 'object') {
39
+ search(val, newPath)
40
+ }
41
+ })
42
+ }
43
+
44
+ fitParser.parse(content, (error, data) => {
45
+ if (error) {
46
+ console.error(error)
47
+ }
48
+ else {
49
+ console.log('Starting deep search...')
50
+ search(data)
51
+
52
+ if (data.sessions && data.sessions.length > 0) {
53
+ console.log('\n=== SESSION OBJECT (First) ===')
54
+ const session = data.sessions[0]
55
+ Object.keys(session).forEach((key) => {
56
+ console.log(`${key}: ${session[key]}`)
57
+ })
58
+ }
59
+ // Explicitly check for jump-related keys
60
+ if (data.jumps) {
61
+ console.log('=== JUMPS OBJECT === ')
62
+ console.log(JSON.stringify(data.jumps, null, 2))
63
+ }
64
+ // Also check for jump events in events
65
+ if (data.events) {
66
+ const jumpEvents = data.events.filter(e => JSON.stringify(e).includes('jump'))
67
+ console.log(`Found ${jumpEvents.length} jump events`)
68
+ }
69
+ }
70
+ })
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: {
@@ -3049,6 +3190,13 @@ exports.FIT = {
3049
3190
  offset: 0,
3050
3191
  units: '',
3051
3192
  },
3193
+ 24: {
3194
+ field: 'ant_id',
3195
+ type: 'uint32z',
3196
+ scale: null,
3197
+ offset: 0,
3198
+ units: '',
3199
+ },
3052
3200
  25: {
3053
3201
  field: 'source_type',
3054
3202
  type: 'source_type',
@@ -4493,6 +4641,128 @@ exports.FIT = {
4493
4641
  units: 'cbar',
4494
4642
  },
4495
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
+ },
4496
4766
  },
4497
4767
  types: {
4498
4768
  file: {
@@ -4605,8 +4875,10 @@ exports.FIT = {
4605
4875
  268: 'dive_summary',
4606
4876
  285: 'jump',
4607
4877
  317: 'climb_pro',
4608
- 319: 'tank_pressure',
4878
+ 319: 'tank_update',
4609
4879
  323: 'tank_summary',
4880
+ 216: 'time_in_zone',
4881
+ 347: 'o_hr_settings',
4610
4882
  65280: 'mfg_range_min',
4611
4883
  65534: 'mfg_range_max',
4612
4884
  },