@@ -994,6 +994,19 @@ class phpFITFileAnalysis
994994 ]
995995 ],
996996
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+
9971010 142 => [
9981011 'mesg_name ' => 'segment_lap ' , 'field_defns ' => [
9991012 0 => ['field_name ' => 'event ' , 'scale ' => 1 , 'offset ' => 0 , 'units ' => '' ],
@@ -1122,6 +1135,9 @@ public function __construct($file_path, $options = null)
11221135 $ this ->readDataRecords ();
11231136 $ this ->oneElementArrays ();
11241137
1138+ // Process HR messages
1139+ $ this ->processHrMessages ();
1140+
11251141 // Handle options.
11261142 $ this ->fixData ($ this ->options );
11271143 $ this ->setUnits ($ this ->options );
@@ -1280,7 +1296,8 @@ private function readDataRecords()
12801296 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 ' ]])) {
12811297 // Check if it's an invalid value for the type
12821298 $ 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 ) {
12841301 // If it's a timestamp, compensate between different in FIT and Unix timestamp epochs
12851302 if ($ field_defn ['field_definition_number ' ] === 253 && !$ this ->garmin_timestamps ) {
12861303 $ tmp_value += FIT_UNIX_TS_DIFF ;
@@ -2653,4 +2670,86 @@ public function showDebugInfo()
26532670 echo '</tbody></table><br><br> ' ;
26542671 }
26552672 }
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+ }
26562755}
0 commit comments