I'm trying to get nrf52 DK to act as central device and accept alert notifications from the android device. I'm using in the nrf end the out-of-box nordic alert notification example that should be able to handle the notifications.
In android end I'm using modified blegatt example, that I've also added the gatt server parts.
Currently the whole shebang works like this: 1. nrf-board advertises itself and phone scans (at this point the roles are traditional as there's no UI in the nrf board). 2. I've opened the gattserver, make alert_notification_service and added characteristics to it and added everything to gattserver. 3. I connect to nrf board via regular gatt connection.
I can see that the service is connected by the board, It tries to read couple of characteristics and write the descriptor for one. However notifications (for testing purposes I'm using hardcoded content for New Alert) do not work properly.
Here's the trace output from the terminal:
04-08 16:01:24.006 BluetoothLeScanner: onScanResult() - ScanResult{mDevice=CE:CE:67:C2:71:FE, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=Nordic_Alert_Notif.], mRssi=-35, mTimestampNanos=1475540620066358}
04-08 16:01:24.071 BluetoothAdapter: stopLeScan()
04-08 16:01:24.085 Timeline: Timeline: Activity_launch_request id:com.example.android.bluetoothlegatt time:98765127
04-08 16:01:24.105 BluetoothAdapter: stopLeScan()
04-08 16:01:24.106 BluetoothAdapter: scan not started yet
04-08 16:01:24.250 BluetoothGattServer: registerCallback()
04-08 16:01:24.250 BluetoothGattServer: registerCallback() - UUID=11b1f22d-460c-4b9a-82c4-aa176dac8a1c
04-08 16:01:24.252 BluetoothGattServer: onServerRegistered() - status=0 serverIf=9
04-08 16:01:24.253 BluetoothGattServer: addService() - service: 00001811-0000-1000-8000-00805f9b34fb
04-08 16:01:24.267 BluetoothLeService: Trying to create a new connection.
04-08 16:01:24.269 BluetoothGattServer: onServiceAdded() - service=00001811-0000-1000-8000-00805f9b34fbstatus=0
04-08 16:01:24.270 BluetoothGatt: connect() - device: CE:CE:67:C2:71:FE, auto: false
04-08 16:01:24.270 BluetoothGatt: registerApp()
04-08 16:01:24.270 BluetoothGatt: registerApp() - UUID=9f766219-eb65-4f92-aedd-22ec8d845d54
04-08 16:01:24.271 BluetoothLeService: onServiceAdded
04-08 16:01:24.272 BluetoothGatt: onClientRegistered() - status=0 clientIf=10
04-08 16:01:24.331 Timeline: Timeline: Activity_idle id: android.os.BinderProxy@3650f763 time:98765373
04-08 16:01:24.349 BluetoothGattServer: onServerConnectionState() - status=0 serverIf=6 device=CE:CE:67:C2:71:FE
04-08 16:01:24.349 BluetoothLeService: onConnectionStateChage device CE:CE:67:C2:71:FE
04-08 16:01:24.350 BluetoothGattServer: onServerConnectionState() - status=0 serverIf=8 device=CE:CE:67:C2:71:FE
04-08 16:01:24.350 BluetoothLeService: onConnectionStateChage device CE:CE:67:C2:71:FE
04-08 16:01:24.350 BluetoothGattServer: onServerConnectionState() - status=0 serverIf=9 device=CE:CE:67:C2:71:FE
04-08 16:01:24.350 BluetoothLeService: onConnectionStateChage device CE:CE:67:C2:71:FE
04-08 16:01:24.353 BluetoothGattServer: onServerConnectionState() - status=0 serverIf=7 device=CE:CE:67:C2:71:FE
04-08 16:01:24.353 BluetoothLeService: onConnectionStateChage device CE:CE:67:C2:71:FE
04-08 16:01:24.356 BluetoothGatt: onClientConnectionState() - status=0 clientIf=10 device=CE:CE:67:C2:71:FE
04-08 16:01:24.356 foo: onConnectionStateChange
04-08 16:01:24.356 foo: status connected
04-08 16:01:24.356 BluetoothGatt: discoverServices() - device: CE:CE:67:C2:71:FE
04-08 16:01:24.364 BluetoothGatt: onSearchComplete() = Device=CE:CE:67:C2:71:FE Status=0
04-08 16:01:24.364 foo: onServicesDiscovered
04-08 16:01:24.736 BluetoothLeService: onCharacteristicReadRequest UUID: 00002a47-0000-1000-8000-00805f9b34fb from device: CE:CE:67:C2:71:FE
04-08 16:01:24.833 BluetoothLeService: onCharacteristicReadRequest UUID: 00002a48-0000-1000-8000-00805f9b34fb from device: CE:CE:67:C2:71:FE
04-08 16:01:24.930 BluetoothLeService: ondescriptorwriterequest UUID: 00002902-0000-1000-8000-00805f9b34fbValue: 1
04-08 16:01:24.931 /BluetoothLeService: sending nofication!
The code:
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private BluetoothGattCharacteristic mSupportedNewAlertCategory;
private BluetoothGattCharacteristic mNewAlert;
private BluetoothGattCharacteristic mSupportedUnreadCategory;
private BluetoothGattCharacteristic mUnreadAlertStatus;
private BluetoothGattCharacteristic mAlertNotificationControlPoint;
private BluetoothDevice mBluetoothDevice;
private BluetoothGattServer mGattServer;
private BluetoothGattService mAlertNotificationService;
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";
// 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) {
Log.d("foo", "onConnectionStateChange");
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d("foo", "status connected");
gatt.discoverServices();
}
if (false) {
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());
} 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 onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.d("foo", "onServicesDiscovered");
if (status == BluetoothGatt.GATT_SUCCESS) {
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.d(TAG, "setCharacterostisRead (GATT)");
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data parsing is
// carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 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());
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
/**
* Initializes a reference to the local Bluetooth adapter.
*
* @return Return true if the initialization is successful.
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
public BluetoothGattServerCallback serverCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
byte value[] = {0x03, 0x04, 0x52, 0x69, 0x63, 0x68, 0x61, 0x72, 0x64};
super.onConnectionStateChange(device, status, newState);
Log.d(TAG, "onConnectionStateChage device " + device.getAddress());
mNewAlert.setValue(value);
mGattServer.notifyCharacteristicChanged(mBluetoothDevice, mNewAlert, true);
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
mBluetoothGatt = mBluetoothDevice.connectGatt(getApplicationContext(), false, mGattCallback);
Log.d(TAG, "onServiceAdded");
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
Log.d(TAG, "onCharacteristicReadRequest UUID: " + characteristic.getUuid().toString() +" from device: " + device.getAddress());
if (characteristic.getUuid().equals(SampleGattAttributes.UUID_SUPPORTED_NEW_ALERT_CATEGORY)) {
byte value[] = {0x1f};
mSupportedNewAlertCategory.setValue(value);
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
} else if (characteristic.getUuid().equals(SampleGattAttributes.UUID_SUPPORTED_UNREAD_ALERT_CATEGORY)) {
byte value[] = {0x1f};
mSupportedUnreadCategory.setValue(value);
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
} else {
byte value[] = {0x03, 0x04, 0x52, 0x69, 0x63, 0x68, 0x61, 0x72, 0x64};
mNewAlert.setValue(value);
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.d(TAG, "onCharacteristicwriterequest UUID: " + characteristic.getUuid().toString());
mGattServer.notifyCharacteristicChanged(mBluetoothDevice, characteristic, true);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
Log.d(TAG, "ondescriptorReadrequest UUID: " + descriptor.getUuid().toString());
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
Log.d(TAG, "ondescriptorwriterequest UUID: " + descriptor.getUuid().toString() + "Value: " + value[0]);
if (value[0] == 1) {
Log.d(TAG, "sending nofication!");
mGattServer.notifyCharacteristicChanged(mBluetoothDevice, mNewAlert, true);
}
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
Log.d(TAG, "onNotificationSent");
}
};
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* @param address The device address of the destination device.
*
* @return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
if (mBluetoothDevice == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
mGattServer = mBluetoothManager.openGattServer(this, serverCallback);
mSupportedNewAlertCategory = new BluetoothGattCharacteristic(SampleGattAttributes.UUID_SUPPORTED_NEW_ALERT_CATEGORY, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_READ);
mNewAlert = new BluetoothGattCharacteristic(SampleGattAttributes.UUID_NEW_ALERT, BluetoothGattCharacteristic.FORMAT_UINT8 | BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
mSupportedUnreadCategory = new BluetoothGattCharacteristic(SampleGattAttributes.UUID_SUPPORTED_UNREAD_ALERT_CATEGORY, BluetoothGattCharacteristic.PROPERTY_READ| BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_READ);
mUnreadAlertStatus = new BluetoothGattCharacteristic(SampleGattAttributes.UUID_UNREAD_ALERT_STATUS, BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
mAlertNotificationControlPoint = new BluetoothGattCharacteristic(SampleGattAttributes.UUID_ALERT_NOTIFICATION_CONTROL_POINT, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
mAlertNotificationControlPoint.setValue(new byte[]{0});
mAlertNotificationControlPoint.setValue(new byte[]{1});
mAlertNotificationControlPoint.setValue(new byte[]{4});
mAlertNotificationControlPoint.setValue(new byte[]{5});
BluetoothGattDescriptor clientCharacteristicConfig = new BluetoothGattDescriptor(SampleGattAttributes.UUID_CLIENT_CHARACTERISTIC_CONFIG, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
clientCharacteristicConfig.setValue(new byte[]{1});
mNewAlert.addDescriptor(clientCharacteristicConfig);
mUnreadAlertStatus.addDescriptor(clientCharacteristicConfig);
mAlertNotificationService = new BluetoothGattService(SampleGattAttributes.UUID_ALERT_NOTIFICATION_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
mAlertNotificationService.addCharacteristic(mSupportedNewAlertCategory);
mAlertNotificationService.addCharacteristic(mNewAlert);
mAlertNotificationService.addCharacteristic(mSupportedUnreadCategory);
mAlertNotificationService.addCharacteristic(mUnreadAlertStatus);
mAlertNotificationService.addCharacteristic(mAlertNotificationControlPoint);
mGattServer.addService(mAlertNotificationService);
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
// mGattServer.connect(mBluetoothDevice, true);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* 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;
}
Log.d(TAG, "readCharacteristics (GATT)");
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;
}
Log.d(TAG, "setCharacteristicNotification (GATT)");
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
/**
* 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<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
Log.d(TAG, "getSupportedServices (GATT)");
return mBluetoothGatt.getServices();
}
The problem is that I cannot get the notifications go trough with the mGattServer.sendResponse()- or .notifyCharacteristicChanged() -calls.
Dunno why there doesn't seem to be any examples of the alert notification stuff. It's quite used feature so one would assume there's tons of examples.
EDIT: The main problem was that I didn't have separate UUID:s for the descriptors. I added 00002902 for one and 10002902 for the second. Also I removed all notification calls from server handling, and put gattserver.sendresponse instead. After this it works.