forked from upstream-mirrors/OpenTracks
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.
This commit is contained in:
@@ -33,7 +33,7 @@ limitations under the License.
|
||||
<string name="none">NONE</string>
|
||||
<string name="ant_sensor_type">ANT</string>
|
||||
<string name="srm_ant_bridge_sensor_type">SRM_ANT_BRIDGE</string>
|
||||
<string name="polar_sensor_type">Polar</string>
|
||||
<string name="polar_sensor_type">POLAR</string>
|
||||
<string name="zephyr_sensor_type">ZEPHYR</string>
|
||||
|
||||
<!-- Send to Google URLs -->
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
-2
@@ -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);
|
||||
|
||||
+58
-47
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user