From 21cd638758dc6a88aa7753c07115f211da96cec7 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 23 May 2018 17:39:11 +0200 Subject: [PATCH] Migrating HRS to use new BleManager API --- .../android/nrftoolbox/hrs/HRSActivity.java | 52 ++++---- .../android/nrftoolbox/hrs/HRSManager.java | 126 +++++++++--------- .../nrftoolbox/hrs/HRSManagerCallbacks.java | 25 +--- .../parser/BodySensorLocationParser.java | 36 ++--- .../parser/HeartRateMeasurementParser.java | 16 +-- app/src/main/res/values/strings_hrs.xml | 1 + 6 files changed, 109 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java index 65a34d48..b80ece9c 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSActivity.java @@ -28,11 +28,13 @@ import android.graphics.Point; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.ViewGroup; import android.widget.TextView; import org.achartengine.GraphicalView; +import java.util.List; import java.util.UUID; 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 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 Handler mHandler = new Handler(); @@ -64,6 +64,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac private GraphicalView mGraphView; private LineGraphView mLineGraph; private TextView mHRSValue, mHRSPosition; + private TextView mBatteryLevelView; private int mHrmValue = 0; private int mCounter = 0; @@ -78,6 +79,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac mLineGraph = LineGraphView.getLineGraphView(); mHRSValue = findViewById(R.id.text_hrs_value); mHRSPosition = findViewById(R.id.text_hrs_position); + mBatteryLevelView = findViewById(R.id.battery); showGraph(); } @@ -183,26 +185,6 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac 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 public void onServicesDiscovered(final BluetoothDevice device, final boolean optionalServicesFound) { // this may notify user or show some views @@ -214,14 +196,28 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac } @Override - public void onHRSensorPositionFound(final BluetoothDevice device, final String position) { - setHRSPositionOnView(position); + public void onBatteryLevelChanged(@NonNull final BluetoothDevice device, final int batteryLevel) { + runOnUiThread(() -> mBatteryLevelView.setText(getString(R.string.battery, batteryLevel))); } @Override - public void onHRValueReceived(final BluetoothDevice device, int value) { - mHrmValue = value; - setHRSValueOnView(mHrmValue); + public void onBodySensorLocationReceived(@NonNull final BluetoothDevice device, final int sensorLocation) { + runOnUiThread(() -> { + 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 rrIntervals) { + mHrmValue = heartRate; + runOnUiThread(() -> mHRSValue.setText(getString(R.string.hrs_value, heartRate))); } @Override @@ -230,6 +226,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac runOnUiThread(() -> { mHRSValue.setText(R.string.not_available_value); mHRSPosition.setText(R.string.not_available); + mBatteryLevelView.setText(R.string.not_available); stopShowGraph(); }); } @@ -238,6 +235,7 @@ public class HRSActivity extends BleProfileActivity implements HRSManagerCallbac protected void setDefaultUI() { mHRSValue.setText(R.string.not_available_value); mHRSPosition.setText(R.string.not_available); + mBatteryLevelView.setText(R.string.not_available); clearGraph(); } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java index ab14f276..5b242508 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManager.java @@ -21,33 +21,37 @@ */ package no.nordicsemi.android.nrftoolbox.hrs; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; -import java.util.Deque; -import java.util.LinkedList; +import java.util.List; import java.util.UUID; -import no.nordicsemi.android.ble.BleManager; -import no.nordicsemi.android.ble.Request; +import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationDataCallback; +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.nrftoolbox.R; +import no.nordicsemi.android.nrftoolbox.battery.BatteryManager; import no.nordicsemi.android.nrftoolbox.parser.BodySensorLocationParser; 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 - * Service and reading heart rate values are performed here. HRSActivity implements HRSManagerCallbacks in order to receive callbacks of BluetoothGatt operations + * HRSManager class performs BluetoothGatt operations for connection, service discovery, + * 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 { - public final static UUID HR_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"); +public class HRSManager extends BatteryManager { + 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_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"); - private BluetoothGattCharacteristic mHRCharacteristic, mHRLocationCharacteristic; + private BluetoothGattCharacteristic mHeartRateCharacteristic, mBodySensorLocationCharacteristic; private static HRSManager managerInstance = null; @@ -61,93 +65,83 @@ public class HRSManager extends BleManager { return managerInstance; } - public HRSManager(final Context context) { + private HRSManager(final Context context) { super(context); } @NonNull @Override - protected BleManagerGattCallback getGattCallback() { + protected BatteryManagerGattCallback getGattCallback() { return mGattCallback; } /** * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving notification, etc */ - private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() { + private final BatteryManagerGattCallback mGattCallback = new BatteryManagerGattCallback() { @Override - protected Deque initGatt(@NonNull final BluetoothGatt gatt) { - final LinkedList requests = new LinkedList<>(); - if (mHRLocationCharacteristic != null) - requests.add(Request.newReadRequest(mHRLocationCharacteristic)); - requests.add(Request.newEnableNotificationsRequest(mHRCharacteristic)); - return requests; + protected void initialize() { + super.initialize(); + readCharacteristic(mBodySensorLocationCharacteristic) + .with(new BodySensorLocationDataCallback() { + @Override + 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 rrIntervals) { + mCallbacks.onHeartRateMeasurementReceived(device, heartRate, contactDetected, energyExpanded, rrIntervals); + } + }); + enableNotifications(mHeartRateCharacteristic); } @Override protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) { final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID); if (service != null) { - mHRCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID); + mHeartRateCharacteristic = service.getCharacteristic(HR_CHARACTERISTIC_UUID); } - return mHRCharacteristic != null; + return mHeartRateCharacteristic != null; } @Override protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) { + super.isOptionalServiceSupported(gatt); final BluetoothGattService service = gatt.getService(HR_SERVICE_UUID); if (service != null) { - mHRLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID); + mBodySensorLocationCharacteristic = service.getCharacteristic(HR_SENSOR_LOCATION_CHARACTERISTIC_UUID); } - return mHRLocationCharacteristic != 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); + return mBodySensorLocationCharacteristic != null; } @Override protected void onDeviceDisconnected() { - mHRLocationCharacteristic = null; - mHRCharacteristic = 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); + super.onDeviceDisconnected(); + mBodySensorLocationCharacteristic = null; + mHeartRateCharacteristic = null; } }; - - /** - * 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); - } - } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java index aa55d822..79f18706 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs/HRSManagerCallbacks.java @@ -21,27 +21,10 @@ */ 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); } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java index 7813f13d..bb1186ce 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/BodySensorLocationParser.java @@ -21,36 +21,22 @@ */ package no.nordicsemi.android.nrftoolbox.parser; -import android.bluetooth.BluetoothGattCharacteristic; +import no.nordicsemi.android.ble.data.Data; public class BodySensorLocationParser { - public static String parse(final BluetoothGattCharacteristic characteristic) { - final int value = unsignedByteToInt(characteristic.getValue()[0]); + public static String parse(final Data data) { + final int value = data.getIntValue(Data.FORMAT_UINT8, 0); switch (value) { - case 6: - return "Foot"; - case 5: - return "Ear Lobe"; - case 4: - return "Hand"; - case 3: - return "Finger"; - case 2: - return "Wrist"; - case 1: - return "Chest"; - case 0: - default: - return "Other"; + case 6: return "Foot"; + case 5: return "Ear Lobe"; + case 4: return "Hand"; + case 3: return "Finger"; + 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; - } } diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java index 205d38a5..ca65506f 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/parser/HeartRateMeasurementParser.java @@ -21,21 +21,21 @@ */ package no.nordicsemi.android.nrftoolbox.parser; -import android.bluetooth.BluetoothGattCharacteristic; - import java.util.ArrayList; import java.util.List; import java.util.Locale; +import no.nordicsemi.android.ble.data.Data; + public class HeartRateMeasurementParser { 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 ENERGY_EXPANDED_STATUS = 0x08; // 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; - 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) @@ -64,21 +64,21 @@ public class HeartRateMeasurementParser { final boolean rrIntervalStatus = (flags & RR_INTERVAL) > 0; // 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) offset++; // energy expanded value is present if a flag was set int energyExpanded = -1; if (energyExpandedStatus) - energyExpanded = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset); + energyExpanded = data.getIntValue(Data.FORMAT_UINT16, offset); offset += 2; // RR-interval is set when a flag is set final List rrIntervals = new ArrayList<>(); if (rrIntervalStatus) { - for (int o = offset; o < characteristic.getValue().length; o += 2) { - final int units = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, o); + for (int o = offset; o < data.getValue().length; o += 2) { + final int units = data.getIntValue(Data.FORMAT_UINT16, o); rrIntervals.add(units * 1000.0f / 1024.0f); // RR interval is in [1/1024s] } } diff --git a/app/src/main/res/values/strings_hrs.xml b/app/src/main/res/values/strings_hrs.xml index 4da0a7f6..03567b13 100644 --- a/app/src/main/res/values/strings_hrs.xml +++ b/app/src/main/res/values/strings_hrs.xml @@ -27,6 +27,7 @@ -152dp DEFAULT HRM + %d bpm sensor position