Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.

Commit 42c51e1

Browse files
committed
Process HR messages
1 parent aaa717d commit 42c51e1

File tree

1 file changed

+100
-1
lines changed

1 file changed

+100
-1
lines changed

src/phpFITFileAnalysis.php

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)