fit-file-parser 2.3.2 → 3.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.
- package/dist/binary.js +16 -6
- package/dist/cjs/binary.js +16 -6
- package/dist/cjs/fit-parser.js +5 -0
- package/dist/cjs/fit.js +104 -0
- package/dist/cjs/fit_types.d.ts +18 -1
- package/dist/cjs/type_generator.js +1 -0
- package/dist/fit-parser.js +5 -0
- package/dist/fit.js +104 -0
- package/dist/fit_types.d.ts +18 -1
- package/dist/type_generator.js +1 -0
- package/package.json +1 -1
package/dist/binary.js
CHANGED
|
@@ -128,7 +128,9 @@ function formatByType(data, type, scale, offset) {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
if (!values.includes('mask')) {
|
|
131
|
-
|
|
131
|
+
const typeMap = FIT.types[type];
|
|
132
|
+
const mapped = typeMap[String(data)];
|
|
133
|
+
return mapped === undefined ? data : mapped;
|
|
132
134
|
}
|
|
133
135
|
const dataItem = {};
|
|
134
136
|
for (const key in FIT.types[type]) {
|
|
@@ -188,6 +190,13 @@ function isInvalidValue(data, type) {
|
|
|
188
190
|
return false;
|
|
189
191
|
}
|
|
190
192
|
}
|
|
193
|
+
function isInvalidBaseTypeValue(data, baseTypeNo) {
|
|
194
|
+
if (Array.isArray(data)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
const baseType = FIT.types.fit_base_type[baseTypeNo];
|
|
198
|
+
return typeof baseType === 'string' ? isInvalidValue(data, baseType) : false;
|
|
199
|
+
}
|
|
191
200
|
function convertTo(data, unitsList, unitName) {
|
|
192
201
|
const options = FIT.options[unitsList];
|
|
193
202
|
const unit = options[unitName];
|
|
@@ -264,6 +273,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
264
273
|
}
|
|
265
274
|
}
|
|
266
275
|
export function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
|
|
276
|
+
var _a, _b;
|
|
267
277
|
const recordHeader = blob[startIndex];
|
|
268
278
|
let localMessageType = recordHeader & 15;
|
|
269
279
|
if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
|
|
@@ -299,7 +309,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
299
309
|
size: blob[fDefIndex + 1],
|
|
300
310
|
endianAbility: (baseType & 128) === 128,
|
|
301
311
|
littleEndian: lEnd,
|
|
302
|
-
baseTypeNo: baseType
|
|
312
|
+
baseTypeNo: baseType,
|
|
303
313
|
name: field,
|
|
304
314
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
305
315
|
};
|
|
@@ -321,7 +331,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
321
331
|
size,
|
|
322
332
|
endianAbility: (baseType & 128) === 128,
|
|
323
333
|
littleEndian: lEnd,
|
|
324
|
-
baseTypeNo: baseType
|
|
334
|
+
baseTypeNo: baseType,
|
|
325
335
|
name: devDef.field_name,
|
|
326
336
|
dataType: getFitMessageBaseType(baseType & 15),
|
|
327
337
|
scale: devDef.scale || 1,
|
|
@@ -357,7 +367,7 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
357
367
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
358
368
|
const fDef = messageType.fieldDefs[i];
|
|
359
369
|
const data = readData(blob, fDef, readDataFromIndex, options);
|
|
360
|
-
if (!isInvalidValue(data, fDef.type)) {
|
|
370
|
+
if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
361
371
|
rawFields.push({ fDef, data });
|
|
362
372
|
}
|
|
363
373
|
readDataFromIndex += fDef.size;
|
|
@@ -373,8 +383,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
373
383
|
if (fDef.isDeveloperField) {
|
|
374
384
|
const field = fDef.name;
|
|
375
385
|
const { type } = fDef;
|
|
376
|
-
const
|
|
377
|
-
const
|
|
386
|
+
const scale = (_a = fDef.scale) !== null && _a !== void 0 ? _a : null;
|
|
387
|
+
const offset = (_b = fDef.offset) !== null && _b !== void 0 ? _b : 0;
|
|
378
388
|
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
379
389
|
}
|
|
380
390
|
else {
|
package/dist/cjs/binary.js
CHANGED
|
@@ -134,7 +134,9 @@ function formatByType(data, type, scale, offset) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
if (!values.includes('mask')) {
|
|
137
|
-
|
|
137
|
+
const typeMap = fit_js_1.FIT.types[type];
|
|
138
|
+
const mapped = typeMap[String(data)];
|
|
139
|
+
return mapped === undefined ? data : mapped;
|
|
138
140
|
}
|
|
139
141
|
const dataItem = {};
|
|
140
142
|
for (const key in fit_js_1.FIT.types[type]) {
|
|
@@ -194,6 +196,13 @@ function isInvalidValue(data, type) {
|
|
|
194
196
|
return false;
|
|
195
197
|
}
|
|
196
198
|
}
|
|
199
|
+
function isInvalidBaseTypeValue(data, baseTypeNo) {
|
|
200
|
+
if (Array.isArray(data)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const baseType = fit_js_1.FIT.types.fit_base_type[baseTypeNo];
|
|
204
|
+
return typeof baseType === 'string' ? isInvalidValue(data, baseType) : false;
|
|
205
|
+
}
|
|
197
206
|
function convertTo(data, unitsList, unitName) {
|
|
198
207
|
const options = fit_js_1.FIT.options[unitsList];
|
|
199
208
|
const unit = options[unitName];
|
|
@@ -270,6 +279,7 @@ function applyOptions(data, field, options, fields) {
|
|
|
270
279
|
}
|
|
271
280
|
}
|
|
272
281
|
function readRecord(blob, messageTypes, developerFields, startIndex, options, startDate, pausedTime) {
|
|
282
|
+
var _a, _b;
|
|
273
283
|
const recordHeader = blob[startIndex];
|
|
274
284
|
let localMessageType = recordHeader & 15;
|
|
275
285
|
if ((recordHeader & CompressedHeaderMask) === CompressedHeaderMask) {
|
|
@@ -305,7 +315,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
305
315
|
size: blob[fDefIndex + 1],
|
|
306
316
|
endianAbility: (baseType & 128) === 128,
|
|
307
317
|
littleEndian: lEnd,
|
|
308
|
-
baseTypeNo: baseType
|
|
318
|
+
baseTypeNo: baseType,
|
|
309
319
|
name: field,
|
|
310
320
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
311
321
|
};
|
|
@@ -327,7 +337,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
327
337
|
size,
|
|
328
338
|
endianAbility: (baseType & 128) === 128,
|
|
329
339
|
littleEndian: lEnd,
|
|
330
|
-
baseTypeNo: baseType
|
|
340
|
+
baseTypeNo: baseType,
|
|
331
341
|
name: devDef.field_name,
|
|
332
342
|
dataType: (0, messages_js_1.getFitMessageBaseType)(baseType & 15),
|
|
333
343
|
scale: devDef.scale || 1,
|
|
@@ -363,7 +373,7 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
363
373
|
for (let i = 0; i < messageType.fieldDefs.length; i++) {
|
|
364
374
|
const fDef = messageType.fieldDefs[i];
|
|
365
375
|
const data = readData(blob, fDef, readDataFromIndex, options);
|
|
366
|
-
if (!isInvalidValue(data, fDef.type)) {
|
|
376
|
+
if (!isInvalidValue(data, fDef.type) && !isInvalidBaseTypeValue(data, fDef.baseTypeNo)) {
|
|
367
377
|
rawFields.push({ fDef, data });
|
|
368
378
|
}
|
|
369
379
|
readDataFromIndex += fDef.size;
|
|
@@ -379,8 +389,8 @@ function readRecord(blob, messageTypes, developerFields, startIndex, options, st
|
|
|
379
389
|
if (fDef.isDeveloperField) {
|
|
380
390
|
const field = fDef.name;
|
|
381
391
|
const { type } = fDef;
|
|
382
|
-
const
|
|
383
|
-
const
|
|
392
|
+
const scale = (_a = fDef.scale) !== null && _a !== void 0 ? _a : null;
|
|
393
|
+
const offset = (_b = fDef.offset) !== null && _b !== void 0 ? _b : 0;
|
|
384
394
|
fields[fDef.name] = applyOptions(formatByType(data, type, scale, offset), field, options, fields);
|
|
385
395
|
}
|
|
386
396
|
else {
|
package/dist/cjs/fit-parser.js
CHANGED
|
@@ -104,6 +104,7 @@ class FitParser {
|
|
|
104
104
|
const jumps = [];
|
|
105
105
|
const time_in_zone = [];
|
|
106
106
|
const activity_metrics = [];
|
|
107
|
+
const user_metrics = [];
|
|
107
108
|
let loopIndex = headerLength;
|
|
108
109
|
const messageTypes = [];
|
|
109
110
|
const developerFields = [];
|
|
@@ -208,6 +209,9 @@ class FitParser {
|
|
|
208
209
|
case 'activity_metrics':
|
|
209
210
|
activity_metrics.push(message);
|
|
210
211
|
break;
|
|
212
|
+
case 'user_metrics':
|
|
213
|
+
user_metrics.push(message);
|
|
214
|
+
break;
|
|
211
215
|
default:
|
|
212
216
|
if (messageType !== '') {
|
|
213
217
|
fitObj[messageType] = message;
|
|
@@ -230,6 +234,7 @@ class FitParser {
|
|
|
230
234
|
fitObj.jumps = jumps;
|
|
231
235
|
fitObj.time_in_zone = time_in_zone;
|
|
232
236
|
fitObj.activity_metrics = activity_metrics;
|
|
237
|
+
fitObj.user_metrics = user_metrics;
|
|
233
238
|
if (isCascadeNeeded) {
|
|
234
239
|
laps = (0, helper_js_1.mapDataIntoLap)(laps, 'records', records);
|
|
235
240
|
laps = (0, helper_js_1.mapDataIntoLap)(laps, 'lengths', lengths);
|
package/dist/cjs/fit.js
CHANGED
|
@@ -4134,6 +4134,109 @@ exports.FIT = {
|
|
|
4134
4134
|
units: 'counts',
|
|
4135
4135
|
},
|
|
4136
4136
|
},
|
|
4137
|
+
// Undocumented Garmin user metrics message. Observed in activity FIT files exported
|
|
4138
|
+
// from Garmin Connect and displayed by fitfileviewer as "User Metrics".
|
|
4139
|
+
79: {
|
|
4140
|
+
name: 'user_metrics',
|
|
4141
|
+
253: {
|
|
4142
|
+
field: 'timestamp',
|
|
4143
|
+
type: 'date_time',
|
|
4144
|
+
scale: null,
|
|
4145
|
+
offset: 0,
|
|
4146
|
+
units: '',
|
|
4147
|
+
},
|
|
4148
|
+
0: {
|
|
4149
|
+
field: 'vo2_max',
|
|
4150
|
+
type: 'uint16',
|
|
4151
|
+
scale: 1024 / 3.5,
|
|
4152
|
+
offset: 0,
|
|
4153
|
+
units: 'ml/kg/min',
|
|
4154
|
+
},
|
|
4155
|
+
1: {
|
|
4156
|
+
field: 'age',
|
|
4157
|
+
type: 'uint8',
|
|
4158
|
+
scale: null,
|
|
4159
|
+
offset: 0,
|
|
4160
|
+
units: 'years',
|
|
4161
|
+
},
|
|
4162
|
+
2: {
|
|
4163
|
+
field: 'height',
|
|
4164
|
+
type: 'uint8',
|
|
4165
|
+
scale: 100,
|
|
4166
|
+
offset: 0,
|
|
4167
|
+
units: 'm',
|
|
4168
|
+
},
|
|
4169
|
+
3: {
|
|
4170
|
+
field: 'weight',
|
|
4171
|
+
type: 'uint16',
|
|
4172
|
+
scale: 10,
|
|
4173
|
+
offset: 0,
|
|
4174
|
+
units: 'kg',
|
|
4175
|
+
},
|
|
4176
|
+
4: {
|
|
4177
|
+
field: 'gender',
|
|
4178
|
+
type: 'gender',
|
|
4179
|
+
scale: null,
|
|
4180
|
+
offset: 0,
|
|
4181
|
+
units: '',
|
|
4182
|
+
},
|
|
4183
|
+
6: {
|
|
4184
|
+
field: 'max_heart_rate',
|
|
4185
|
+
type: 'uint8',
|
|
4186
|
+
scale: null,
|
|
4187
|
+
offset: 0,
|
|
4188
|
+
units: 'bpm',
|
|
4189
|
+
},
|
|
4190
|
+
8: {
|
|
4191
|
+
field: 'remaining_recovery_time',
|
|
4192
|
+
type: 'uint16',
|
|
4193
|
+
scale: null,
|
|
4194
|
+
offset: 0,
|
|
4195
|
+
units: '',
|
|
4196
|
+
},
|
|
4197
|
+
11: {
|
|
4198
|
+
field: 'lthr',
|
|
4199
|
+
type: 'uint16',
|
|
4200
|
+
scale: null,
|
|
4201
|
+
offset: 0,
|
|
4202
|
+
units: 'bpm',
|
|
4203
|
+
},
|
|
4204
|
+
12: {
|
|
4205
|
+
field: 'ltpower',
|
|
4206
|
+
type: 'uint16',
|
|
4207
|
+
scale: null,
|
|
4208
|
+
offset: 0,
|
|
4209
|
+
units: 'watts',
|
|
4210
|
+
},
|
|
4211
|
+
13: {
|
|
4212
|
+
field: 'ltspeed',
|
|
4213
|
+
type: 'uint16',
|
|
4214
|
+
scale: 1000,
|
|
4215
|
+
offset: 0,
|
|
4216
|
+
units: 'm/s',
|
|
4217
|
+
},
|
|
4218
|
+
16: {
|
|
4219
|
+
field: 'start_of_activity',
|
|
4220
|
+
type: 'date_time',
|
|
4221
|
+
scale: null,
|
|
4222
|
+
offset: 0,
|
|
4223
|
+
units: '',
|
|
4224
|
+
},
|
|
4225
|
+
19: {
|
|
4226
|
+
field: 'first_vo2_max',
|
|
4227
|
+
type: 'uint32',
|
|
4228
|
+
scale: 65536 / 3.5,
|
|
4229
|
+
offset: 0,
|
|
4230
|
+
units: 'ml/kg/min',
|
|
4231
|
+
},
|
|
4232
|
+
35: {
|
|
4233
|
+
field: 'end_of_previous_activity',
|
|
4234
|
+
type: 'date_time',
|
|
4235
|
+
scale: null,
|
|
4236
|
+
offset: 0,
|
|
4237
|
+
units: '',
|
|
4238
|
+
},
|
|
4239
|
+
},
|
|
4137
4240
|
103: {
|
|
4138
4241
|
name: 'monitoring_info',
|
|
4139
4242
|
253: {
|
|
@@ -4930,6 +5033,7 @@ exports.FIT = {
|
|
|
4930
5033
|
55: 'monitoring',
|
|
4931
5034
|
72: 'training_file',
|
|
4932
5035
|
78: 'hrv',
|
|
5036
|
+
79: 'user_metrics',
|
|
4933
5037
|
80: 'ant_rx',
|
|
4934
5038
|
81: 'ant_tx',
|
|
4935
5039
|
82: 'ant_channel_id',
|
package/dist/cjs/fit_types.d.ts
CHANGED
|
@@ -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' | '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';
|
|
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' | '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' | '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';
|
|
@@ -841,6 +841,22 @@ export interface ParsedMonitoring {
|
|
|
841
841
|
export interface ParsedHrv {
|
|
842
842
|
time?: number[];
|
|
843
843
|
}
|
|
844
|
+
export interface ParsedUserMetrics {
|
|
845
|
+
vo2_max?: number;
|
|
846
|
+
age?: number;
|
|
847
|
+
height?: number;
|
|
848
|
+
weight?: number;
|
|
849
|
+
gender?: Gender;
|
|
850
|
+
max_heart_rate?: number;
|
|
851
|
+
remaining_recovery_time?: number;
|
|
852
|
+
lthr?: number;
|
|
853
|
+
ltpower?: number;
|
|
854
|
+
ltspeed?: number;
|
|
855
|
+
start_of_activity?: string;
|
|
856
|
+
first_vo2_max?: number;
|
|
857
|
+
end_of_previous_activity?: string;
|
|
858
|
+
timestamp: string;
|
|
859
|
+
}
|
|
844
860
|
export interface ParsedLength {
|
|
845
861
|
event?: Event;
|
|
846
862
|
event_type?: EventType;
|
|
@@ -1040,4 +1056,5 @@ export interface ParsedFit {
|
|
|
1040
1056
|
jumps?: ParsedJump[];
|
|
1041
1057
|
time_in_zone?: ParsedTimeInZone[];
|
|
1042
1058
|
activity_metrics?: ParsedActivityMetrics[];
|
|
1059
|
+
user_metrics?: ParsedUserMetrics[];
|
|
1043
1060
|
}
|
|
@@ -211,6 +211,7 @@ function generateFitType() {
|
|
|
211
211
|
jumps: 'parsed_jump',
|
|
212
212
|
time_in_zone: 'parsed_time_in_zone',
|
|
213
213
|
activity_metrics: 'parsed_activity_metrics',
|
|
214
|
+
user_metrics: 'parsed_user_metrics',
|
|
214
215
|
};
|
|
215
216
|
const referenceProperties = {
|
|
216
217
|
file_creator: 'parsed_file_creator',
|
package/dist/fit-parser.js
CHANGED
|
@@ -102,6 +102,7 @@ export default class FitParser {
|
|
|
102
102
|
const jumps = [];
|
|
103
103
|
const time_in_zone = [];
|
|
104
104
|
const activity_metrics = [];
|
|
105
|
+
const user_metrics = [];
|
|
105
106
|
let loopIndex = headerLength;
|
|
106
107
|
const messageTypes = [];
|
|
107
108
|
const developerFields = [];
|
|
@@ -206,6 +207,9 @@ export default class FitParser {
|
|
|
206
207
|
case 'activity_metrics':
|
|
207
208
|
activity_metrics.push(message);
|
|
208
209
|
break;
|
|
210
|
+
case 'user_metrics':
|
|
211
|
+
user_metrics.push(message);
|
|
212
|
+
break;
|
|
209
213
|
default:
|
|
210
214
|
if (messageType !== '') {
|
|
211
215
|
fitObj[messageType] = message;
|
|
@@ -228,6 +232,7 @@ export default class FitParser {
|
|
|
228
232
|
fitObj.jumps = jumps;
|
|
229
233
|
fitObj.time_in_zone = time_in_zone;
|
|
230
234
|
fitObj.activity_metrics = activity_metrics;
|
|
235
|
+
fitObj.user_metrics = user_metrics;
|
|
231
236
|
if (isCascadeNeeded) {
|
|
232
237
|
laps = mapDataIntoLap(laps, 'records', records);
|
|
233
238
|
laps = mapDataIntoLap(laps, 'lengths', lengths);
|
package/dist/fit.js
CHANGED
|
@@ -4131,6 +4131,109 @@ export const FIT = {
|
|
|
4131
4131
|
units: 'counts',
|
|
4132
4132
|
},
|
|
4133
4133
|
},
|
|
4134
|
+
// Undocumented Garmin user metrics message. Observed in activity FIT files exported
|
|
4135
|
+
// from Garmin Connect and displayed by fitfileviewer as "User Metrics".
|
|
4136
|
+
79: {
|
|
4137
|
+
name: 'user_metrics',
|
|
4138
|
+
253: {
|
|
4139
|
+
field: 'timestamp',
|
|
4140
|
+
type: 'date_time',
|
|
4141
|
+
scale: null,
|
|
4142
|
+
offset: 0,
|
|
4143
|
+
units: '',
|
|
4144
|
+
},
|
|
4145
|
+
0: {
|
|
4146
|
+
field: 'vo2_max',
|
|
4147
|
+
type: 'uint16',
|
|
4148
|
+
scale: 1024 / 3.5,
|
|
4149
|
+
offset: 0,
|
|
4150
|
+
units: 'ml/kg/min',
|
|
4151
|
+
},
|
|
4152
|
+
1: {
|
|
4153
|
+
field: 'age',
|
|
4154
|
+
type: 'uint8',
|
|
4155
|
+
scale: null,
|
|
4156
|
+
offset: 0,
|
|
4157
|
+
units: 'years',
|
|
4158
|
+
},
|
|
4159
|
+
2: {
|
|
4160
|
+
field: 'height',
|
|
4161
|
+
type: 'uint8',
|
|
4162
|
+
scale: 100,
|
|
4163
|
+
offset: 0,
|
|
4164
|
+
units: 'm',
|
|
4165
|
+
},
|
|
4166
|
+
3: {
|
|
4167
|
+
field: 'weight',
|
|
4168
|
+
type: 'uint16',
|
|
4169
|
+
scale: 10,
|
|
4170
|
+
offset: 0,
|
|
4171
|
+
units: 'kg',
|
|
4172
|
+
},
|
|
4173
|
+
4: {
|
|
4174
|
+
field: 'gender',
|
|
4175
|
+
type: 'gender',
|
|
4176
|
+
scale: null,
|
|
4177
|
+
offset: 0,
|
|
4178
|
+
units: '',
|
|
4179
|
+
},
|
|
4180
|
+
6: {
|
|
4181
|
+
field: 'max_heart_rate',
|
|
4182
|
+
type: 'uint8',
|
|
4183
|
+
scale: null,
|
|
4184
|
+
offset: 0,
|
|
4185
|
+
units: 'bpm',
|
|
4186
|
+
},
|
|
4187
|
+
8: {
|
|
4188
|
+
field: 'remaining_recovery_time',
|
|
4189
|
+
type: 'uint16',
|
|
4190
|
+
scale: null,
|
|
4191
|
+
offset: 0,
|
|
4192
|
+
units: '',
|
|
4193
|
+
},
|
|
4194
|
+
11: {
|
|
4195
|
+
field: 'lthr',
|
|
4196
|
+
type: 'uint16',
|
|
4197
|
+
scale: null,
|
|
4198
|
+
offset: 0,
|
|
4199
|
+
units: 'bpm',
|
|
4200
|
+
},
|
|
4201
|
+
12: {
|
|
4202
|
+
field: 'ltpower',
|
|
4203
|
+
type: 'uint16',
|
|
4204
|
+
scale: null,
|
|
4205
|
+
offset: 0,
|
|
4206
|
+
units: 'watts',
|
|
4207
|
+
},
|
|
4208
|
+
13: {
|
|
4209
|
+
field: 'ltspeed',
|
|
4210
|
+
type: 'uint16',
|
|
4211
|
+
scale: 1000,
|
|
4212
|
+
offset: 0,
|
|
4213
|
+
units: 'm/s',
|
|
4214
|
+
},
|
|
4215
|
+
16: {
|
|
4216
|
+
field: 'start_of_activity',
|
|
4217
|
+
type: 'date_time',
|
|
4218
|
+
scale: null,
|
|
4219
|
+
offset: 0,
|
|
4220
|
+
units: '',
|
|
4221
|
+
},
|
|
4222
|
+
19: {
|
|
4223
|
+
field: 'first_vo2_max',
|
|
4224
|
+
type: 'uint32',
|
|
4225
|
+
scale: 65536 / 3.5,
|
|
4226
|
+
offset: 0,
|
|
4227
|
+
units: 'ml/kg/min',
|
|
4228
|
+
},
|
|
4229
|
+
35: {
|
|
4230
|
+
field: 'end_of_previous_activity',
|
|
4231
|
+
type: 'date_time',
|
|
4232
|
+
scale: null,
|
|
4233
|
+
offset: 0,
|
|
4234
|
+
units: '',
|
|
4235
|
+
},
|
|
4236
|
+
},
|
|
4134
4237
|
103: {
|
|
4135
4238
|
name: 'monitoring_info',
|
|
4136
4239
|
253: {
|
|
@@ -4927,6 +5030,7 @@ export const FIT = {
|
|
|
4927
5030
|
55: 'monitoring',
|
|
4928
5031
|
72: 'training_file',
|
|
4929
5032
|
78: 'hrv',
|
|
5033
|
+
79: 'user_metrics',
|
|
4930
5034
|
80: 'ant_rx',
|
|
4931
5035
|
81: 'ant_tx',
|
|
4932
5036
|
82: 'ant_channel_id',
|
package/dist/fit_types.d.ts
CHANGED
|
@@ -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' | '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';
|
|
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' | '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' | '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';
|
|
@@ -841,6 +841,22 @@ export interface ParsedMonitoring {
|
|
|
841
841
|
export interface ParsedHrv {
|
|
842
842
|
time?: number[];
|
|
843
843
|
}
|
|
844
|
+
export interface ParsedUserMetrics {
|
|
845
|
+
vo2_max?: number;
|
|
846
|
+
age?: number;
|
|
847
|
+
height?: number;
|
|
848
|
+
weight?: number;
|
|
849
|
+
gender?: Gender;
|
|
850
|
+
max_heart_rate?: number;
|
|
851
|
+
remaining_recovery_time?: number;
|
|
852
|
+
lthr?: number;
|
|
853
|
+
ltpower?: number;
|
|
854
|
+
ltspeed?: number;
|
|
855
|
+
start_of_activity?: string;
|
|
856
|
+
first_vo2_max?: number;
|
|
857
|
+
end_of_previous_activity?: string;
|
|
858
|
+
timestamp: string;
|
|
859
|
+
}
|
|
844
860
|
export interface ParsedLength {
|
|
845
861
|
event?: Event;
|
|
846
862
|
event_type?: EventType;
|
|
@@ -1040,4 +1056,5 @@ export interface ParsedFit {
|
|
|
1040
1056
|
jumps?: ParsedJump[];
|
|
1041
1057
|
time_in_zone?: ParsedTimeInZone[];
|
|
1042
1058
|
activity_metrics?: ParsedActivityMetrics[];
|
|
1059
|
+
user_metrics?: ParsedUserMetrics[];
|
|
1043
1060
|
}
|
package/dist/type_generator.js
CHANGED
|
@@ -191,6 +191,7 @@ export function generateFitType() {
|
|
|
191
191
|
jumps: 'parsed_jump',
|
|
192
192
|
time_in_zone: 'parsed_time_in_zone',
|
|
193
193
|
activity_metrics: 'parsed_activity_metrics',
|
|
194
|
+
user_metrics: 'parsed_user_metrics',
|
|
194
195
|
};
|
|
195
196
|
const referenceProperties = {
|
|
196
197
|
file_creator: 'parsed_file_creator',
|