Removed Polar and Zephyr (pre Bluetooth LE) sensors.

This commit is contained in:
Dennis Guse
2019-07-04 23:29:26 +02:00
parent 864f7cb595
commit 3a685c609d
11 changed files with 1 additions and 621 deletions
@@ -1,138 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor;
/**
* 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 {
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 buffer an array of bytes to parse
* @param i 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) {
int heartRate = 0;
boolean heartrateValid = false;
// Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends.
for (int i = 0; i < buffer.length - 8; i++) {
heartrateValid = packetValid(buffer,i);
if (heartrateValid) {
heartRate = buffer[i + 5] & 0xFF;
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);
return Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis())
.setHeartRate(b)
.build();
}
/**
* Applies packet validation rules to buffer
*
* @param buffer an array of bytes to parse
* @return whether buffer has a valid packet starting at index zero
*/
@Override
public boolean isValid(byte[] buffer) {
return packetValid(buffer,0);
}
/**
* Polar uses variable packet sizes; 8, 10, 12, 14 and rarely 16.
* The most frequent are 8 and 10.
* We will wait for 16 bytes.
* This way, we are assured of getting one good one.
*
* @return the size of buffer needed to parse a good packet
*/
@Override
public int getFrameSize() {
return 16;
}
/**
* Searches buffer for the beginning of a valid packet.
*
* @param buffer 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 (packetValid(buffer,i)) {
return i;
}
}
return -1;
}
}
@@ -1,30 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
/**
* A Polar heart rate sensor manager.
*
* @author John R. Gerthoffer
*/
public class PolarSensorManager extends BluetoothSensorManager {
public PolarSensorManager(Context context) {
super(context, new PolarMessageParser());
}
}
@@ -91,13 +91,8 @@ public class SensorManagerFactory {
* @param context the context
*/
private static SensorManager getSensorManager(Context context) {
String sensorType = PreferencesUtils.getString(
context, R.string.sensor_type_key, PreferencesUtils.SENSOR_TYPE_DEFAULT);
//TODO Implement Bluetooth LE Sensor manager
switch (sensorType) {
case "ZEPHYR": return new ZephyrSensorManager(context);
case "POLAR": return new PolarSensorManager(context);
}
return null;
}
}
@@ -1,80 +0,0 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import java.util.LinkedList;
import java.util.List;
/**
* A history of Zephyr stride counter reading.
* These can be used as an alternate method to calculate the correct cadence.
* This is a work around for an issue with some HxM firmware.
*
* @author Dominik Rottsches
*/
public class StrideReadings {
// visible for testing
protected static final int NUM_READINGS_FOR_AVERAGE = 10;
protected static final int MIN_READINGS_FOR_AVERAGE = 5;
protected static final int CADENCE_NOT_AVAILABLE = -1;
// TODO: Check whether 1Hz assumption is okay for cadence calculation
// otherwise add heart beat timestamp to this list and compute
// cadence from these timestamps.
private final List<Integer> strideReadingsHistory;
public StrideReadings() {
strideReadingsHistory = new LinkedList<>();
}
public void updateStrideReading(int numStrides) {
// HRM/HxM documentation says, transmission frequency is 1 Hz,
// let's keep last NUM_READINGS_FOR_AVERAGE readings.
// TODO: Calibrate this using a reliable footpod / cadence sensor,
// otherwise use heartbeat timestamp for calculation.
strideReadingsHistory.add(0, numStrides);
while (strideReadingsHistory.size() > NUM_READINGS_FOR_AVERAGE) {
strideReadingsHistory.remove(strideReadingsHistory.size()-1);
}
}
public int getCadence() {
if (strideReadingsHistory.size() < MIN_READINGS_FOR_AVERAGE) {
// Bail out if we cannot really get a meaningful average yet.
return CADENCE_NOT_AVAILABLE;
}
// Compute assuming 1 stride reading/second.
int timeSinceOldestReadingSecs = strideReadingsHistory.size() - 1;
int stridesThen = strideReadingsHistory.get(strideReadingsHistory.size()-1);
int stridesNow = strideReadingsHistory.get(0);
// Contrary to documentation stride value seems to roll over at 128.
return Math.round( (float)(mod((stridesNow - stridesThen), 128)) /
timeSinceOldestReadingSecs * 60);
}
/**
* Modulo operation with positive return values, Java's remainder operator doesn't change sign.
*
* @return x mod y
*/
private static int mod(int x, int y)
{
int result = x % y;
return result < 0 ? result + y : result;
}
}
@@ -1,108 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor;
import java.util.Arrays;
/**
* An implementation of a Sensor MessageParser for Zephyr.
*
* @author Sandor Dornbush
* @author Dominik Ršttsches
*/
public class ZephyrMessageParser implements MessageParser {
public static final int ZEPHYR_HXM_BYTE_STX = 0;
public static final int ZEPHYR_HXM_BYTE_CRC = 58;
public static final int ZEPHYR_HXM_BYTE_ETX = 59;
private static final byte[] CADENCE_BUG_FW_ID = {0x1A, 0x00, 0x31, 0x65, 0x50, 0x00, 0x31, 0x62};
private StrideReadings strideReadings;
@Override
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
Sensor.SensorDataSet.Builder sds =
Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis());
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
.setValue(buffer[12] & 0xFF)
.setState(Sensor.SensorState.SENDING);
sds.setHeartRate(heartrate);
Sensor.SensorData.Builder batteryLevel = Sensor.SensorData.newBuilder()
.setValue(buffer[11])
.setState(Sensor.SensorState.SENDING);
sds.setBatteryLevel(batteryLevel);
setCadence(sds, buffer);
return sds.build();
}
private void setCadence(Sensor.SensorDataSet.Builder sds, byte[] buffer) {
// Device Firmware ID, Firmware Version, Hardware ID, Hardware Version
// 0x1A00316550003162 produces erroneous values for Cadence and needs
// a workaround based on the stride counter.
// Firmware values range from field 3 to 10 (inclusive) of the byte buffer.
byte[] hardwareFirmwareId = Arrays.copyOfRange(buffer, 3, 11);
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder();
if (Arrays.equals(hardwareFirmwareId, CADENCE_BUG_FW_ID)) {
if (strideReadings == null) {
strideReadings = new StrideReadings();
}
strideReadings.updateStrideReading(buffer[54] & 0xFF);
if (strideReadings.getCadence() != StrideReadings.CADENCE_NOT_AVAILABLE) {
cadence.setValue(strideReadings.getCadence()).setState(Sensor.SensorState.SENDING);
}
} else {
cadence
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
.setState(Sensor.SensorState.SENDING);
}
sds.setCadence(cadence);
}
@Override
public boolean isValid(byte[] buffer) {
// Check STX (Start of Text), ETX (End of Text) and CRC Checksum
return buffer.length > ZEPHYR_HXM_BYTE_ETX
&& buffer[ZEPHYR_HXM_BYTE_STX] == 0x02
&& buffer[ZEPHYR_HXM_BYTE_ETX] == 0x03
&& SensorUtils.getCrc8(buffer, 3, 55) == buffer[ZEPHYR_HXM_BYTE_CRC];
}
@Override
public int getFrameSize() {
return 60;
}
@Override
public int findNextAlignment(byte[] buffer) {
// TODO test or understand this code.
for (int i = 0; i < buffer.length - 1; i++) {
if (buffer[i] == 0x03 && buffer[i+1] == 0x02) {
return i;
}
}
return -1;
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
/**
* A Zephyr heart rate sensor manager.
*
* @author Jimmy Shih
*/
public class ZephyrSensorManager extends BluetoothSensorManager {
public ZephyrSensorManager(Context context) {
super(context, new ZephyrMessageParser());
}
}
@@ -44,8 +44,6 @@ public class SensorSettingsActivity extends AbstractSettingsActivity {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.sensor_settings);
configSensorType();
findPreference(getString(R.string.settings_sensor_bluetooth_pairing_key))
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
@@ -56,34 +54,6 @@ public class SensorSettingsActivity extends AbstractSettingsActivity {
});
}
private void configSensorType() {
ListPreference preference = (ListPreference) findPreference(getString(R.string.sensor_type_key));
String value = PreferencesUtils.getString(
this, R.string.sensor_type_key, PreferencesUtils.SENSOR_TYPE_DEFAULT);
String[] options = getResources().getStringArray(R.array.sensor_type_bluetooth_options);
String[] values = getResources().getStringArray( R.array.sensor_type_bluetooth_values);
OnPreferenceChangeListener listener = new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
updateUiBySensorType((String) newValue);
return true;
}
};
configureListPreference(preference, options, options, values, value, listener);
}
/**
* Updates the UI based on the sensor type.
*
* @param sensorType the sensor type
*/
private void updateUiBySensorType(String sensorType) {
boolean isBluetooth = getString(R.string.sensor_type_value_polar).equals(sensorType) || getString(R.string.sensor_type_value_zephyr).equals(sensorType);
findPreference(getString(R.string.settings_sensor_bluetooth_key)).setEnabled(isBluetooth);
}
@Override
protected void onResume() {
super.onResume();