From 6b375614cabcefb8e492462ede673fb0535a4e7e Mon Sep 17 00:00:00 2001 From: JohnG Date: Thu, 19 May 2011 23:00:22 -0400 Subject: [PATCH] Fixes for Polar HRM code review of May 1. Addressed all comments. Cleaned up Polar parser mostly. Added decaying heart rate algo for now. Tried to reach out to joni to work on merging projects. No response yet. --- MyTracks/res/values/do_not_translate.xml | 2 +- .../apps/mytracks/SettingsActivity.java | 24 ++-- .../sensors/BluetoothConnectionManager.java | 3 +- .../services/sensors/PolarMessageParser.java | 105 ++++++++++-------- .../services/sensors/PolarSensorManager.java | 2 +- 5 files changed, 70 insertions(+), 66 deletions(-) diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml index 9454de3fc..17116204f 100644 --- a/MyTracks/res/values/do_not_translate.xml +++ b/MyTracks/res/values/do_not_translate.xml @@ -33,7 +33,7 @@ limitations under the License. NONE ANT SRM_ANT_BRIDGE - Polar + POLAR ZEPHYR diff --git a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java index 87773b1df..043479139 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java @@ -19,8 +19,8 @@ import static com.google.android.apps.mytracks.Constants.TAG; import com.google.android.apps.mytracks.io.backup.BackupActivityHelper; import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; +import com.google.android.apps.mytracks.services.StatusAnnouncerFactory; import com.google.android.apps.mytracks.services.sensors.ant.AntUtils; -import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory; import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.BluetoothDeviceUtils; import com.google.android.maps.mytracks.R; @@ -232,8 +232,8 @@ public class SettingsActivity extends PreferenceActivity { private void updateSensorSettings(String sensorType) { boolean usesBluetooth = - getString(R.string.zephyr_sensor_type).equals(sensorType) || - getString(R.string.polar_sensor_type).equals(sensorType); + getString(R.string.zephyr_sensor_type).equals(sensorType) + || getString(R.string.polar_sensor_type).equals(sensorType); findPreference( getString(R.string.bluetooth_sensor_key)).setEnabled(usesBluetooth); findPreference( @@ -267,6 +267,9 @@ public class SettingsActivity extends PreferenceActivity { final ListPreference minRequiredAccuracy = (ListPreference) findPreference( getString(R.string.min_required_accuracy_key)); + final ListPreference splitFrequency = + (ListPreference) findPreference( + getString(R.string.split_frequency_key)); minRecordingDistance.setEntries(isMetric ? R.array.min_recording_distance_options @@ -277,20 +280,11 @@ public class SettingsActivity extends PreferenceActivity { minRequiredAccuracy.setEntries(isMetric ? R.array.min_required_accuracy_options : R.array.min_required_accuracy_options_ft); - setTaskOptions(isMetric, R.string.announcement_frequency_key); - setTaskOptions(isMetric, R.string.split_frequency_key); + splitFrequency.setEntries(isMetric + ? R.array.split_frequency_options + : R.array.split_frequency_options_ft); } - /** - * Set the user visible options for a periodic task. - */ - private void setTaskOptions(boolean isMetric, int listId) { - final ListPreference taskFrequency = - (ListPreference) findPreference(getString(listId)); - taskFrequency.setEntries(isMetric - ? R.array.task_frequency_options - : R.array.task_frequency_options_ft); - } /** * Configures preference actions related to bluetooth. diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java index 3f1c6ba27..8fbbdc44b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java @@ -396,8 +396,7 @@ public class BluetoothConnectionManager { // Send copy of the obtained bytes to the UI Activity. // Avoids memory inconsistency issues. - byte[] b = buffer.clone(); - handler.obtainMessage(MESSAGE_READ, bytes, -1, b) + handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone()) .sendToTarget(); } catch (IOException e) { Log.e(Constants.TAG, "disconnected", e); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java index ee11135a7..1e5620bfb 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java @@ -15,84 +15,98 @@ */ package com.google.android.apps.mytracks.services.sensors; -import android.util.Log; - -import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.Sensor; /** - * An implementation of a SensorData parser for Polar Wearlink Bluetooth HRM. + * An implementation of a Sensor MessageParser for Polar Wearlink Bluetooth HRM. * + * Polar Bluetooth Wearlink packet example; + * Hdr Len Chk Seq Status HeartRate RRInterval_16-bits + * FE 08 F7 06 F1 48 03 64 + * where; + * Hdr always = 254 (0xFE), + * Chk = 255 - Len + * Seq range 0 to 15 + * Status = Upper nibble may be battery voltage + * bit 0 is Beat Detection flag. + * + * Additional packet examples; + * FE 08 F7 06 F1 48 03 64 + * FE 0A F5 06 F1 48 03 64 03 70 + * * @author John R. Gerthoffer */ public class PolarMessageParser implements MessageParser { - // Last Heart Rate value storage (in case of corrupt buffer) + private int lastHeartRate = 0; + + /** + * Applies Polar packet validation rules to buffer. + * Polar packets are checked for following; + * offset 0 = header byte, 254 (0xFE). + * offset 1 = packet length byte, 8, 10, 12, 14. + * offset 2 = check byte, 255 - packet length. + * offset 3 = sequence byte, range from 0 to 15. + * + * @param an array of bytes to parse + * @param buffer offset to beginning of packet. + * @return whether buffer has a valid packet at offset i + */ + private boolean packetValid (byte[] buffer, int i) { + boolean headerValid = (buffer[i] & 0xFF) == 0xFE; + boolean checkbyteValid = (buffer[i + 2] & 0xFF) == (0xFF - (buffer[i + 1] & 0xFF)); + boolean sequenceValid = (buffer[i + 3] & 0xFF) < 16; + + return headerValid && checkbyteValid && sequenceValid; + } @Override public Sensor.SensorDataSet parseBuffer(byte[] buffer) { - StringBuilder sb = new StringBuilder(); + int heartRate = 0; + boolean heartrateValid = false; - // Due to a memory inconsistency issue, the packet does not always start at buffer[0]. - // While it works stepping thru in debug mode, it is not consistent when running real-time. - // Note: After changing the BluetoothConnectionManager to pass a copy of 'buffer', - // the problem was resolved! Passing a buffer copy in threaded comm's is typical in my experience. - // Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends. - heartRate = lastHeartRate; // Default to use last value. (If our buffer is corrupted). for (int i = 0; i < buffer.length - 8; i++) { - boolean bHdrOK = ((buffer[i] & 0xFF) == 0xFE); - boolean bChkOK = ((buffer[i+2] & 0xFF) == (0xFF - (buffer[i+1] & 0xFF))); - boolean bSeqOK = ((buffer[i+3] & 0xFF) < 16); - boolean bStatusOK = ((buffer[i+4] & 0xFF) > 128); // I've seen 0xF1 or 0xE1. - - if (bHdrOK && bChkOK && bSeqOK && bStatusOK) { + heartrateValid = packetValid(buffer,i); + if (heartrateValid) { heartRate = buffer[i + 5] & 0xFF; - lastHeartRate = heartRate; // Remember good value for next time. - break; // Let's go store our data. + break; } } + // If our buffer is corrupted, use decaying last good value. + if(!heartrateValid) { + heartRate = (int) (lastHeartRate * 0.8); + if(heartRate < 50) + heartRate = 0; + } + + lastHeartRate = heartRate; // Remember good value for next time. + // Heart Rate Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() .setValue(heartRate) .setState(Sensor.SensorState.SENDING); Sensor.SensorDataSet sds = - Sensor.SensorDataSet.newBuilder() - .setCreationTime(System.currentTimeMillis()) - .setHeartRate(b) - .build(); + Sensor.SensorDataSet.newBuilder() + .setCreationTime(System.currentTimeMillis()) + .setHeartRate(b) + .build(); return sds; } /** - * Applies packet validation rule to buffer - * Parsing rule for a good Polar HRM packet; - * boolean goodHdr = ((buffer[0] & 0xFF) == 0xFE); - * boolean goodChk = ((buffer[2] & 0xFF) == (0xFF - (buffer[1] & 0xFF))); - * goodPacket = goodHdr && goodChk; + * Applies packet validation rules to buffer * * @param an array of bytes to parse * @return whether buffer has a valid packet starting at index zero */ @Override public boolean isValid(byte[] buffer) { - /** - * Polar Bluetooth Wearlink packet example; - * Hdr Len Chk Seq Status HeartRate RRInterval_16-bits - * FE 08 F7 06 F1 48 03 64 - * where Hdr always = 0xFE, Chk = 0xFF - Len - * - * Additional packet examples; - * FE 08 F7 06 F1 48 03 64 - * FE 0A F5 06 F1 48 03 64 03 70 - */ - boolean goodHdr = ((buffer[0] & 0xFF) == 0xFE); - boolean goodChk = ((buffer[2] & 0xFF) == (0xFF - (buffer[1] & 0xFF))); - return goodHdr && goodChk; + return packetValid(buffer,0); } /** @@ -110,18 +124,15 @@ public class PolarMessageParser implements MessageParser { /** * Searches buffer for the beginning of a valid packet. - * Parsing rule for a good Polar HRM packet; - * boolean goodHdr = ((buffer[0] & 0xFF) == 0xFE); - * boolean goodChk = ((buffer[2] & 0xFF) == (0xFF - (buffer[1] & 0xFF))); - * goodPacket = goodHdr && goodChk; * * @param an array of bytes to parse + * @return index to beginning of good packet, or -1 if none found. */ @Override public int findNextAlignment(byte[] buffer) { // Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends. for (int i = 0; i < buffer.length - 8; i++) { - if (((buffer[i] & 0xFF) == 0xFE) && ((buffer[i+2] & 0xFF) == (0xFF - (buffer[i+1] & 0xFF)))) { + if (packetValid(buffer,i)) { return i; } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java index 7240cd316..d1d1f7441 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java @@ -18,7 +18,7 @@ package com.google.android.apps.mytracks.services.sensors; import android.content.Context; /** - * PolarSensorManager - Straight copy from ZephyrSensorManager, renamed, of course. + * PolarSensorManager - A sensor manager for Polar heart rate monitors. */ public class PolarSensorManager extends BluetoothSensorManager {