fit-file-parser 6.1.2 → 6.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.
@@ -1,23 +1,42 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { calculateCRC, readRecord } from './binary.js';
2
11
  import { mapDataIntoLap, mapDataIntoSession } from './helper.js';
3
12
  export { FitBaseType, FitEncoder } from './fit-encoder.js';
4
13
  export { getFitCoursePointId, getFitGarminProductDisplayName, getFitGarminProductName, getFitManufacturerName, getFitSportId, getFitSportName, getFitSubSportId, getFitSubSportName, } from './profile-lookup.js';
5
14
  export { FitMessageReaderError, fitTimestampToUnixMilliseconds, getFitBaseTypeId, readFitMessages, readFitStringField, readFitUnsignedField, } from './raw-message-reader.js';
15
+ function hasFitHeader(blob, start) {
16
+ const headerLength = blob[start];
17
+ return blob.length - start >= headerLength
18
+ && (headerLength === 12 || headerLength === 14)
19
+ && blob[start + 8] === 0x2E
20
+ && blob[start + 9] === 0x46
21
+ && blob[start + 10] === 0x49
22
+ && blob[start + 11] === 0x54;
23
+ }
6
24
  export default class FitParser {
7
25
  constructor(options = {}) {
8
- var _a, _b, _c, _d;
26
+ var _a, _b, _c, _d, _e;
9
27
  this.options = {
10
28
  force: options.force != null ? options.force : true,
29
+ strict: (_a = options.strict) !== null && _a !== void 0 ? _a : false,
11
30
  speedUnit: options.speedUnit || 'm/s',
12
31
  lengthUnit: options.lengthUnit || 'm',
13
32
  temperatureUnit: options.temperatureUnit || 'celsius',
14
33
  elapsedRecordField: options.elapsedRecordField || false,
15
34
  pressureUnit: options.pressureUnit || 'bar',
16
35
  mode: options.mode || 'list',
17
- includeRawDeveloperFields: (_a = options.includeRawDeveloperFields) !== null && _a !== void 0 ? _a : false,
18
- includeRawMessages: (_b = options.includeRawMessages) !== null && _b !== void 0 ? _b : false,
19
- includeUnmappedMessages: (_c = options.includeUnmappedMessages) !== null && _c !== void 0 ? _c : false,
20
- rawMessagesOnly: (_d = options.rawMessagesOnly) !== null && _d !== void 0 ? _d : false,
36
+ includeRawDeveloperFields: (_b = options.includeRawDeveloperFields) !== null && _b !== void 0 ? _b : false,
37
+ includeRawMessages: (_c = options.includeRawMessages) !== null && _c !== void 0 ? _c : false,
38
+ includeUnmappedMessages: (_d = options.includeUnmappedMessages) !== null && _d !== void 0 ? _d : false,
39
+ rawMessagesOnly: (_e = options.rawMessagesOnly) !== null && _e !== void 0 ? _e : false,
21
40
  };
22
41
  }
23
42
  parseAsync(content) {
@@ -32,7 +51,81 @@ export default class FitParser {
32
51
  });
33
52
  });
34
53
  }
54
+ /**
55
+ * Parses FIT files in input order with independent decoder state. Uses the
56
+ * same force and strict policies as parseAsync; strict mode rejects trailing
57
+ * bytes, while the default ignores a non-FIT suffix after parsed files.
58
+ * Rejects with an Error when any file cannot be parsed.
59
+ */
60
+ parseFilesAsync(content) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const blob = content instanceof ArrayBuffer
63
+ ? new Uint8Array(content)
64
+ : new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
65
+ const results = [];
66
+ let start = 0;
67
+ do {
68
+ const remaining = blob.length - start;
69
+ const headerLength = blob[start];
70
+ if (results.length > 0 && !this.options.strict && !hasFitHeader(blob, start)) {
71
+ break;
72
+ }
73
+ if (remaining < 12) {
74
+ throw new Error('File to small to be a FIT file');
75
+ }
76
+ if (headerLength !== 12 && headerLength !== 14) {
77
+ throw new Error('Incorrect header size');
78
+ }
79
+ if (remaining < headerLength) {
80
+ throw new Error('File to small to be a FIT file');
81
+ }
82
+ if (blob[start + 8] !== 0x2E
83
+ || blob[start + 9] !== 0x46
84
+ || blob[start + 10] !== 0x49
85
+ || blob[start + 11] !== 0x54) {
86
+ throw new Error('Missing \'.FIT\' in header');
87
+ }
88
+ const dataLength = new DataView(blob.buffer, blob.byteOffset + start, remaining)
89
+ .getUint32(4, true);
90
+ const crcStart = start + headerLength + dataLength;
91
+ // File completeness and CRC checks belong to the shared parser below,
92
+ // preserving the same validation policy and error order in every API.
93
+ let end = Math.min(crcStart + 2, blob.length);
94
+ // Missing CRC bytes can also precede the next file. Check only the
95
+ // declared boundary and its one-byte CRC variant; never scan activity
96
+ // payloads for a guessed file boundary.
97
+ if (this.options.force && !this.options.strict) {
98
+ if (hasFitHeader(blob, crcStart)) {
99
+ end = crcStart;
100
+ }
101
+ else if (hasFitHeader(blob, crcStart + 1)) {
102
+ end = crcStart + 1;
103
+ }
104
+ }
105
+ try {
106
+ results.push(yield new Promise((resolve, reject) => {
107
+ this.parseFile(blob.slice(start, end).buffer, (error, data) => {
108
+ if (error) {
109
+ reject(error);
110
+ }
111
+ else if (data) {
112
+ resolve(data);
113
+ }
114
+ });
115
+ }));
116
+ }
117
+ catch (error) {
118
+ throw error instanceof Error ? error : new Error(String(error));
119
+ }
120
+ start = end;
121
+ } while (start < blob.length);
122
+ return results;
123
+ });
124
+ }
35
125
  parse(content, callback) {
126
+ this.parseFile(content, callback);
127
+ }
128
+ parseFile(content, callback) {
36
129
  var _a, _b, _c;
37
130
  const blob = content instanceof ArrayBuffer
38
131
  ? new Uint8Array(content)
@@ -75,10 +168,14 @@ export default class FitParser {
75
168
  callback('File data exceeds input length', undefined);
76
169
  return;
77
170
  }
78
- if (!this.options.force && crcStart + 2 > blob.length) {
171
+ if ((!this.options.force || this.options.strict) && crcStart + 2 > blob.length) {
79
172
  callback('File CRC missing', undefined);
80
173
  return;
81
174
  }
175
+ if (this.options.strict && crcStart + 2 < blob.length) {
176
+ callback('Unexpected bytes after FIT file', undefined);
177
+ return;
178
+ }
82
179
  if (!this.options.force) {
83
180
  const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
84
181
  const crcFileCalc = calculateCRC(blob, 0, crcStart);
@@ -92,6 +189,7 @@ export default class FitParser {
92
189
  protocolVersion,
93
190
  };
94
191
  const messages = {};
192
+ const batteries = [];
95
193
  let sessions = [];
96
194
  let laps = [];
97
195
  const records = [];
@@ -141,6 +239,10 @@ export default class FitParser {
141
239
  let pausedTime = 0;
142
240
  while (loopIndex < crcStart) {
143
241
  const { globalMessageNumber, littleEndian, message, messageType, nextIndex, compressedTimestamp, rawFields: recordRawFields, rawDeveloperFields: recordRawDeveloperFields, unmappedFields: recordUnmappedFields, unmappedDeveloperFields: recordUnmappedDeveloperFields, } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime, dataView, decoderState, crcStart);
242
+ if (this.options.strict
243
+ && (!Number.isInteger(nextIndex) || nextIndex <= loopIndex || nextIndex > crcStart)) {
244
+ throw new Error('Invalid FIT record bounds');
245
+ }
144
246
  loopIndex = nextIndex;
145
247
  if (globalMessageNumber !== undefined) {
146
248
  const messageIndex = (_a = messageCountsByGlobalNumber.get(globalMessageNumber)) !== null && _a !== void 0 ? _a : 0;
@@ -191,6 +293,9 @@ export default class FitParser {
191
293
  ((_b = messages[messageType]) !== null && _b !== void 0 ? _b : (messages[messageType] = [])).push(message);
192
294
  }
193
295
  switch (messageType) {
296
+ case 'battery':
297
+ batteries.push(message);
298
+ break;
194
299
  case 'lap':
195
300
  laps.push(message);
196
301
  break;
@@ -335,6 +440,7 @@ export default class FitParser {
335
440
  fitObj.time_in_zone = time_in_zone;
336
441
  fitObj.activity_metrics = activity_metrics;
337
442
  fitObj.user_metrics = user_metrics;
443
+ fitObj.batteries = batteries;
338
444
  fitObj.messages = messages;
339
445
  if (rawDeveloperFields) {
340
446
  fitObj.raw_developer_fields = rawDeveloperFields;
@@ -156,7 +156,7 @@ export type MaxMetCategory = 'generic' | 'cycling' | number;
156
156
  export type MaxMetHeartRateSource = 'whr' | 'hrm' | number;
157
157
  export type MaxMetSpeedSource = 'onboard_gps' | 'connected_gps' | 'cadence' | number;
158
158
  export type MesgCount = 'num_per_file' | 'max_per_file' | 'max_per_file_type' | number;
159
- 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' | 'training_settings' | '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' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
159
+ 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' | 'training_settings' | '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' | 'battery' | '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' | 'ohr_settings' | 'exd_screen_configuration' | 'exd_data_field_configuration' | 'exd_data_concept_configuration' | 'field_description' | 'developer_data_id' | 'magnetometer_data' | 'barometer_data' | 'one_d_sensor_calibration' | 'monitoring_hr_data' | 'time_in_zone' | 'set' | 'stress_level' | 'max_met_data' | 'dive_settings' | 'dive_gas' | 'dive_alarm' | 'exercise_title' | 'dive_summary' | 'spo2_data' | 'sleep_level' | 'jump' | 'aad_accel_features' | 'beat_intervals' | 'respiration_rate' | 'hsa_accelerometer_data' | 'hsa_step_data' | 'hsa_spo2_data' | 'hsa_stress_data' | 'hsa_respiration_data' | 'hsa_heart_rate_data' | 'split' | 'split_summary' | 'hsa_body_battery_data' | 'hsa_event' | 'climb_pro' | 'tank_update' | 'tank_summary' | 'sleep_assessment' | 'hrv_status_summary' | 'hrv_value' | 'raw_bbi' | 'device_aux_battery_info' | 'hsa_gyroscope_data' | 'chrono_shot_session' | 'chrono_shot_data' | 'hsa_configuration_data' | 'dive_apnea_alarm' | 'skin_temp_overnight' | 'hsa_wrist_temperature_data' | 'nap_event' | 'sleep_disruption_severity_period' | 'sleep_disruption_overnight_severity' | 'mfg_range_min' | 'mfg_range_max' | 'definition' | number;
160
160
  export interface MessageIndex {
161
161
  value: number;
162
162
  reserved: boolean;
@@ -498,6 +498,8 @@ export interface ParsedSession {
498
498
  best_lap_index?: number;
499
499
  min_altitude?: number;
500
500
  active_time?: number;
501
+ avg_strokes_per_length?: number;
502
+ avg_swolf?: number;
501
503
  player_score?: number;
502
504
  opponent_score?: number;
503
505
  opponent_name?: string;
@@ -522,6 +524,9 @@ export interface ParsedSession {
522
524
  avg_left_pedal_smoothness?: number;
523
525
  avg_right_pedal_smoothness?: number;
524
526
  avg_combined_pedal_smoothness?: number;
527
+ num_segment_laps?: number;
528
+ front_gear_shift_count?: number;
529
+ rear_gear_shift_count?: number;
525
530
  sport_profile_name?: string;
526
531
  sport_index?: number;
527
532
  time_standing?: number;
@@ -588,6 +593,7 @@ export interface ParsedSession {
588
593
  avg_core_temperature?: number;
589
594
  min_core_temperature?: number;
590
595
  max_core_temperature?: number;
596
+ avg_grade_adjusted_speed?: number;
591
597
  timestamp?: Date;
592
598
  message_index?: MessageIndex;
593
599
  laps?: ParsedLap[];
@@ -620,6 +626,10 @@ export interface ParsedLap {
620
626
  lap_trigger?: LapTrigger;
621
627
  sport?: Sport;
622
628
  event_group?: number;
629
+ nec_lat?: number;
630
+ nec_long?: number;
631
+ swc_lat?: number;
632
+ swc_long?: number;
623
633
  num_lengths?: number;
624
634
  normalized_power?: number;
625
635
  left_right_balance?: LeftRightBalance100;
@@ -653,6 +663,7 @@ export interface ParsedLap {
653
663
  min_heart_rate?: number;
654
664
  active_time?: number;
655
665
  wkt_step_index?: MessageIndex;
666
+ avg_swolf?: number;
656
667
  opponent_score?: number;
657
668
  stroke_count?: (number | null)[];
658
669
  zone_count?: (number | null)[];
@@ -669,11 +680,14 @@ export interface ParsedLap {
669
680
  avg_saturated_hemoglobin_percent?: (number | null)[];
670
681
  min_saturated_hemoglobin_percent?: (number | null)[];
671
682
  max_saturated_hemoglobin_percent?: (number | null)[];
683
+ avg_strokes_per_length?: number;
672
684
  avg_left_torque_effectiveness?: number;
673
685
  avg_right_torque_effectiveness?: number;
674
686
  avg_left_pedal_smoothness?: number;
675
687
  avg_right_pedal_smoothness?: number;
676
688
  avg_combined_pedal_smoothness?: number;
689
+ front_gear_shift_count?: number;
690
+ rear_gear_shift_count?: number;
677
691
  time_standing?: number;
678
692
  stand_count?: number;
679
693
  avg_left_pco?: number;
@@ -710,11 +724,13 @@ export interface ParsedLap {
710
724
  jump_count?: number;
711
725
  avg_grit?: number;
712
726
  avg_flow?: number;
727
+ metabolic_calories?: number;
713
728
  total_fractional_ascent?: number;
714
729
  total_fractional_descent?: number;
715
730
  avg_core_temperature?: number;
716
731
  min_core_temperature?: number;
717
732
  max_core_temperature?: number;
733
+ avg_grade_adjusted_speed?: number;
718
734
  timestamp?: Date;
719
735
  message_index?: MessageIndex;
720
736
  records?: ParsedRecord[];
@@ -1119,6 +1135,10 @@ export interface ParsedMonitoringInfo {
1119
1135
  resting_metabolic_rate?: number;
1120
1136
  timestamp?: Date;
1121
1137
  }
1138
+ export interface ParsedBattery {
1139
+ capacity?: number;
1140
+ timestamp?: Date;
1141
+ }
1122
1142
  export interface ParsedPad {
1123
1143
  }
1124
1144
  export interface ParsedSlaveDevice {
@@ -2007,6 +2027,7 @@ export interface ParsedMessages {
2007
2027
  ant_channel_id?: ParsedAntChannelId[];
2008
2028
  length?: ParsedLength[];
2009
2029
  monitoring_info?: ParsedMonitoringInfo[];
2030
+ battery?: ParsedBattery[];
2010
2031
  pad?: ParsedPad[];
2011
2032
  slave_device?: ParsedSlaveDevice[];
2012
2033
  connectivity?: ParsedConnectivity[];
@@ -2134,6 +2155,7 @@ export interface ParsedFit {
2134
2155
  time_in_zone?: ParsedTimeInZone[];
2135
2156
  activity_metrics?: ParsedActivityMetrics[];
2136
2157
  user_metrics?: ParsedUserMetrics[];
2158
+ batteries?: ParsedBattery[];
2137
2159
  capabilities?: ParsedCapabilities;
2138
2160
  hrm_profile?: ParsedHrmProfile;
2139
2161
  sdm_profile?: ParsedSdmProfile;
package/dist/profile.js CHANGED
@@ -1345,6 +1345,60 @@ export const PROFILE_MESSAGES = {
1345
1345
  name: 'goal',
1346
1346
  },
1347
1347
  18: {
1348
+ 79: {
1349
+ field: 'avg_strokes_per_length',
1350
+ type: 'uint16',
1351
+ baseType: 'uint16',
1352
+ array: false,
1353
+ scale: 10,
1354
+ offset: 0,
1355
+ units: 'strokes/length',
1356
+ },
1357
+ 80: {
1358
+ field: 'avg_swolf',
1359
+ type: 'uint16',
1360
+ baseType: 'uint16',
1361
+ array: false,
1362
+ scale: 1,
1363
+ offset: 0,
1364
+ units: '',
1365
+ },
1366
+ 106: {
1367
+ field: 'num_segment_laps',
1368
+ type: 'uint16',
1369
+ baseType: 'uint16',
1370
+ array: false,
1371
+ scale: 1,
1372
+ offset: 0,
1373
+ units: '',
1374
+ },
1375
+ 107: {
1376
+ field: 'front_gear_shift_count',
1377
+ type: 'uint16',
1378
+ baseType: 'uint16',
1379
+ array: false,
1380
+ scale: 1,
1381
+ offset: 0,
1382
+ units: '',
1383
+ },
1384
+ 108: {
1385
+ field: 'rear_gear_shift_count',
1386
+ type: 'uint16',
1387
+ baseType: 'uint16',
1388
+ array: false,
1389
+ scale: 1,
1390
+ offset: 0,
1391
+ units: '',
1392
+ },
1393
+ 211: {
1394
+ field: 'avg_grade_adjusted_speed',
1395
+ type: 'uint32',
1396
+ baseType: 'uint32',
1397
+ array: false,
1398
+ scale: 1000,
1399
+ offset: 0,
1400
+ units: 'm/s',
1401
+ },
1348
1402
  0: {
1349
1403
  field: 'event',
1350
1404
  type: 'event',
@@ -2805,6 +2859,96 @@ export const PROFILE_MESSAGES = {
2805
2859
  name: 'session',
2806
2860
  },
2807
2861
  19: {
2862
+ 27: {
2863
+ field: 'nec_lat',
2864
+ type: 'sint32',
2865
+ baseType: 'sint32',
2866
+ array: false,
2867
+ scale: 1,
2868
+ offset: 0,
2869
+ units: 'semicircles',
2870
+ },
2871
+ 28: {
2872
+ field: 'nec_long',
2873
+ type: 'sint32',
2874
+ baseType: 'sint32',
2875
+ array: false,
2876
+ scale: 1,
2877
+ offset: 0,
2878
+ units: 'semicircles',
2879
+ },
2880
+ 29: {
2881
+ field: 'swc_lat',
2882
+ type: 'sint32',
2883
+ baseType: 'sint32',
2884
+ array: false,
2885
+ scale: 1,
2886
+ offset: 0,
2887
+ units: 'semicircles',
2888
+ },
2889
+ 30: {
2890
+ field: 'swc_long',
2891
+ type: 'sint32',
2892
+ baseType: 'sint32',
2893
+ array: false,
2894
+ scale: 1,
2895
+ offset: 0,
2896
+ units: 'semicircles',
2897
+ },
2898
+ 73: {
2899
+ field: 'avg_swolf',
2900
+ type: 'uint16',
2901
+ baseType: 'uint16',
2902
+ array: false,
2903
+ scale: 1,
2904
+ offset: 0,
2905
+ units: '',
2906
+ },
2907
+ 90: {
2908
+ field: 'avg_strokes_per_length',
2909
+ type: 'uint16',
2910
+ baseType: 'uint16',
2911
+ array: false,
2912
+ scale: 10,
2913
+ offset: 0,
2914
+ units: 'strokes / length',
2915
+ },
2916
+ 96: {
2917
+ field: 'front_gear_shift_count',
2918
+ type: 'uint16',
2919
+ baseType: 'uint16',
2920
+ array: false,
2921
+ scale: 1,
2922
+ offset: 0,
2923
+ units: '',
2924
+ },
2925
+ 97: {
2926
+ field: 'rear_gear_shift_count',
2927
+ type: 'uint16',
2928
+ baseType: 'uint16',
2929
+ array: false,
2930
+ scale: 1,
2931
+ offset: 0,
2932
+ units: '',
2933
+ },
2934
+ 155: {
2935
+ field: 'metabolic_calories',
2936
+ type: 'uint16',
2937
+ baseType: 'uint16',
2938
+ array: false,
2939
+ scale: 1,
2940
+ offset: 0,
2941
+ units: 'kcal',
2942
+ },
2943
+ 161: {
2944
+ field: 'avg_grade_adjusted_speed',
2945
+ type: 'uint32',
2946
+ baseType: 'uint32',
2947
+ array: false,
2948
+ scale: 1000,
2949
+ offset: 0,
2950
+ units: 'm/s',
2951
+ },
2808
2952
  0: {
2809
2953
  field: 'event',
2810
2954
  type: 'event',
@@ -6983,6 +7127,27 @@ export const PROFILE_MESSAGES = {
6983
7127
  },
6984
7128
  name: 'monitoring_info',
6985
7129
  },
7130
+ 104: {
7131
+ 2: {
7132
+ field: 'capacity',
7133
+ type: 'uint8',
7134
+ baseType: 'uint8',
7135
+ array: false,
7136
+ scale: 1,
7137
+ offset: 0,
7138
+ units: '%',
7139
+ },
7140
+ 253: {
7141
+ field: 'timestamp',
7142
+ type: 'date_time',
7143
+ baseType: 'uint32',
7144
+ array: false,
7145
+ scale: 1,
7146
+ offset: 0,
7147
+ units: '',
7148
+ },
7149
+ name: 'battery',
7150
+ },
6986
7151
  105: {
6987
7152
  name: 'pad',
6988
7153
  },
@@ -15263,6 +15428,7 @@ export const PROFILE_TYPES = {
15263
15428
  82: 'ant_channel_id',
15264
15429
  101: 'length',
15265
15430
  103: 'monitoring_info',
15431
+ 104: 'battery',
15266
15432
  105: 'pad',
15267
15433
  106: 'slave_device',
15268
15434
  127: 'connectivity',
@@ -240,6 +240,7 @@ export function generateFitType() {
240
240
  time_in_zone: 'parsed_time_in_zone',
241
241
  activity_metrics: 'parsed_activity_metrics',
242
242
  user_metrics: 'parsed_user_metrics',
243
+ batteries: 'parsed_battery',
243
244
  };
244
245
  const referenceProperties = {
245
246
  file_creator: 'parsed_file_creator',
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "fit-file-parser",
3
3
  "type": "module",
4
- "version": "6.1.2",
4
+ "version": "6.2.1",
5
5
  "private": false,
6
- "description": "Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)",
6
+ "description": "JavaScript and TypeScript library for parsing and encoding FIT files, with developer-field support, raw message access, and ESM/CommonJS builds.",
7
7
  "author": {
8
8
  "name": "jimmykane"
9
9
  },
@@ -24,13 +24,22 @@
24
24
  "url": "git+ssh://git@github.com/jimmykane/fit-parser.git"
25
25
  },
26
26
  "bugs": {
27
- "url": "https://github.com/jimmykane/fit-parser"
27
+ "url": "https://github.com/jimmykane/fit-parser/issues"
28
28
  },
29
29
  "keywords": [
30
30
  "fit",
31
- "ant",
31
+ "fit-file",
32
+ "fit-parser",
33
+ "fit-file-parsing",
34
+ "fit-encoder",
35
+ "javascript",
36
+ "typescript",
37
+ "nodejs",
38
+ "fitness",
32
39
  "garmin",
33
- "parse"
40
+ "polar",
41
+ "suunto",
42
+ "binary-parser"
34
43
  ],
35
44
  "exports": {
36
45
  ".": {