@@ -994,6 +994,19 @@ class phpFITFileAnalysis
994
994
]
995
995
],
996
996
997
+ // 'event_timestamp' and 'event_timestamp_12' should have scale of 1024 but due to floating point rounding errors.
998
+ // These are manually divided by 1024 later in the processHrMessages() function.
999
+ 132 => [
1000
+ 'mesg_name ' => 'hr ' , 'field_defns ' => [
1001
+ 0 => ['field_name ' => 'fractional_timestamp ' , 'scale ' => 32768 , 'offset ' => 0 , 'units ' => 's ' ],
1002
+ 1 => ['field_name ' => 'time256 ' , 'scale ' => 256 , 'offset ' => 0 , 'units ' => 's ' ],
1003
+ 6 => ['field_name ' => 'filtered_bpm ' , 'scale ' => 1 , 'offset ' => 0 , 'units ' => 'bpm ' ],
1004
+ 9 => ['field_name ' => 'event_timestamp ' , 'scale ' => 1 , 'offset ' => 0 , 'units ' => 's ' ],
1005
+ 10 => ['field_name ' => 'event_timestamp_12 ' , 'scale ' => 1 , 'offset ' => 0 , 'units ' => 's ' ],
1006
+ 253 => ['field_name ' => 'timestamp ' , 'scale ' => 1 , 'offset ' => 0 , 'units ' => 's ' ]
1007
+ ]
1008
+ ],
1009
+
997
1010
142 => [
998
1011
'mesg_name ' => 'segment_lap ' , 'field_defns ' => [
999
1012
0 => ['field_name ' => 'event ' , 'scale ' => 1 , 'offset ' => 0 , 'units ' => '' ],
@@ -1122,6 +1135,9 @@ public function __construct($file_path, $options = null)
1122
1135
$ this ->readDataRecords ();
1123
1136
$ this ->oneElementArrays ();
1124
1137
1138
+ // Process HR messages
1139
+ $ this ->processHrMessages ();
1140
+
1125
1141
// Handle options.
1126
1142
$ this ->fixData ($ this ->options );
1127
1143
$ this ->setUnits ($ this ->options );
@@ -1280,7 +1296,8 @@ private function readDataRecords()
1280
1296
if (isset ($ this ->data_mesg_info [$ this ->defn_mesgs [$ local_mesg_type ]['global_mesg_num ' ]]['field_defns ' ][$ field_defn ['field_definition_number ' ]]) && isset ($ this ->types [$ field_defn ['base_type ' ]])) {
1281
1297
// Check if it's an invalid value for the type
1282
1298
$ tmp_value = unpack ($ this ->types [$ field_defn ['base_type ' ]]['format ' ], substr ($ this ->file_contents , $ this ->file_pointer , $ field_defn ['size ' ]))['tmp ' ];
1283
- if ($ tmp_value !== $ this ->invalid_values [$ field_defn ['base_type ' ]]) {
1299
+ if ($ tmp_value !== $ this ->invalid_values [$ field_defn ['base_type ' ]] ||
1300
+ $ this ->defn_mesgs [$ local_mesg_type ]['global_mesg_num ' ] === 132 ) {
1284
1301
// If it's a timestamp, compensate between different in FIT and Unix timestamp epochs
1285
1302
if ($ field_defn ['field_definition_number ' ] === 253 && !$ this ->garmin_timestamps ) {
1286
1303
$ tmp_value += FIT_UNIX_TS_DIFF ;
@@ -2653,4 +2670,86 @@ public function showDebugInfo()
2653
2670
echo '</tbody></table><br><br> ' ;
2654
2671
}
2655
2672
}
2673
+
2674
+ /*
2675
+ * Process HR messages
2676
+ *
2677
+ * Based heavily on logic in commit:
2678
+ * https://github.com/GoldenCheetah/GoldenCheetah/commit/957ae470999b9a57b5b8ec57e75512d4baede1ec
2679
+ * Particularly the decodeHr() method
2680
+ */
2681
+ private function processHrMessages ()
2682
+ {
2683
+ // Check that we have received HR messages
2684
+ if (empty ($ this ->data_mesgs ['hr ' ])) {
2685
+ return ;
2686
+ }
2687
+
2688
+ $ hr = [];
2689
+ $ timestamps = [];
2690
+
2691
+ // Load all filtered_bpm values into the $hr array
2692
+ foreach ($ this ->data_mesgs ['hr ' ]['filtered_bpm ' ] as $ hr_val ) {
2693
+ if (is_array ($ hr_val )) {
2694
+ foreach ($ hr_val as $ sub_hr_val ) {
2695
+ $ hr [] = $ sub_hr_val ;
2696
+ }
2697
+ } else {
2698
+ $ hr [] = $ hr_val ;
2699
+ }
2700
+ }
2701
+
2702
+ // Manually scale timestamps (i.e. divide by 1024)
2703
+ $ last_event_timestamp = $ this ->data_mesgs ['hr ' ]['event_timestamp ' ];
2704
+ $ start_timestamp = $ this ->data_mesgs ['hr ' ]['timestamp ' ] - $ last_event_timestamp / 1024.0 ;
2705
+ $ timestamps [] = $ last_event_timestamp / 1024.0 ;
2706
+
2707
+ // Determine timestamps (similar to compressed timestamps)
2708
+ foreach ($ this ->data_mesgs ['hr ' ]['event_timestamp_12 ' ] as $ event_timestamp_12_val ) {
2709
+ $ j =0 ;
2710
+ for ($ i =0 ; $ i <11 ; $ i ++) {
2711
+ $ last_event_timestamp12 = $ last_event_timestamp & 0xFFF ;
2712
+ $ next_event_timestamp12 ;
2713
+
2714
+ if ($ j % 2 === 0 ) {
2715
+ $ next_event_timestamp12 = $ event_timestamp_12_val [$ i ] + (($ event_timestamp_12_val [$ i +1 ] & 0xF ) << 8 );
2716
+ $ last_event_timestamp = ($ last_event_timestamp & 0xFFFFF000 ) + $ next_event_timestamp12 ;
2717
+ } else {
2718
+ $ next_event_timestamp12 = 16 * $ event_timestamp_12_val [$ i +1 ] + (($ event_timestamp_12_val [$ i ] & 0xF0 ) >> 4 );
2719
+ $ last_event_timestamp = ($ last_event_timestamp & 0xFFFFF000 ) + $ next_event_timestamp12 ;
2720
+ $ i ++;
2721
+ }
2722
+ if ($ next_event_timestamp12 < $ last_event_timestamp12 ) {
2723
+ $ last_event_timestamp += 0x1000 ;
2724
+ }
2725
+
2726
+ $ timestamps [] = $ last_event_timestamp / 1024.0 ;
2727
+ $ j ++;
2728
+ }
2729
+ }
2730
+
2731
+ // Map HR values to timestamps
2732
+ $ filtered_bpm_arr = [];
2733
+ $ secs = 0 ;
2734
+ $ min_record_ts = min ($ this ->data_mesgs ['record ' ]['timestamp ' ]);
2735
+ $ max_record_ts = max ($ this ->data_mesgs ['record ' ]['timestamp ' ]);
2736
+ foreach ($ timestamps as $ idx => $ timestamp ) {
2737
+ $ ts_secs = round ($ timestamp + $ start_timestamp );
2738
+
2739
+ // Skip timestamps outside of the range we're interested in
2740
+ if ($ ts_secs >= $ min_record_ts && $ ts_secs <= $ max_record_ts ) {
2741
+ if (isset ($ filtered_bpm_arr [$ ts_secs ])) {
2742
+ $ filtered_bpm_arr [$ ts_secs ][0 ] += $ hr [$ idx ];
2743
+ $ filtered_bpm_arr [$ ts_secs ][1 ]++;
2744
+ } else {
2745
+ $ filtered_bpm_arr [$ ts_secs ] = [$ hr [$ idx ], 1 ];
2746
+ }
2747
+ }
2748
+ }
2749
+
2750
+ // Populate the heart_rate fields for record messages
2751
+ foreach ($ filtered_bpm_arr as $ idx => $ arr ) {
2752
+ $ this ->data_mesgs ['record ' ]['heart_rate ' ][$ idx ] = (int )round ($ arr [0 ] / $ arr [1 ]);
2753
+ }
2754
+ }
2656
2755
}
0 commit comments