Migrating HRS to use new BleManager API

This commit is contained in:
Aleksander Nowakowski
2018-05-23 17:39:11 +02:00
parent 06da68c3b7
commit 21cd638758
6 changed files with 109 additions and 147 deletions

View File

@@ -28,11 +28,13 @@ import android.graphics.Point;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.achartengine.GraphicalView; import org.achartengine.GraphicalView;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.ble.BleManager; import no.nordicsemi.android.ble.BleManager;
@@ -53,8 +55,6 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
private final static String GRAPH_COUNTER = "graph_counter"; private final static String GRAPH_COUNTER = "graph_counter";
private final static String HR_VALUE = "hr_value"; private final static String HR_VALUE = "hr_value";
private final static int MAX_HR_VALUE = 65535;
private final static int MIN_POSITIVE_VALUE = 0;
private final static int REFRESH_INTERVAL = 1000; // 1 second interval private final static int REFRESH_INTERVAL = 1000; // 1 second interval
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
@@ -64,6 +64,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
private GraphicalView mGraphView; private GraphicalView mGraphView;
private LineGraphView mLineGraph; private LineGraphView mLineGraph;
private TextView mHRSValue, mHRSPosition; private TextView mHRSValue, mHRSPosition;
private TextView mBatteryLevelView;
private int mHrmValue = 0; private int mHrmValue = 0;
private int mCounter = 0; private int mCounter = 0;
@@ -78,6 +79,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
mLineGraph = LineGraphView.getLineGraphView(); mLineGraph = LineGraphView.getLineGraphView();
mHRSValue = findViewById(R.id.text_hrs_value); mHRSValue = findViewById(R.id.text_hrs_value);
mHRSPosition = findViewById(R.id.text_hrs_position); mHRSPosition = findViewById(R.id.text_hrs_position);
mBatteryLevelView = findViewById(R.id.battery);
showGraph(); showGraph();
} }
@@ -183,26 +185,6 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
return manager; return manager;
} }
private void setHRSValueOnView(final int value) {
runOnUiThread(() -> {
if (value >= MIN_POSITIVE_VALUE && value <= MAX_HR_VALUE) {
mHRSValue.setText(Integer.toString(value));
} else {
mHRSValue.setText(R.string.not_available_value);
}
});
}
private void setHRSPositionOnView(final String position) {
runOnUiThread(() -> {
if (position != null) {
mHRSPosition.setText(position);
} else {
mHRSPosition.setText(R.string.not_available);
}
});
}
@Override @Override
public void onServicesDiscovered(final BluetoothDevice device, final boolean optionalServicesFound) { public void onServicesDiscovered(final BluetoothDevice device, final boolean optionalServicesFound) {
// this may notify user or show some views // this may notify user or show some views
@@ -214,14 +196,28 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
} }
@Override @Override
public void onHRSensorPositionFound(final BluetoothDevice device, final String position) { public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) {
setHRSPositionOnView(position); runOnUiThread(() -> mBatteryLevelView.setText(getString(R.string.battery, batteryLevel)));
} }
@Override @Override
public void onHRValueReceived(final BluetoothDevice device, int value) { public void onBodySensorLocationReceived(@NonNull final BluetoothDevice device, final int sensorLocation) {
mHrmValue = value; runOnUiThread(() -> {
setHRSValueOnView(mHrmValue); if (sensorLocation >= SENSOR_LOCATION_FIRST && sensorLocation <= SENSOR_LOCATION_LAST) {
mHRSPosition.setText(getResources().getStringArray(R.array.hrs_locations)[sensorLocation]);
} else {
mHRSPosition.setText(R.string.hrs_location_other);
}
});
}
@Override
public void onHeartRateMeasurementReceived(@NonNull final BluetoothDevice device, final int heartRate,
@Nullable final Boolean contactDetected,
@Nullable final Integer energyExpanded,
@Nullable final List<Integer> rrIntervals) {
mHrmValue = heartRate;
runOnUiThread(() -> mHRSValue.setText(getString(R.string.hrs_value, heartRate)));
} }
@Override @Override
@@ -230,6 +226,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
runOnUiThread(() -> { runOnUiThread(() -> {
mHRSValue.setText(R.string.not_available_value); mHRSValue.setText(R.string.not_available_value);
mHRSPosition.setText(R.string.not_available); mHRSPosition.setText(R.string.not_available);
mBatteryLevelView.setText(R.string.not_available);
stopShowGraph(); stopShowGraph();
}); });
} }
@@ -238,6 +235,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac
protected void setDefaultUI() { protected void setDefaultUI() {
mHRSValue.setText(R.string.not_available_value); mHRSValue.setText(R.string.not_available_value);
mHRSPosition.setText(R.string.not_available); mHRSPosition.setText(R.string.not_available);
mBatteryLevelView.setText(R.string.not_available);
clearGraph(); clearGraph();
} }

View File

@@ -21,33 +21,37 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hrs; package no.nordicsemi.android.nrftoolbox.hrs;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothGattService;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Deque; import java.util.List;
import java.util.LinkedList;
import java.util.UUID; import java.util.UUID;
import no.nordicsemi.android.ble.BleManager; import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationDataCallback;
import no.nordicsemi.android.ble.Request; import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementDataCallback;
import no.nordicsemi.android.ble.data.Data;
import no.nordicsemi.android.log.LogContract; import no.nordicsemi.android.log.LogContract;
import no.nordicsemi.android.nrftoolbox.R; import no.nordicsemi.android.nrftoolbox.battery.BatteryManager;
import no.nordicsemi.android.nrftoolbox.parser.BodySensorLocationParser; import no.nordicsemi.android.nrftoolbox.parser.BodySensorLocationParser;
import no.nordicsemi.android.nrftoolbox.parser.HeartRateMeasurementParser; import no.nordicsemi.android.nrftoolbox.parser.HeartRateMeasurementParser;
/** /**
* HRSManager class performs BluetoothGatt operations for connection, service discovery, enabling notification and reading characteristics. All operations required to connect to device with BLE HR * HRSManager class performs BluetoothGatt operations for connection, service discovery,
* Service and reading heart rate values are performed here. HRSActivity implements HRSManagerCallbacks in order to receive callbacks of BluetoothGatt operations * enabling notification and reading characteristics.
* All operations required to connect to device with BLE Heart Rate Service and reading
* heart rate values are performed here.
*/ */
public class HRSManager extends BleManager<HRSManagerCallbacks> { public class HRSManager extends BatteryManager<HRSManagerCallbacks> {
public final static UUID HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); public static final UUID HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb");
private static final UUID HR_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb"); private static final UUID HR_SENSOR_LOCATION_CHARACTERISTIC_UUID = UUID.fromString("00002A38-0000-1000-8000-00805f9b34fb");
private static final UUID HR_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"); private static final UUID HR_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb");
private BluetoothGattCharacteristic mHRCharacteristic, mHRLocationCharacteristic; private BluetoothGattCharacteristic mHeartRateCharacteristic, mBodySensorLocationCharacteristic;
private static HRSManager managerInstance = null; private static HRSManager managerInstance = null;
@@ -61,93 +65,83 @@ public class HRSManager extends BleManager<HRSManagerCallbacks> {
return managerInstance; return managerInstance;
} }
public HRSManager(final Context context) { private HRSManager(final Context context) {
super(context); super(context);
} }
@NonNull @NonNull
@Override @Override
protected BleManagerGattCallback getGattCallback() { protected BatteryManagerGattCallback getGattCallback() {
return mGattCallback; return mGattCallback;
} }
/** /**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc
*/ */
private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { private final BatteryManagerGattCallback mGattCallback = new BatteryManagerGattCallback() {
@Override @Override
protected Deque<Request> initGatt(@NonNull final BluetoothGatt gatt) { protected void initialize() {
final LinkedList<Request> requests = new LinkedList<>(); super.initialize();
if (mHRLocationCharacteristic != null) readCharacteristic(mBodySensorLocationCharacteristic)
requests.add(Request.newReadRequest(mHRLocationCharacteristic)); .with(new BodySensorLocationDataCallback() {
requests.add(Request.newEnableNotificationsRequest(mHRCharacteristic)); @Override
return requests; public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.APPLICATION, "\"" + BodySensorLocationParser.parse(data) + "\" received");
super.onDataReceived(device, data);
}
@Override
public void onBodySensorLocationReceived(@NonNull final BluetoothDevice device,
final int sensorLocation) {
mCallbacks.onBodySensorLocationReceived(device, sensorLocation);
}
})
.fail((device, status) -> log(LogContract.Log.Level.WARNING, "Body Sensor Location characteristic not found"));
setNotificationCallback(mHeartRateCharacteristic)
.with(new HeartRateMeasurementDataCallback() {
@Override
public void onDataReceived(@NonNull final BluetoothDevice device, @NonNull final Data data) {
log(LogContract.Log.Level.APPLICATION, "\"" + HeartRateMeasurementParser.parse(data) + "\" received");
super.onDataReceived(device, data);
}
@Override
public void onHeartRateMeasurementReceived(@NonNull final BluetoothDevice device,
final int heartRate,
@Nullable final Boolean contactDetected,
@Nullable final Integer energyExpanded,
@Nullable final List<Integer> rrIntervals) {
mCallbacks.onHeartRateMeasurementReceived(device, heartRate, contactDetected, energyExpanded, rrIntervals);
}
});
enableNotifications(mHeartRateCharacteristic);
} }
@Override @Override
protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) { protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID); final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID);
if (service != null) { if (service != null) {
mHRCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID); mHeartRateCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID);
} }
return mHRCharacteristic != null; return mHeartRateCharacteristic != null;
} }
@Override @Override
protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) { protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) {
super.isOptionalServiceSupported(gatt);
final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID); final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID);
if (service != null) { if (service != null) {
mHRLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID); mBodySensorLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID);
} }
return mHRLocationCharacteristic != null; return mBodySensorLocationCharacteristic != null;
}
@Override
public void onCharacteristicRead(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
log(LogContract.Log.Level.APPLICATION, "\"" + BodySensorLocationParser.parse(characteristic) + "\" received");
final String sensorPosition = getBodySensorPosition(characteristic.getValue()[0]);
//This will send callback to HRSActivity when HR sensor position on body is found in HR device
mCallbacks.onHRSensorPositionFound(gatt.getDevice(), sensorPosition);
} }
@Override @Override
protected void onDeviceDisconnected() { protected void onDeviceDisconnected() {
mHRLocationCharacteristic = null; super.onDeviceDisconnected();
mHRCharacteristic = null; mBodySensorLocationCharacteristic = null;
} mHeartRateCharacteristic = null;
@Override
public void onCharacteristicNotified(@NonNull final BluetoothGatt gatt, @NonNull final BluetoothGattCharacteristic characteristic) {
log(LogContract.Log.Level.APPLICATION, "\"" + HeartRateMeasurementParser.parse(characteristic) + "\" received");
int hrValue;
if (isHeartRateInUINT16(characteristic.getValue()[0])) {
hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 1);
} else {
hrValue = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 1);
}
//This will send callback to HRSActivity when new HR value is received from HR device
mCallbacks.onHRValueReceived(gatt.getDevice(), hrValue);
} }
}; };
/**
* This method will decode and return Heart rate sensor position on body
*/
private String getBodySensorPosition(final byte bodySensorPositionValue) {
final String[] locations = getContext().getResources().getStringArray(R.array.hrs_locations);
if (bodySensorPositionValue > locations.length)
return getContext().getString(R.string.hrs_location_other);
return locations[bodySensorPositionValue];
}
/**
* This method will check if Heart rate value is in 8 bits or 16 bits
*/
private boolean isHeartRateInUINT16(final byte value) {
return ((value & 0x01) != 0);
}
} }

View File

@@ -21,27 +21,10 @@
*/ */
package no.nordicsemi.android.nrftoolbox.hrs; package no.nordicsemi.android.nrftoolbox.hrs;
import android.bluetooth.BluetoothDevice; import no.nordicsemi.android.ble.common.profile.hr.BodySensorLocationCallback;
import no.nordicsemi.android.ble.common.profile.hr.HeartRateMeasurementCallback;
import no.nordicsemi.android.nrftoolbox.battery.BatteryManagerCallbacks;
import no.nordicsemi.android.ble.BleManagerCallbacks; interface HRSManagerCallbacks extends BatteryManagerCallbacks, BodySensorLocationCallback, HeartRateMeasurementCallback {
public interface HRSManagerCallbacks extends BleManagerCallbacks {
/**
* Called when the sensor position information has been obtained from the sensor
*
* @param device the bluetooth device from which the value was obtained
* @param position
* the sensor position
*/
void onHRSensorPositionFound(final BluetoothDevice device, String position);
/**
* Called when new Heart Rate value has been obtained from the sensor
*
* @param device the bluetooth device from which the value was obtained
* @param value
* the new value
*/
void onHRValueReceived(final BluetoothDevice device, int value);
} }

View File

@@ -21,36 +21,22 @@
*/ */
package no.nordicsemi.android.nrftoolbox.parser; package no.nordicsemi.android.nrftoolbox.parser;
import android.bluetooth.BluetoothGattCharacteristic; import no.nordicsemi.android.ble.data.Data;
public class BodySensorLocationParser { public class BodySensorLocationParser {
public static String parse(final BluetoothGattCharacteristic characteristic) { public static String parse(final Data data) {
final int value = unsignedByteToInt(characteristic.getValue()[0]); final int value = data.getIntValue(Data.FORMAT_UINT8, 0);
switch (value) { switch (value) {
case 6: case 6: return "Foot";
return "Foot"; case 5: return "Ear Lobe";
case 5: case 4: return "Hand";
return "Ear Lobe"; case 3: return "Finger";
case 4: case 2: return "Wrist";
return "Hand"; case 1: return "Chest";
case 3: case 0:
return "Finger"; default: return "Other";
case 2:
return "Wrist";
case 1:
return "Chest";
case 0:
default:
return "Other";
} }
} }
/**
* Convert a signed byte to an unsigned int.
*/
private static int unsignedByteToInt(byte b) {
return b & 0xFF;
}
} }

View File

@@ -21,21 +21,21 @@
*/ */
package no.nordicsemi.android.nrftoolbox.parser; package no.nordicsemi.android.nrftoolbox.parser;
import android.bluetooth.BluetoothGattCharacteristic;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import no.nordicsemi.android.ble.data.Data;
public class HeartRateMeasurementParser { public class HeartRateMeasurementParser {
private static final byte HEART_RATE_VALUE_FORMAT = 0x01; // 1 bit private static final byte HEART_RATE_VALUE_FORMAT = 0x01; // 1 bit
private static final byte SENSOR_CONTACT_STATUS = 0x06; // 2 bits private static final byte SENSOR_CONTACT_STATUS = 0x06; // 2 bits
private static final byte ENERGY_EXPANDED_STATUS = 0x08; // 1 bit private static final byte ENERGY_EXPANDED_STATUS = 0x08; // 1 bit
private static final byte RR_INTERVAL = 0x10; // 1 bit private static final byte RR_INTERVAL = 0x10; // 1 bit
public static String parse(final BluetoothGattCharacteristic characteristic) { public static String parse(final Data data) {
int offset = 0; int offset = 0;
final int flags = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset++); final int flags = data.getIntValue(Data.FORMAT_UINT8, offset++);
/* /*
* false Heart Rate Value Format is set to UINT8. Units: beats per minute (bpm) * false Heart Rate Value Format is set to UINT8. Units: beats per minute (bpm)
@@ -64,21 +64,21 @@ public class HeartRateMeasurementParser {
final boolean rrIntervalStatus = (flags & RR_INTERVAL) > 0; final boolean rrIntervalStatus = (flags & RR_INTERVAL) > 0;
// heart rate value is 8 or 16 bit long // heart rate value is 8 or 16 bit long
int heartRateValue = characteristic.getIntValue(value16bit ? BluetoothGattCharacteristic.FORMAT_UINT16 : BluetoothGattCharacteristic.FORMAT_UINT8, offset++); // bits per minute int heartRateValue = data.getIntValue(value16bit ? Data.FORMAT_UINT16 : Data.FORMAT_UINT8, offset++); // bits per minute
if (value16bit) if (value16bit)
offset++; offset++;
// energy expanded value is present if a flag was set // energy expanded value is present if a flag was set
int energyExpanded = -1; int energyExpanded = -1;
if (energyExpandedStatus) if (energyExpandedStatus)
energyExpanded = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); energyExpanded = data.getIntValue(Data.FORMAT_UINT16, offset);
offset += 2; offset += 2;
// RR-interval is set when a flag is set // RR-interval is set when a flag is set
final List<Float> rrIntervals = new ArrayList<>(); final List<Float> rrIntervals = new ArrayList<>();
if (rrIntervalStatus) { if (rrIntervalStatus) {
for (int o = offset; o < characteristic.getValue().length; o += 2) { for (int o = offset; o < data.getValue().length; o += 2) {
final int units = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, o); final int units = data.getIntValue(Data.FORMAT_UINT16, o);
rrIntervals.add(units * 1000.0f / 1024.0f); // RR interval is in [1/1024s] rrIntervals.add(units * 1000.0f / 1024.0f); // RR interval is in [1/1024s]
} }
} }

View File

@@ -27,6 +27,7 @@
<dimen name="hrs_feature_title_long_margin">-152dp</dimen> <dimen name="hrs_feature_title_long_margin">-152dp</dimen>
<string name="hrs_default_name">DEFAULT HRM</string> <string name="hrs_default_name">DEFAULT HRM</string>
<string name="hrs_value">%d</string>
<string name="hrs_value_unit">bpm</string> <string name="hrs_value_unit">bpm</string>
<string name="hrs_position_label">sensor position</string> <string name="hrs_position_label">sensor position</string>