fit-file-parser 2.2.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 +57 -56
- package/dist/binary.js +4 -4
- package/dist/cjs/binary.js +4 -4
- package/dist/cjs/fit.js +3 -1
- package/dist/cjs/fit_types.d.ts +47 -30
- package/dist/cjs/type_generator.js +8 -5
- package/dist/fit.js +3 -1
- package/dist/fit_types.d.ts +47 -30
- package/dist/type_generator.js +8 -5
- package/eslint.config.js +5 -0
- package/package.json +1 -1
- package/probe_session.js +38 -33
package/deep_probe.js
CHANGED
|
@@ -1,69 +1,70 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { createRequire } from 'module'
|
|
1
3
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
const FitParser = require('./dist/fit-parser.js').default;
|
|
4
|
+
const require = createRequire(import.meta.url)
|
|
5
|
+
const FitParser = require('./dist/fit-parser.js').default
|
|
6
6
|
|
|
7
|
-
const content = fs.readFileSync('/Users/dimitrios/Projects/sports-lib/samples/fit/jumps-mtb.fit')
|
|
7
|
+
const content = fs.readFileSync('/Users/dimitrios/Projects/sports-lib/samples/fit/jumps-mtb.fit')
|
|
8
8
|
const fitParser = new FitParser({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
9
|
+
force: true,
|
|
10
|
+
speedUnit: 'km/h',
|
|
11
|
+
lengthUnit: 'm',
|
|
12
|
+
temperatureUnit: 'celsius',
|
|
13
|
+
elapsedRecordField: true,
|
|
14
|
+
mode: 'both',
|
|
15
|
+
})
|
|
16
16
|
|
|
17
17
|
function search(obj, path = []) {
|
|
18
|
-
|
|
18
|
+
if (!obj || typeof obj !== 'object')
|
|
19
|
+
return
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Object.keys(obj).forEach((key) => {
|
|
22
|
+
const val = obj[key]
|
|
23
|
+
const newPath = [...path, key]
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if (typeof val === 'object') {
|
|
39
|
+
search(val, newPath)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
fitParser.parse(content, (error, data) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
if (error) {
|
|
46
|
+
console.error(error)
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log('Starting deep search...')
|
|
50
|
+
search(data)
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
console.log(`Found ${jumpEvents.length} jump events`);
|
|
67
|
-
}
|
|
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
68
|
}
|
|
69
|
-
}
|
|
69
|
+
}
|
|
70
|
+
})
|
package/dist/binary.js
CHANGED
|
@@ -228,10 +228,10 @@ function applyOptions(data, field, options) {
|
|
|
228
228
|
case 'end_pressure':
|
|
229
229
|
return convertTo(data, 'pressureUnits', options.pressureUnit);
|
|
230
230
|
case 'ant_id': {
|
|
231
|
-
const n1 = (data >>> 28) &
|
|
232
|
-
const n2 = (data >>> 24) &
|
|
233
|
-
const n3 = (data >>> 16) &
|
|
234
|
-
const n4 = data &
|
|
231
|
+
const n1 = (data >>> 28) & 0xF;
|
|
232
|
+
const n2 = (data >>> 24) & 0xF;
|
|
233
|
+
const n3 = (data >>> 16) & 0xFF;
|
|
234
|
+
const n4 = data & 0xFFFF;
|
|
235
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
236
|
}
|
|
237
237
|
default:
|
package/dist/cjs/binary.js
CHANGED
|
@@ -234,10 +234,10 @@ function applyOptions(data, field, options) {
|
|
|
234
234
|
case 'end_pressure':
|
|
235
235
|
return convertTo(data, 'pressureUnits', options.pressureUnit);
|
|
236
236
|
case 'ant_id': {
|
|
237
|
-
const n1 = (data >>> 28) &
|
|
238
|
-
const n2 = (data >>> 24) &
|
|
239
|
-
const n3 = (data >>> 16) &
|
|
240
|
-
const n4 = data &
|
|
237
|
+
const n1 = (data >>> 28) & 0xF;
|
|
238
|
+
const n2 = (data >>> 24) & 0xF;
|
|
239
|
+
const n3 = (data >>> 16) & 0xFF;
|
|
240
|
+
const n4 = data & 0xFFFF;
|
|
241
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
242
|
}
|
|
243
243
|
default:
|
package/dist/cjs/fit.js
CHANGED
|
@@ -4875,8 +4875,10 @@ exports.FIT = {
|
|
|
4875
4875
|
268: 'dive_summary',
|
|
4876
4876
|
285: 'jump',
|
|
4877
4877
|
317: 'climb_pro',
|
|
4878
|
-
319: '
|
|
4878
|
+
319: 'tank_update',
|
|
4879
4879
|
323: 'tank_summary',
|
|
4880
|
+
216: 'time_in_zone',
|
|
4881
|
+
347: 'o_hr_settings',
|
|
4880
4882
|
65280: 'mfg_range_min',
|
|
4881
4883
|
65534: 'mfg_range_max',
|
|
4882
4884
|
},
|
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' | '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' | '
|
|
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' | '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';
|
|
@@ -363,6 +363,8 @@ export interface ParsedSession {
|
|
|
363
363
|
training_stress_score?: number;
|
|
364
364
|
intensity_factor?: number;
|
|
365
365
|
left_right_balance?: LeftRightBalance100;
|
|
366
|
+
end_position_lat?: number;
|
|
367
|
+
end_position_long?: number;
|
|
366
368
|
avg_stroke_count?: number;
|
|
367
369
|
avg_stroke_distance?: number;
|
|
368
370
|
swim_stroke?: SwimStroke;
|
|
@@ -387,10 +389,10 @@ export interface ParsedSession {
|
|
|
387
389
|
max_pos_vertical_speed?: number;
|
|
388
390
|
max_neg_vertical_speed?: number;
|
|
389
391
|
min_heart_rate?: number;
|
|
390
|
-
time_in_hr_zone?:
|
|
391
|
-
time_in_speed_zone?:
|
|
392
|
-
time_in_cadence_zone?:
|
|
393
|
-
time_in_power_zone?:
|
|
392
|
+
time_in_hr_zone?: number[];
|
|
393
|
+
time_in_speed_zone?: number[];
|
|
394
|
+
time_in_cadence_zone?: number[];
|
|
395
|
+
time_in_power_zone?: number[];
|
|
394
396
|
avg_lap_time?: number;
|
|
395
397
|
best_lap_index?: number;
|
|
396
398
|
min_altitude?: number;
|
|
@@ -418,6 +420,7 @@ export interface ParsedSession {
|
|
|
418
420
|
avg_left_pedal_smoothness?: number;
|
|
419
421
|
avg_right_pedal_smoothness?: number;
|
|
420
422
|
avg_combined_pedal_smoothness?: number;
|
|
423
|
+
sport_profile_name?: string;
|
|
421
424
|
sport_index?: number;
|
|
422
425
|
time_standing?: number;
|
|
423
426
|
stand_count?: number;
|
|
@@ -442,12 +445,22 @@ export interface ParsedSession {
|
|
|
442
445
|
avg_vertical_ratio?: number;
|
|
443
446
|
avg_stance_time_balance?: number;
|
|
444
447
|
avg_step_length?: number;
|
|
445
|
-
|
|
448
|
+
total_anaerobic_training_effect?: number;
|
|
446
449
|
avg_vam?: number;
|
|
450
|
+
recovery_advisor?: number;
|
|
451
|
+
min_temperature?: number;
|
|
452
|
+
training_load_peak?: number;
|
|
453
|
+
enhanced_avg_respiration_rate?: number;
|
|
454
|
+
enhanced_max_respiration_rate?: number;
|
|
455
|
+
est_sweat_loss?: number;
|
|
456
|
+
enhanced_min_respiration_rate?: number;
|
|
447
457
|
total_grit?: number;
|
|
448
|
-
|
|
458
|
+
jump_count?: number;
|
|
459
|
+
avg_flow?: number;
|
|
460
|
+
primary_benefit?: number;
|
|
449
461
|
workout_feel?: number;
|
|
450
462
|
workout_rpe?: number;
|
|
463
|
+
resting_calories?: number;
|
|
451
464
|
avg_grit?: number;
|
|
452
465
|
avg_flow?: number;
|
|
453
466
|
timestamp: string;
|
|
@@ -506,10 +519,10 @@ export interface ParsedLap {
|
|
|
506
519
|
avg_neg_vertical_speed?: number;
|
|
507
520
|
max_pos_vertical_speed?: number;
|
|
508
521
|
max_neg_vertical_speed?: number;
|
|
509
|
-
time_in_hr_zone?:
|
|
510
|
-
time_in_speed_zone?:
|
|
511
|
-
time_in_cadence_zone?:
|
|
512
|
-
time_in_power_zone?:
|
|
522
|
+
time_in_hr_zone?: number[];
|
|
523
|
+
time_in_speed_zone?: number[];
|
|
524
|
+
time_in_cadence_zone?: number[];
|
|
525
|
+
time_in_power_zone?: number[];
|
|
513
526
|
repetition_num?: number;
|
|
514
527
|
min_altitude?: number;
|
|
515
528
|
min_heart_rate?: number;
|
|
@@ -666,6 +679,7 @@ export interface ParsedDeviceInfo {
|
|
|
666
679
|
ant_transmission_type?: number;
|
|
667
680
|
ant_device_number?: number;
|
|
668
681
|
ant_network?: AntNetwork;
|
|
682
|
+
ant_id?: number;
|
|
669
683
|
source_type?: SourceType;
|
|
670
684
|
product_name?: string;
|
|
671
685
|
battery_level?: number;
|
|
@@ -823,7 +837,7 @@ export interface ParsedMonitoring {
|
|
|
823
837
|
timestamp: number;
|
|
824
838
|
}
|
|
825
839
|
export interface ParsedHrv {
|
|
826
|
-
time?:
|
|
840
|
+
time?: number[];
|
|
827
841
|
}
|
|
828
842
|
export interface ParsedLength {
|
|
829
843
|
event?: Event;
|
|
@@ -858,6 +872,7 @@ export interface ParsedOHrSettings {
|
|
|
858
872
|
timestamp: string;
|
|
859
873
|
}
|
|
860
874
|
export interface ParsedJump {
|
|
875
|
+
enhanced_mets?: number;
|
|
861
876
|
distance?: number;
|
|
862
877
|
height?: number;
|
|
863
878
|
score?: number;
|
|
@@ -875,11 +890,30 @@ export interface ParsedFieldDescription {
|
|
|
875
890
|
}
|
|
876
891
|
export interface ParsedDeveloperDataId {
|
|
877
892
|
developer_id?: number;
|
|
878
|
-
application_id?:
|
|
893
|
+
application_id?: number[];
|
|
879
894
|
manufacturer_id?: Manufacturer;
|
|
880
895
|
developer_data_index?: number;
|
|
881
896
|
application_version?: number;
|
|
882
897
|
}
|
|
898
|
+
export interface ParsedTimeInZone {
|
|
899
|
+
reference_mesg?: number;
|
|
900
|
+
reference_index?: number;
|
|
901
|
+
time_in_hr_zone?: number[];
|
|
902
|
+
time_in_speed_zone?: number[];
|
|
903
|
+
time_in_power_zone?: number[];
|
|
904
|
+
hr_zone_high_boundary_deprecated?: number[];
|
|
905
|
+
hr_zone_high_boundary?: number[];
|
|
906
|
+
speed_zone_high_boundary?: number[];
|
|
907
|
+
power_zone_high_boundary?: number[];
|
|
908
|
+
hr_calc_type?: HrZoneCalc;
|
|
909
|
+
max_heart_rate_deprecated?: number;
|
|
910
|
+
max_heart_rate?: number;
|
|
911
|
+
resting_heart_rate?: number;
|
|
912
|
+
threshold_heart_rate?: number;
|
|
913
|
+
pwr_calc_type?: PwrZoneCalc;
|
|
914
|
+
functional_threshold_power?: number;
|
|
915
|
+
timestamp: string;
|
|
916
|
+
}
|
|
883
917
|
export interface ParsedStressLevel {
|
|
884
918
|
stress_level_value?: number;
|
|
885
919
|
stress_level_time?: string;
|
|
@@ -952,23 +986,6 @@ export interface ParsedTankSummary {
|
|
|
952
986
|
end_pressure?: number;
|
|
953
987
|
volume_used?: number;
|
|
954
988
|
}
|
|
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
|
-
}
|
|
972
989
|
export interface ParsedFit {
|
|
973
990
|
protocolVersion?: number;
|
|
974
991
|
profileVersion?: number;
|
|
@@ -60,8 +60,14 @@ function generateArrayProperty(name, type, optional = true) {
|
|
|
60
60
|
}
|
|
61
61
|
function generateTypeFromField(def) {
|
|
62
62
|
switch (def.type) {
|
|
63
|
+
case 'uint32_array':
|
|
64
|
+
case 'uint16_array':
|
|
65
|
+
case 'uint8_array':
|
|
66
|
+
case 'sint32_array':
|
|
67
|
+
case 'sint16_array':
|
|
68
|
+
case 'sint8_array':
|
|
63
69
|
case 'byte_array':
|
|
64
|
-
return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.
|
|
70
|
+
return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword));
|
|
65
71
|
case 'bool':
|
|
66
72
|
return typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword);
|
|
67
73
|
case 'date_time':
|
|
@@ -104,11 +110,7 @@ function generateTypes(types) {
|
|
|
104
110
|
const typeAlias = typescript_1.default.factory.createTypeAliasDeclaration([
|
|
105
111
|
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
|
|
106
112
|
], snakeToCamel(name), undefined, typescript_1.default.factory.createUnionTypeNode([...names.map(n => typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
|
|
107
|
-
// TODO those are somehow missing in the FIT.types.mesg_num definition
|
|
108
|
-
// especially 'tank_update' (319 in messages) is mapped to 'tank_pressure' a bug?
|
|
109
113
|
? [
|
|
110
|
-
typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('o_hr_settings')),
|
|
111
|
-
typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('tank_update')),
|
|
112
114
|
typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('definition')),
|
|
113
115
|
]
|
|
114
116
|
: [])]));
|
|
@@ -207,6 +209,7 @@ function generateFitType() {
|
|
|
207
209
|
tank_updates: 'parsed_tank_update',
|
|
208
210
|
tank_summaries: 'parsed_tank_summary',
|
|
209
211
|
jumps: 'parsed_jump',
|
|
212
|
+
time_in_zone: 'parsed_time_in_zone',
|
|
210
213
|
};
|
|
211
214
|
const referenceProperties = {
|
|
212
215
|
file_creator: 'parsed_file_creator',
|
package/dist/fit.js
CHANGED
|
@@ -4872,8 +4872,10 @@ export const FIT = {
|
|
|
4872
4872
|
268: 'dive_summary',
|
|
4873
4873
|
285: 'jump',
|
|
4874
4874
|
317: 'climb_pro',
|
|
4875
|
-
319: '
|
|
4875
|
+
319: 'tank_update',
|
|
4876
4876
|
323: 'tank_summary',
|
|
4877
|
+
216: 'time_in_zone',
|
|
4878
|
+
347: 'o_hr_settings',
|
|
4877
4879
|
65280: 'mfg_range_min',
|
|
4878
4880
|
65534: 'mfg_range_max',
|
|
4879
4881
|
},
|
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' | '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' | '
|
|
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' | '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';
|
|
@@ -363,6 +363,8 @@ export interface ParsedSession {
|
|
|
363
363
|
training_stress_score?: number;
|
|
364
364
|
intensity_factor?: number;
|
|
365
365
|
left_right_balance?: LeftRightBalance100;
|
|
366
|
+
end_position_lat?: number;
|
|
367
|
+
end_position_long?: number;
|
|
366
368
|
avg_stroke_count?: number;
|
|
367
369
|
avg_stroke_distance?: number;
|
|
368
370
|
swim_stroke?: SwimStroke;
|
|
@@ -387,10 +389,10 @@ export interface ParsedSession {
|
|
|
387
389
|
max_pos_vertical_speed?: number;
|
|
388
390
|
max_neg_vertical_speed?: number;
|
|
389
391
|
min_heart_rate?: number;
|
|
390
|
-
time_in_hr_zone?:
|
|
391
|
-
time_in_speed_zone?:
|
|
392
|
-
time_in_cadence_zone?:
|
|
393
|
-
time_in_power_zone?:
|
|
392
|
+
time_in_hr_zone?: number[];
|
|
393
|
+
time_in_speed_zone?: number[];
|
|
394
|
+
time_in_cadence_zone?: number[];
|
|
395
|
+
time_in_power_zone?: number[];
|
|
394
396
|
avg_lap_time?: number;
|
|
395
397
|
best_lap_index?: number;
|
|
396
398
|
min_altitude?: number;
|
|
@@ -418,6 +420,7 @@ export interface ParsedSession {
|
|
|
418
420
|
avg_left_pedal_smoothness?: number;
|
|
419
421
|
avg_right_pedal_smoothness?: number;
|
|
420
422
|
avg_combined_pedal_smoothness?: number;
|
|
423
|
+
sport_profile_name?: string;
|
|
421
424
|
sport_index?: number;
|
|
422
425
|
time_standing?: number;
|
|
423
426
|
stand_count?: number;
|
|
@@ -442,12 +445,22 @@ export interface ParsedSession {
|
|
|
442
445
|
avg_vertical_ratio?: number;
|
|
443
446
|
avg_stance_time_balance?: number;
|
|
444
447
|
avg_step_length?: number;
|
|
445
|
-
|
|
448
|
+
total_anaerobic_training_effect?: number;
|
|
446
449
|
avg_vam?: number;
|
|
450
|
+
recovery_advisor?: number;
|
|
451
|
+
min_temperature?: number;
|
|
452
|
+
training_load_peak?: number;
|
|
453
|
+
enhanced_avg_respiration_rate?: number;
|
|
454
|
+
enhanced_max_respiration_rate?: number;
|
|
455
|
+
est_sweat_loss?: number;
|
|
456
|
+
enhanced_min_respiration_rate?: number;
|
|
447
457
|
total_grit?: number;
|
|
448
|
-
|
|
458
|
+
jump_count?: number;
|
|
459
|
+
avg_flow?: number;
|
|
460
|
+
primary_benefit?: number;
|
|
449
461
|
workout_feel?: number;
|
|
450
462
|
workout_rpe?: number;
|
|
463
|
+
resting_calories?: number;
|
|
451
464
|
avg_grit?: number;
|
|
452
465
|
avg_flow?: number;
|
|
453
466
|
timestamp: string;
|
|
@@ -506,10 +519,10 @@ export interface ParsedLap {
|
|
|
506
519
|
avg_neg_vertical_speed?: number;
|
|
507
520
|
max_pos_vertical_speed?: number;
|
|
508
521
|
max_neg_vertical_speed?: number;
|
|
509
|
-
time_in_hr_zone?:
|
|
510
|
-
time_in_speed_zone?:
|
|
511
|
-
time_in_cadence_zone?:
|
|
512
|
-
time_in_power_zone?:
|
|
522
|
+
time_in_hr_zone?: number[];
|
|
523
|
+
time_in_speed_zone?: number[];
|
|
524
|
+
time_in_cadence_zone?: number[];
|
|
525
|
+
time_in_power_zone?: number[];
|
|
513
526
|
repetition_num?: number;
|
|
514
527
|
min_altitude?: number;
|
|
515
528
|
min_heart_rate?: number;
|
|
@@ -666,6 +679,7 @@ export interface ParsedDeviceInfo {
|
|
|
666
679
|
ant_transmission_type?: number;
|
|
667
680
|
ant_device_number?: number;
|
|
668
681
|
ant_network?: AntNetwork;
|
|
682
|
+
ant_id?: number;
|
|
669
683
|
source_type?: SourceType;
|
|
670
684
|
product_name?: string;
|
|
671
685
|
battery_level?: number;
|
|
@@ -823,7 +837,7 @@ export interface ParsedMonitoring {
|
|
|
823
837
|
timestamp: number;
|
|
824
838
|
}
|
|
825
839
|
export interface ParsedHrv {
|
|
826
|
-
time?:
|
|
840
|
+
time?: number[];
|
|
827
841
|
}
|
|
828
842
|
export interface ParsedLength {
|
|
829
843
|
event?: Event;
|
|
@@ -858,6 +872,7 @@ export interface ParsedOHrSettings {
|
|
|
858
872
|
timestamp: string;
|
|
859
873
|
}
|
|
860
874
|
export interface ParsedJump {
|
|
875
|
+
enhanced_mets?: number;
|
|
861
876
|
distance?: number;
|
|
862
877
|
height?: number;
|
|
863
878
|
score?: number;
|
|
@@ -875,11 +890,30 @@ export interface ParsedFieldDescription {
|
|
|
875
890
|
}
|
|
876
891
|
export interface ParsedDeveloperDataId {
|
|
877
892
|
developer_id?: number;
|
|
878
|
-
application_id?:
|
|
893
|
+
application_id?: number[];
|
|
879
894
|
manufacturer_id?: Manufacturer;
|
|
880
895
|
developer_data_index?: number;
|
|
881
896
|
application_version?: number;
|
|
882
897
|
}
|
|
898
|
+
export interface ParsedTimeInZone {
|
|
899
|
+
reference_mesg?: number;
|
|
900
|
+
reference_index?: number;
|
|
901
|
+
time_in_hr_zone?: number[];
|
|
902
|
+
time_in_speed_zone?: number[];
|
|
903
|
+
time_in_power_zone?: number[];
|
|
904
|
+
hr_zone_high_boundary_deprecated?: number[];
|
|
905
|
+
hr_zone_high_boundary?: number[];
|
|
906
|
+
speed_zone_high_boundary?: number[];
|
|
907
|
+
power_zone_high_boundary?: number[];
|
|
908
|
+
hr_calc_type?: HrZoneCalc;
|
|
909
|
+
max_heart_rate_deprecated?: number;
|
|
910
|
+
max_heart_rate?: number;
|
|
911
|
+
resting_heart_rate?: number;
|
|
912
|
+
threshold_heart_rate?: number;
|
|
913
|
+
pwr_calc_type?: PwrZoneCalc;
|
|
914
|
+
functional_threshold_power?: number;
|
|
915
|
+
timestamp: string;
|
|
916
|
+
}
|
|
883
917
|
export interface ParsedStressLevel {
|
|
884
918
|
stress_level_value?: number;
|
|
885
919
|
stress_level_time?: string;
|
|
@@ -952,23 +986,6 @@ export interface ParsedTankSummary {
|
|
|
952
986
|
end_pressure?: number;
|
|
953
987
|
volume_used?: number;
|
|
954
988
|
}
|
|
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
|
-
}
|
|
972
989
|
export interface ParsedFit {
|
|
973
990
|
protocolVersion?: number;
|
|
974
991
|
profileVersion?: number;
|
package/dist/type_generator.js
CHANGED
|
@@ -40,8 +40,14 @@ export function generateArrayProperty(name, type, optional = true) {
|
|
|
40
40
|
}
|
|
41
41
|
export function generateTypeFromField(def) {
|
|
42
42
|
switch (def.type) {
|
|
43
|
+
case 'uint32_array':
|
|
44
|
+
case 'uint16_array':
|
|
45
|
+
case 'uint8_array':
|
|
46
|
+
case 'sint32_array':
|
|
47
|
+
case 'sint16_array':
|
|
48
|
+
case 'sint8_array':
|
|
43
49
|
case 'byte_array':
|
|
44
|
-
return ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.
|
|
50
|
+
return ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword));
|
|
45
51
|
case 'bool':
|
|
46
52
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
47
53
|
case 'date_time':
|
|
@@ -84,11 +90,7 @@ export function generateTypes(types) {
|
|
|
84
90
|
const typeAlias = ts.factory.createTypeAliasDeclaration([
|
|
85
91
|
ts.factory.createModifier(ts.SyntaxKind.ExportKeyword),
|
|
86
92
|
], snakeToCamel(name), undefined, ts.factory.createUnionTypeNode([...names.map(n => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
|
|
87
|
-
// TODO those are somehow missing in the FIT.types.mesg_num definition
|
|
88
|
-
// especially 'tank_update' (319 in messages) is mapped to 'tank_pressure' a bug?
|
|
89
93
|
? [
|
|
90
|
-
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('o_hr_settings')),
|
|
91
|
-
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('tank_update')),
|
|
92
94
|
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('definition')),
|
|
93
95
|
]
|
|
94
96
|
: [])]));
|
|
@@ -187,6 +189,7 @@ export function generateFitType() {
|
|
|
187
189
|
tank_updates: 'parsed_tank_update',
|
|
188
190
|
tank_summaries: 'parsed_tank_summary',
|
|
189
191
|
jumps: 'parsed_jump',
|
|
192
|
+
time_in_zone: 'parsed_time_in_zone',
|
|
190
193
|
};
|
|
191
194
|
const referenceProperties = {
|
|
192
195
|
file_creator: 'parsed_file_creator',
|
package/eslint.config.js
CHANGED
package/package.json
CHANGED
package/probe_session.js
CHANGED
|
@@ -1,44 +1,49 @@
|
|
|
1
|
+
import { createRequire } from 'module'
|
|
1
2
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// Note: Importing TS directly might not work with node without a loader.
|
|
3
|
+
// Note: Importing TS directly might not work with node without a loader.
|
|
6
4
|
// Better to use the built file if available, or use ts-node.
|
|
7
5
|
// But earlier I saw `dist/fit-parser.js`. Let's try to import that if key is default.
|
|
8
|
-
// Actually, let's just use `fs.readFileSync` and basic node.
|
|
6
|
+
// Actually, let's just use `fs.readFileSync` and basic node.
|
|
9
7
|
// If `dist` is CJS, I can use `createRequire`.
|
|
10
8
|
|
|
11
|
-
import
|
|
12
|
-
|
|
9
|
+
import fs from 'node:fs'
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url)
|
|
13
12
|
|
|
14
|
-
const FitParserDist = require('./dist/fit-parser.js').default
|
|
13
|
+
const FitParserDist = require('./dist/fit-parser.js').default
|
|
15
14
|
|
|
16
|
-
const content = fs.readFileSync('/Users/dimitrios/Projects/sports-lib/samples/fit/jumps-mtb.fit')
|
|
15
|
+
const content = fs.readFileSync('/Users/dimitrios/Projects/sports-lib/samples/fit/jumps-mtb.fit')
|
|
17
16
|
const fitParser = new FitParserDist({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
})
|
|
17
|
+
force: true,
|
|
18
|
+
speedUnit: 'km/h',
|
|
19
|
+
lengthUnit: 'm',
|
|
20
|
+
temperatureUnit: 'celsius',
|
|
21
|
+
elapsedRecordField: true,
|
|
22
|
+
mode: 'both',
|
|
23
|
+
})
|
|
25
24
|
|
|
26
25
|
fitParser.parse(content, (error, data) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
if (error) {
|
|
27
|
+
console.error(error)
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const session = data.sessions[0]
|
|
31
|
+
if (session) {
|
|
32
|
+
console.log('=== SESSION KEYS & VALUES ===')
|
|
33
|
+
Object.keys(session).forEach((key) => {
|
|
34
|
+
const val = session[key]
|
|
35
|
+
console.log(`${key}: ${val}`)
|
|
36
|
+
if (Math.abs(val - 11) < 0.1)
|
|
37
|
+
console.log(`>>> MATCH 11 (Jump Count?): ${key}`)
|
|
38
|
+
if (Math.abs(val - 159) < 0.1)
|
|
39
|
+
console.log(`>>> MATCH 159 (Resting Cals?): ${key}`)
|
|
40
|
+
if (Math.abs(val - 6079174) < 1)
|
|
41
|
+
console.log(`>>> MATCH 6M (Training Load?): ${key}`)
|
|
42
|
+
if (Math.abs(val - 100) < 0.1)
|
|
43
|
+
console.log(`>>> MATCH 100 (Avg VAM?): ${key}`)
|
|
44
|
+
if (Math.abs(val - 2756) < 1)
|
|
45
|
+
console.log(`>>> MATCH 2756 (Avg Resp?): ${key}`)
|
|
46
|
+
})
|
|
43
47
|
}
|
|
44
|
-
}
|
|
48
|
+
}
|
|
49
|
+
})
|