diff --git a/README.md b/README.md index 1854a34..80df715 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,11 @@ array getJSON(float $crank_length = null, int $ftp = null, array $data_required * ['timestamp', 'paused', 'temperature', 'lap', 'position_lat', 'position_long', 'distance', 'altitude', 'speed', 'heart_rate', 'cadence', 'power', 'quadrant-analysis'] */ ``` +Returns array of gear change information (if present, e.g. using Shimano D-Fly Wireless Di2 Transmitter): +```php +// By default, time spent in a gear whilst the timer is paused (e.g. autopause) is ignored. Set to false to include. +array gearChanges($bIgnoreTimerPaused = true) +``` ##Acknowledgement This class has been created using information available in a Software Development Kit (SDK) made available by ANT ([thisisant.com](http://www.thisisant.com/resources/fit)). diff --git a/src/phpFITFileAnalysis.php b/src/phpFITFileAnalysis.php index 4c7c2b2..13228ee 100644 --- a/src/phpFITFileAnalysis.php +++ b/src/phpFITFileAnalysis.php @@ -1999,6 +1999,146 @@ public function quadrantAnalysis($crank_length, $ftp, $selected_cadence = 90, $u return $quadrant_plot; } + + /** + * Returns array of gear change information. + */ + public function gearChanges($bIgnoreTimerPaused = true) + { + /** + * Event enumerated values of interest + * 42 = front_gear_change + * 43 = rear_gear_change + */ + $fgcek = array_keys($this->data_mesgs['event']['event'], 42); // front gear change event keys + $rgcek = array_keys($this->data_mesgs['event']['event'], 43); // rear gear change event keys + + /** + * gear_change_data (uint32) + * components: + * rear_gear_num 00000000 00000000 00000000 11111111 + * rear_gear 00000000 00000000 11111111 00000000 + * front_gear_num 00000000 11111111 00000000 00000000 + * front_gear 11111111 00000000 00000000 00000000 + * scale: 1, 1, 1, 1 + * bits: 8, 8, 8, 8 + */ + + $fgc = []; // front gear components + $front_gears = []; + foreach ($fgcek as $k) { + $fgc_tmp = [ + 'timestamp' => $this->data_mesgs['event']['timestamp'][$k], + // 'data' => $this->data_mesgs['event']['data'][$k], + // 'event_type' => $this->data_mesgs['event']['event_type'][$k], + // 'event_group' => $this->data_mesgs['event']['event_group'][$k], + 'rear_gear_num' => $this->data_mesgs['event']['data'][$k] & 255, + 'rear_gear' => ($this->data_mesgs['event']['data'][$k] >> 8) & 255, + 'front_gear_num' => ($this->data_mesgs['event']['data'][$k] >> 16) & 255, + 'front_gear' => ($this->data_mesgs['event']['data'][$k] >> 24) & 255 + ]; + + $fgc[] = $fgc_tmp; + + if (!array_key_exists($fgc_tmp['front_gear_num'], $front_gears)) { + $front_gears[$fgc_tmp['front_gear_num']] = $fgc_tmp['front_gear']; + } + } + ksort($front_gears); + + $rgc = []; // rear gear components + $rear_gears = []; + foreach ($rgcek as $k) { + $rgc_tmp = [ + 'timestamp' => $this->data_mesgs['event']['timestamp'][$k], + // 'data' => $this->data_mesgs['event']['data'][$k], + // 'event_type' => $this->data_mesgs['event']['event_type'][$k], + // 'event_group' => $this->data_mesgs['event']['event_group'][$k], + 'rear_gear_num' => $this->data_mesgs['event']['data'][$k] & 255, + 'rear_gear' => ($this->data_mesgs['event']['data'][$k] >> 8) & 255, + 'front_gear_num' => ($this->data_mesgs['event']['data'][$k] >> 16) & 255, + 'front_gear' => ($this->data_mesgs['event']['data'][$k] >> 24) & 255 + ]; + + $rgc[] = $rgc_tmp; + + if (!array_key_exists($rgc_tmp['rear_gear_num'], $rear_gears)) { + $rear_gears[$rgc_tmp['rear_gear_num']] = $rgc_tmp['rear_gear']; + } + } + ksort($rear_gears); + + $timestamps = $this->data_mesgs['record']['timestamp']; + $first_ts = min($timestamps); // first timestamp + $last_ts = max($timestamps); // last timestamp + + $fg = 0; // front gear at start of ride + $rg = 0; // rear gear at start of ride + + if (isset($fgc[0]['timestamp'])) { + if ($first_ts == $fgc[0]['timestamp']) { + $fg = $fgc[0]['front_gear']; + } + else { + $fg = $fgc[0]['front_gear_num'] == 1 ? $front_gears[2] : $front_gears[1]; + } + } + + if (isset($rgc[0]['timestamp'])) { + if ($first_ts == $rgc[0]['timestamp']) { + $rg = $rgc[0]['rear_gear']; + } + else { + $rg = $rgc[0]['rear_gear_num'] == min($rear_gears) ? $rear_gears[$rgc[0]['rear_gear_num'] + 1] : $rear_gears[$rgc[0]['rear_gear_num'] - 1]; + } + } + + $fg_summary = []; + $rg_summary = []; + $combined = []; + $gears_array = []; + + if($bIgnoreTimerPaused === true) { + $is_paused = $this->isPaused(); + } + + reset($fgc); + reset($rgc); + for ($i = $first_ts; $i < $last_ts; ++$i) { + if($bIgnoreTimerPaused === true && $is_paused[$i] === true) { + continue; + } + + $fgc_tmp = current($fgc); + $rgc_tmp = current($rgc); + + if ($i > $fgc_tmp['timestamp']) { + if (next($fgc) !== false) { + $fg = $fgc_tmp['front_gear']; + } + } + $fg_summary[$fg] = isset($fg_summary[$fg]) ? $fg_summary[$fg] + 1 : 1; + + if ($i > $rgc_tmp['timestamp']) { + if (next($rgc) !== false) { + $rg = $rgc_tmp['rear_gear']; + } + } + $rg_summary[$rg] = isset($rg_summary[$rg]) ? $rg_summary[$rg] + 1 : 1; + + $combined[$fg][$rg] = isset($combined[$fg][$rg]) ? $combined[$fg][$rg] + 1 : 1; + + $gears_array[$i] = ['front_gear' => $fg, 'rear_gear' => $rg]; + } + + krsort($fg_summary); + krsort($rg_summary); + krsort($combined); + + $output = ['front_gear_summary' => $fg_summary, 'rear_gear_summary' => $rg_summary, 'combined_summary' => $combined, 'gears_array' => $gears_array]; + + return $output; + } /** * Create a JSON object that contains available record message information and CPV/AEPF if requested/available.