fit-file-parser 1.20.0 → 2.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.
@@ -1,261 +1,241 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
-
7
- var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8
-
9
- var _binary = require('./binary');
10
-
11
- var _helper = require('./helper');
12
-
13
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
14
-
15
- var FitParser = function () {
16
- function FitParser() {
17
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
18
-
19
- _classCallCheck(this, FitParser);
20
-
21
- this.options = {
22
- force: options.force != null ? options.force : true,
23
- speedUnit: options.speedUnit || 'm/s',
24
- lengthUnit: options.lengthUnit || 'm',
25
- temperatureUnit: options.temperatureUnit || 'celsius',
26
- elapsedRecordField: options.elapsedRecordField || false,
27
- pressureUnit: options.pressureUnit || 'bar',
28
- mode: options.mode || 'list'
29
- };
30
- }
31
-
32
- _createClass(FitParser, [{
33
- key: 'parse',
34
- value: function parse(content, callback) {
35
- var blob = new Uint8Array((0, _binary.getArrayBuffer)(content));
36
-
37
- if (blob.length < 12) {
38
- callback('File to small to be a FIT file', {});
39
- if (!this.options.force) {
40
- return;
41
- }
42
- }
43
-
44
- var headerLength = blob[0];
45
- if (headerLength !== 14 && headerLength !== 12) {
46
- callback('Incorrect header size', {});
47
- if (!this.options.force) {
48
- return;
49
- }
50
- }
51
-
52
- var fileTypeString = '';
53
- for (var i = 8; i < 12; i++) {
54
- fileTypeString += String.fromCharCode(blob[i]);
55
- }
56
- if (fileTypeString !== '.FIT') {
57
- callback('Missing \'.FIT\' in header', {});
58
- if (!this.options.force) {
59
- return;
1
+ import { calculateCRC, getArrayBuffer, readRecord } from './binary.js';
2
+ import { mapDataIntoLap, mapDataIntoSession } from './helper.js';
3
+ export default class FitParser {
4
+ constructor(options = {}) {
5
+ this.options = {
6
+ force: options.force != null ? options.force : true,
7
+ speedUnit: options.speedUnit || 'm/s',
8
+ lengthUnit: options.lengthUnit || 'm',
9
+ temperatureUnit: options.temperatureUnit || 'celsius',
10
+ elapsedRecordField: options.elapsedRecordField || false,
11
+ pressureUnit: options.pressureUnit || 'bar',
12
+ mode: options.mode || 'list',
13
+ };
14
+ }
15
+ parseAsync(content) {
16
+ return new Promise((resolve, reject) => {
17
+ this.parse(content, (error, data) => {
18
+ if (error) {
19
+ reject(error);
20
+ }
21
+ else if (data) {
22
+ resolve(data);
23
+ }
24
+ });
25
+ });
26
+ }
27
+ parse(content, callback) {
28
+ var _a;
29
+ const blob = new Uint8Array(getArrayBuffer(content));
30
+ if (blob.length < 12) {
31
+ callback('File to small to be a FIT file', undefined);
32
+ if (!this.options.force) {
33
+ return;
34
+ }
60
35
  }
61
- }
62
-
63
- if (headerLength === 14) {
64
- var crcHeader = blob[12] + (blob[13] << 8);
65
- var crcHeaderCalc = (0, _binary.calculateCRC)(blob, 0, 12);
66
- if (crcHeader !== crcHeaderCalc) {
67
- // callback('Header CRC mismatch', {});
68
- // TODO: fix Header CRC check
69
- if (!this.options.force) {
70
- return;
71
- }
36
+ const headerLength = blob[0];
37
+ if (headerLength !== 14 && headerLength !== 12) {
38
+ callback('Incorrect header size', undefined);
39
+ if (!this.options.force) {
40
+ return;
41
+ }
72
42
  }
73
- }
74
-
75
- var protocolVersion = blob[1];
76
- var profileVersion = blob[2] + (blob[3] << 8);
77
- var dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);
78
- var crcStart = dataLength + headerLength;
79
- var crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
80
- var crcFileCalc = (0, _binary.calculateCRC)(blob, headerLength === 12 ? 0 : headerLength, crcStart);
81
-
82
- if (crcFile !== crcFileCalc) {
83
- // callback('File CRC mismatch', {});
84
- // TODO: fix File CRC check
85
- if (!this.options.force) {
86
- return;
43
+ let fileTypeString = '';
44
+ for (let i = 8; i < 12; i++) {
45
+ fileTypeString += String.fromCharCode(blob[i]);
87
46
  }
88
- }
89
-
90
- var fitObj = {};
91
- fitObj.protocolVersion = protocolVersion;
92
- fitObj.profileVersion = profileVersion;
93
-
94
- var sessions = [];
95
- var laps = [];
96
- var records = [];
97
- var events = [];
98
- var hrv = [];
99
- var devices = [];
100
- var applications = [];
101
- var fieldDescriptions = [];
102
- var dive_gases = [];
103
- var course_points = [];
104
- var sports = [];
105
- var monitors = [];
106
- var stress = [];
107
- var definitions = [];
108
- var file_ids = [];
109
- var monitor_info = [];
110
- var lengths = [];
111
- var tank_updates = [];
112
- var tank_summaries = [];
113
-
114
- var loopIndex = headerLength;
115
- var messageTypes = [];
116
- var developerFields = [];
117
-
118
- var isModeCascade = this.options.mode === 'cascade';
119
- var isCascadeNeeded = isModeCascade || this.options.mode === 'both';
120
-
121
- var startDate = void 0;
122
- var lastStopTimestamp = void 0;
123
- var pausedTime = 0;
124
-
125
- while (loopIndex < crcStart) {
126
- var _readRecord = (0, _binary.readRecord)(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime),
127
- nextIndex = _readRecord.nextIndex,
128
- messageType = _readRecord.messageType,
129
- message = _readRecord.message;
130
-
131
- loopIndex = nextIndex;
132
-
133
- switch (messageType) {
134
- case 'lap':
135
- laps.push(message);
136
- break;
137
- case 'session':
138
- sessions.push(message);
139
- break;
140
- case 'event':
141
- if (message.event === 'timer') {
142
- if (message.event_type === 'stop_all') {
143
- lastStopTimestamp = message.timestamp;
144
- } else if (message.event_type === 'start' && lastStopTimestamp) {
145
- pausedTime += (message.timestamp - lastStopTimestamp) / 1000;
146
- }
47
+ if (fileTypeString !== '.FIT') {
48
+ callback('Missing \'.FIT\' in header', undefined);
49
+ if (!this.options.force) {
50
+ return;
147
51
  }
148
- events.push(message);
149
- break;
150
- case 'length':
151
- lengths.push(message);
152
- break;
153
- case 'hrv':
154
- hrv.push(message);
155
- break;
156
- case 'record':
157
- if (!startDate) {
158
- startDate = message.timestamp;
159
- message.elapsed_time = 0;
160
- message.timer_time = 0;
161
- }
162
- records.push(message);
163
- break;
164
- case 'field_description':
165
- fieldDescriptions.push(message);
166
- break;
167
- case 'device_info':
168
- devices.push(message);
169
- break;
170
- case 'developer_data_id':
171
- applications.push(message);
172
- break;
173
- case 'dive_gas':
174
- dive_gases.push(message);
175
- break;
176
- case 'course_point':
177
- course_points.push(message);
178
- break;
179
- case 'sport':
180
- sports.push(message);
181
- break;
182
- case 'file_id':
183
- if (message) {
184
- file_ids.push(message);
52
+ }
53
+ if (headerLength === 14) {
54
+ const crcHeader = blob[12] + (blob[13] << 8);
55
+ const crcHeaderCalc = calculateCRC(blob, 0, 12);
56
+ if (crcHeader !== crcHeaderCalc) {
57
+ // callback('Header CRC mismatch', {});
58
+ // TODO: fix Header CRC check
59
+ if (!this.options.force) {
60
+ return;
61
+ }
185
62
  }
186
- break;
187
- case 'definition':
188
- if (message) {
189
- definitions.push(message);
63
+ }
64
+ const protocolVersion = blob[1];
65
+ const profileVersion = blob[2] + (blob[3] << 8);
66
+ const dataLength = blob[4] + (blob[5] << 8) + (blob[6] << 16) + (blob[7] << 24);
67
+ const crcStart = dataLength + headerLength;
68
+ const crcFile = blob[crcStart] + (blob[crcStart + 1] << 8);
69
+ const crcFileCalc = calculateCRC(blob, headerLength === 12 ? 0 : headerLength, crcStart);
70
+ if (crcFile !== crcFileCalc) {
71
+ // callback('File CRC mismatch', {});
72
+ // TODO: fix File CRC check
73
+ if (!this.options.force) {
74
+ return;
190
75
  }
191
- break;
192
- case 'monitoring':
193
- monitors.push(message);
194
- break;
195
- case 'monitoring_info':
196
- monitor_info.push(message);
197
- break;
198
- case 'stress_level':
199
- stress.push(message);
200
- break;
201
- case 'software':
202
- fitObj.software = message;
203
- break;
204
- case 'tank_update':
205
- tank_updates.push(message);
206
- break;
207
- case 'tank_summary':
208
- tank_summaries.push(message);
209
- break;
210
- default:
211
- if (messageType !== '') {
212
- fitObj[messageType] = message;
76
+ }
77
+ const fitObj = {
78
+ profileVersion,
79
+ protocolVersion,
80
+ };
81
+ let sessions = [];
82
+ let laps = [];
83
+ const records = [];
84
+ const events = [];
85
+ const hr_zone = [];
86
+ const power_zone = [];
87
+ const hrv = [];
88
+ const devices = [];
89
+ const applications = [];
90
+ const fieldDescriptions = [];
91
+ const dive_gases = [];
92
+ const course_points = [];
93
+ const sports = [];
94
+ const monitors = [];
95
+ const stress = [];
96
+ const definitions = [];
97
+ const file_ids = [];
98
+ const monitor_info = [];
99
+ const lengths = [];
100
+ const tank_updates = [];
101
+ const tank_summaries = [];
102
+ let loopIndex = headerLength;
103
+ const messageTypes = [];
104
+ const developerFields = [];
105
+ const isModeCascade = this.options.mode === 'cascade';
106
+ const isCascadeNeeded = isModeCascade || this.options.mode === 'both';
107
+ let startDate;
108
+ let lastStopTimestamp;
109
+ let pausedTime = 0;
110
+ while (loopIndex < crcStart) {
111
+ const { nextIndex, messageType, message } = readRecord(blob, messageTypes, developerFields, loopIndex, this.options, startDate, pausedTime);
112
+ loopIndex = nextIndex;
113
+ switch (messageType) {
114
+ case 'lap':
115
+ laps.push(message);
116
+ break;
117
+ case 'session':
118
+ sessions.push(message);
119
+ break;
120
+ case 'event':
121
+ if (message.event === 'timer') {
122
+ if (message.event_type === 'stop_all') {
123
+ lastStopTimestamp = message.timestamp;
124
+ }
125
+ else if (message.event_type === 'start' && lastStopTimestamp) {
126
+ pausedTime += (message.timestamp - lastStopTimestamp) / 1000;
127
+ }
128
+ }
129
+ events.push(message);
130
+ break;
131
+ case 'length':
132
+ lengths.push(message);
133
+ break;
134
+ case 'hrv':
135
+ hrv.push(message);
136
+ break;
137
+ case 'hr_zone':
138
+ hr_zone.push(message);
139
+ break;
140
+ case 'power_zone':
141
+ power_zone.push(message);
142
+ break;
143
+ case 'record':
144
+ if (!startDate) {
145
+ startDate = message.timestamp;
146
+ message.elapsed_time = 0;
147
+ message.timer_time = 0;
148
+ }
149
+ records.push(message);
150
+ break;
151
+ case 'field_description':
152
+ fieldDescriptions.push(message);
153
+ break;
154
+ case 'device_info':
155
+ devices.push(message);
156
+ break;
157
+ case 'developer_data_id':
158
+ applications.push(message);
159
+ break;
160
+ case 'dive_gas':
161
+ dive_gases.push(message);
162
+ break;
163
+ case 'course_point':
164
+ course_points.push(message);
165
+ break;
166
+ case 'sport':
167
+ sports.push(message);
168
+ break;
169
+ case 'file_id':
170
+ if (message) {
171
+ file_ids.push(message);
172
+ }
173
+ break;
174
+ case 'definition':
175
+ if (message) {
176
+ definitions.push(message);
177
+ }
178
+ break;
179
+ case 'monitoring':
180
+ monitors.push(message);
181
+ break;
182
+ case 'monitoring_info':
183
+ monitor_info.push(message);
184
+ break;
185
+ case 'stress_level':
186
+ stress.push(message);
187
+ break;
188
+ case 'software':
189
+ fitObj.software = message;
190
+ break;
191
+ case 'tank_update':
192
+ tank_updates.push(message);
193
+ break;
194
+ case 'tank_summary':
195
+ tank_summaries.push(message);
196
+ break;
197
+ default:
198
+ if (messageType !== '') {
199
+ fitObj[messageType] = message;
200
+ }
201
+ break;
213
202
  }
214
- break;
215
203
  }
216
- }
217
-
218
- if (isCascadeNeeded) {
219
- fitObj.activity = fitObj.activity || {};
220
- laps = (0, _helper.mapDataIntoLap)(laps, 'records', records);
221
- laps = (0, _helper.mapDataIntoLap)(laps, 'lengths', lengths);
222
- sessions = (0, _helper.mapDataIntoSession)(sessions, laps);
223
- fitObj.activity.sessions = sessions;
224
- fitObj.activity.events = events;
225
- fitObj.activity.hrv = hrv;
226
- fitObj.activity.device_infos = devices;
227
- fitObj.activity.developer_data_ids = applications;
228
- fitObj.activity.field_descriptions = fieldDescriptions;
229
- fitObj.activity.sports = sports;
230
- }
231
- if (!isModeCascade) {
232
- fitObj.sessions = sessions;
233
- fitObj.laps = laps;
234
- fitObj.lengths = lengths;
235
- fitObj.records = records;
236
- fitObj.events = events;
237
- fitObj.device_infos = devices;
238
- fitObj.developer_data_ids = applications;
239
- fitObj.field_descriptions = fieldDescriptions;
240
- fitObj.hrv = hrv;
241
- fitObj.dive_gases = dive_gases;
242
- fitObj.course_points = course_points;
243
- fitObj.sports = sports;
244
- fitObj.devices = devices;
245
- fitObj.monitors = monitors;
246
- fitObj.stress = stress;
247
- fitObj.file_ids = file_ids;
248
- fitObj.monitor_info = monitor_info;
249
- fitObj.definitions = definitions;
250
- fitObj.tank_updates = tank_updates;
251
- fitObj.tank_summaries = tank_summaries;
252
- }
253
-
254
- callback(null, fitObj);
204
+ if (isCascadeNeeded) {
205
+ laps = mapDataIntoLap(laps, 'records', records);
206
+ laps = mapDataIntoLap(laps, 'lengths', lengths);
207
+ sessions = mapDataIntoSession(sessions, laps);
208
+ fitObj.hr_zone = hr_zone;
209
+ fitObj.power_zone = power_zone;
210
+ fitObj.activity = Object.assign(Object.assign({}, ((_a = fitObj.activity) !== null && _a !== void 0 ? _a : {})), { // ugly but we assume the activity was parsed correctly with all other members correctly
211
+ sessions,
212
+ events,
213
+ hrv, device_infos: devices, developer_data_ids: applications, field_descriptions: fieldDescriptions, sports });
214
+ }
215
+ if (!isModeCascade) {
216
+ fitObj.sessions = sessions;
217
+ fitObj.laps = laps;
218
+ fitObj.lengths = lengths;
219
+ fitObj.records = records;
220
+ fitObj.events = events;
221
+ fitObj.device_infos = devices;
222
+ fitObj.developer_data_ids = applications;
223
+ fitObj.field_descriptions = fieldDescriptions;
224
+ fitObj.hrv = hrv;
225
+ fitObj.hr_zone = hr_zone;
226
+ fitObj.power_zone = power_zone;
227
+ fitObj.dive_gases = dive_gases;
228
+ fitObj.course_points = course_points;
229
+ fitObj.sports = sports;
230
+ fitObj.devices = devices;
231
+ fitObj.monitors = monitors;
232
+ fitObj.stress = stress;
233
+ fitObj.file_ids = file_ids;
234
+ fitObj.monitor_info = monitor_info;
235
+ fitObj.definitions = definitions;
236
+ fitObj.tank_updates = tank_updates;
237
+ fitObj.tank_summaries = tank_summaries;
238
+ }
239
+ callback(undefined, fitObj);
255
240
  }
256
- }]);
257
-
258
- return FitParser;
259
- }();
260
-
261
- exports.default = FitParser;
241
+ }
package/dist/fit.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ export type SpeedUnits = 'm/s' | 'mph' | 'km/h';
2
+ export type LengthUnits = 'm' | 'mi' | 'km';
3
+ export type TemperatureUnits = '°C' | 'kelvin' | 'fahrenheit';
4
+ export type PressureUnits = 'cbar' | 'bar' | 'psi';
5
+ export type MessageName = 'file_id' | 'file_creator' | 'timestamp_correlation' | 'o_hr_settings' | 'software' | 'slave_device' | 'capabilities' | 'file_capabilities' | 'mesg_capabilities' | 'field_capabilities' | 'device_settings' | 'user_profile' | 'hrm_profile' | 'sdm_profile' | 'bike_profile' | 'connectivity' | 'watchface_settings' | 'ohr_settings' | 'time_in_zone' | 'zones_target' | 'sport' | 'hr_zone' | 'speed_zone' | 'cadence_zone' | 'power_zone' | 'met_zone' | 'dive_settings' | 'dive_alarm' | 'dive_gas' | 'goal' | 'activity' | 'session' | 'lap' | 'length' | 'record' | 'event' | 'device_info' | 'device_aux_battery_info' | 'training_file' | 'weather_conditions' | 'weather_alert' | 'gps_metadata' | 'camera_event' | 'gyroscope_data' | 'accelerometer_data' | 'magnetometer_data' | 'barometer_data' | 'three_d_sensor_calibration' | 'one_d_sensor_calibration' | 'video_frame' | 'obdii_data' | 'nmea_sentence' | 'aviation_attitude' | 'video' | 'video_title' | 'video_description' | 'video_clip' | 'set' | 'jump' | 'course' | 'course_point' | 'segment_id' | 'segment_leaderboard_entry' | 'segment_point' | 'segment_lap' | 'segment_file' | 'workout' | 'workout_session' | 'workout_step' | 'exercise_title' | 'schedule' | 'totals' | 'weight_scale' | 'blood_pressure' | 'monitoring_info' | 'monitoring' | 'monitoring_hr_data' | 'hr' | 'memo_glob' | 'stress_level' | 'dive_summary' | 'hrv' | 'beat_intervals' | 'respiration_rate' | 'spo2_data' | 'sleep_level' | 'pad' | 'field_description' | 'developer_data_id' | 'definition' | 'tank_update' | 'tank_summary';
6
+ export type Unit<T extends string> = Record<T, {
7
+ multiplier: number;
8
+ offset: number;
9
+ }>;
10
+ export interface FieldDefinition {
11
+ type: string | number;
12
+ fDefNo: number;
13
+ size: number;
14
+ endianAbility: boolean;
15
+ littleEndian: boolean;
16
+ baseTypeNo: number;
17
+ name: string;
18
+ dataType: string;
19
+ scale?: number;
20
+ offset?: number;
21
+ developerDataIndex?: number;
22
+ isDeveloperField?: boolean;
23
+ }
24
+ export interface MessageObject {
25
+ field: string;
26
+ type: string;
27
+ scale: number | null;
28
+ offset: number;
29
+ units: string;
30
+ }
31
+ interface Message {
32
+ name: MessageName;
33
+ [fieldId: number]: MessageObject;
34
+ }
35
+ export interface FitTypeOptions {
36
+ speedUnits: Unit<SpeedUnits>;
37
+ lengthUnits: Unit<LengthUnits>;
38
+ temperatureUnits: Unit<TemperatureUnits>;
39
+ pressureUnits: Unit<PressureUnits>;
40
+ }
41
+ export interface FitType {
42
+ scConst: number;
43
+ options: FitTypeOptions;
44
+ messages: {
45
+ [messageId: number]: Message;
46
+ };
47
+ types: {
48
+ [typeName: string]: Record<number, string | number>;
49
+ };
50
+ }
51
+ export declare const FIT: FitType;
52
+ export {};