From dc5a43040b8146581d76d1eb831e2d2e3c82b1ad Mon Sep 17 00:00:00 2001 From: Graham Jones Date: Sat, 8 Aug 2020 22:25:34 +0100 Subject: [PATCH] BLE Service discovery working - next to check characteristics exist and subscribe to notifications from them. --- .../openseizuredetector/GattAttributes.java | 24 ++ .../openseizuredetector/SdDataSourceBLE.java | 236 +++++++++++++++++- 2 files changed, 250 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/uk/org/openseizuredetector/GattAttributes.java diff --git a/app/src/main/java/uk/org/openseizuredetector/GattAttributes.java b/app/src/main/java/uk/org/openseizuredetector/GattAttributes.java new file mode 100644 index 0000000..9d890f1 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/GattAttributes.java @@ -0,0 +1,24 @@ +package uk.org.openseizuredetector; +// Defines the servies and characteristics we need to subscribe to. +import java.util.HashMap; + +public class GattAttributes { + private static HashMap attributes = new HashMap(); + public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb"; + public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; + + + static { + // Sample Services. + attributes.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate Service"); + attributes.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information Service"); + // Sample Characteristics. + attributes.put("00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement"); + attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String"); + } + + public static String lookup(String uuid, String defaultName) { + String name = attributes.get(uuid); + return name == null ? defaultName : name; + } +} diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java index 9b0e68c..ce8dfa7 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceBLE.java @@ -23,12 +23,23 @@ */ package uk.org.openseizuredetector; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.preference.PreferenceManager; import android.text.format.Time; import android.util.Log; @@ -40,18 +51,42 @@ import org.json.JSONObject; import org.jtransforms.fft.DoubleFFT_1D; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Timer; import java.util.TimerTask; +import java.util.UUID; /** * A data source that registers for BLE GATT notifications from a device and * waits to be notified of data being available. */ public class SdDataSourceBLE extends SdDataSource { - private SdDataBroadcastReceiver mSdDataBroadcastReceiver; - - private String TAG = "SdDataSourceBLE"; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + private String mBluetoothDeviceAddress; + private BluetoothGatt mBluetoothGatt; + private int mConnectionState = STATE_DISCONNECTED; + + private static final int STATE_DISCONNECTED = 0; + private static final int STATE_CONNECTING = 1; + private static final int STATE_CONNECTED = 2; + + public final static String ACTION_GATT_CONNECTED = + "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; + public final static String ACTION_GATT_DISCONNECTED = + "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; + public final static String ACTION_GATT_SERVICES_DISCOVERED = + "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; + public final static String ACTION_DATA_AVAILABLE = + "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; + public final static String EXTRA_DATA = + "com.example.bluetooth.le.EXTRA_DATA"; + + public final static UUID UUID_HEART_RATE_MEASUREMENT = + UUID.fromString(GattAttributes.HEART_RATE_MEASUREMENT); + public SdDataSourceBLE(Context context, Handler handler, @@ -79,6 +114,36 @@ public class SdDataSourceBLE extends SdDataSource { mContext.startActivity(intent); } Log.i(TAG,"mBLEDevice is "+mBleDeviceName+", Addr="+mBleDeviceAddr); + + + // Now we have selected a BLE Device, open the bluetooth adapter and connect to it. + if (mBluetoothManager == null) { + mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + Log.e(TAG, "Unable to initialize BluetoothManager."); + } + } + + mBluetoothAdapter = mBluetoothManager.getAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "Unable to obtain a BluetoothAdapter."); + } + + if (mBluetoothAdapter == null || mBleDeviceAddr == null) { + Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); + } + + final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mBleDeviceAddr); + if (device == null) { + Log.w(TAG, "Device not found. Unable to connect."); + } + // We want to directly connect to the device, so we are setting the autoConnect + // parameter to false. + mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback); + Log.d(TAG, "Trying to create a new connection."); + mBluetoothDeviceAddress = mBleDeviceAddr; + mConnectionState = STATE_CONNECTING; + } /** @@ -87,6 +152,18 @@ public class SdDataSourceBLE extends SdDataSource { public void stop() { Log.i(TAG, "stop()"); mUtil.writeToSysLogFile("SDDataSourceBLE.stop()"); + + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.disconnect(); + if (mBluetoothGatt == null) { + return; + } + mBluetoothGatt.close(); + mBluetoothGatt = null; + super.stop(); } @@ -94,22 +171,161 @@ public class SdDataSourceBLE extends SdDataSource { + // Implements callback methods for GATT events that the app cares about. For example, + // connection change and services discovered. + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + String intentAction; + if (newState == BluetoothProfile.STATE_CONNECTED) { + intentAction = ACTION_GATT_CONNECTED; + mConnectionState = STATE_CONNECTED; + broadcastUpdate(intentAction); + Log.i(TAG, "Connected to GATT server."); + // Attempts to discover services after successful connection. + Log.i(TAG, "Attempting to start service discovery:" + + mBluetoothGatt.discoverServices()); - public class SdDataBroadcastReceiver extends BroadcastReceiver { - //private String TAG = "SdDataBroadcastReceiver"; + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + intentAction = ACTION_GATT_DISCONNECTED; + mConnectionState = STATE_DISCONNECTED; + Log.i(TAG, "Disconnected from GATT server."); + broadcastUpdate(intentAction); + } + } @Override - public void onReceive(Context context, Intent intent) { - Log.v(TAG,"SdDataBroadcastReceiver.onReceive()"); - String jsonStr = intent.getStringExtra("data"); - Log.v(TAG,"SdDataBroadcastReceiver.onReceive() - data="+jsonStr); - updateFromJSON(jsonStr); + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + if (status == BluetoothGatt.GATT_SUCCESS) { + Log.v(TAG,"Services discovered"); + List serviceList = mBluetoothGatt.getServices(); + for (int i=0; i 0) { + final StringBuilder stringBuilder = new StringBuilder(data.length); + for(byte byteChar : data) + stringBuilder.append(String.format("%02X ", byteChar)); + intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); + } + } + mContext.sendBroadcast(intent); + } + + + + /** + * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported + * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)} + * callback. + * + * @param characteristic The characteristic to read from. + */ + public void readCharacteristic(BluetoothGattCharacteristic characteristic) { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.readCharacteristic(characteristic); + } + + /** + * Enables or disables notification on a give characteristic. + * + * @param characteristic Characteristic to act on. + * @param enabled If true, enable notification. False otherwise. + */ + public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, + boolean enabled) { + if (mBluetoothAdapter == null || mBluetoothGatt == null) { + Log.w(TAG, "BluetoothAdapter not initialized"); + return; + } + mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); + + // This is specific to Heart Rate Measurement. + if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { + BluetoothGattDescriptor descriptor = characteristic.getDescriptor( + UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); + descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + mBluetoothGatt.writeDescriptor(descriptor); } } + /** + * Retrieves a list of supported GATT services on the connected device. This should be + * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. + * + * @return A {@code List} of supported services. + */ + public List getSupportedGattServices() { + if (mBluetoothGatt == null) return null; + + return mBluetoothGatt.getServices(); + } + + } + + + +