After successfully solving the familiar problem in onCharacteristicwrite, I continue to encounter those status 133 in readCharacteristic function.
A brief code here: I store characteristics to variables in onServicesDiscovered function:
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
try {
syncDataService = gatt.getService(GiraffeFriendAttributes.SYNC_DATA_SERVICE);
if (syncDataService == null) throw new AssertionError("sync data service null!");
syncDataInputChar = syncDataService.getCharacteristic(GiraffeFriendAttributes.SYNC_DATA_INPUT_CHAR);
syncDataOutputChar = syncDataService.getCharacteristic(GiraffeFriendAttributes.SYNC_DATA_OUTPUT_CHAR);
if (syncDataInputChar == null || syncDataOutputChar == null) throw new AssertionError("sync data service null!");
...
} catch ...
}
And then after some writing of SYNC_DATA_INPUT_CHAR into the device, the device will change the value of one of it's characteristic and I will need to fetch that value. So I write codes below.
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.d(TAG, String.format("Sync: onCharWrite, status = %d", status));
try {
...
else if (characteristic.getUuid().equals(SYNC_DATA_INPUT_CHAR)) {
Log.d(TAG, String.format("Sync: on write data index: %x, %x", dataIndexs[0], dataIndexs[1]));
gatt.readCharacteristic(syncDataOutputChar);
}
} catch ...
}
Error occurs in the readCharacteristic function, it triggers the onCharacteristicRead function with status 133.
Here are some logs:
D/BluetoothGatt﹕ writeCharacteristic() - uuid: 0000ffa6-0000-1000-8000-00805f9b34fb
D/BluetoothGatt﹕ onCharacteristicRead() - Device=78:A5:04:3D:4F:C6 UUID=0000ffab-0000-1000-8000-00805f9b34fb Status=133
W/BluetoothGatt﹕ Unhandled exception: java.lang.NullPointerException: src == null
onClientConnectionState() - status=0 clientIf=4 device=78:A5:04:3D:4F:C6
As #benka has told me, I've checked the properties of the characteristic and found that the value is 10. I thought it should be 2(PROPERTY_READ) + 8(PROPERTY_WRITE), so directly call function readCharacteristic should be OK. I will put all attributes of the characteristic below.
syncDataOutputChar = {android.bluetooth.BluetoothGattCharacteristic#830030678056}
mDescriptors = {java.util.ArrayList#830030679448} size = 0
mValue = null
mUuid = {java.util.UUID#830030680416}"0000ffab-0000-1000-8000-00805f9b34fb"
mService = {android.bluetooth.BluetoothGattService#830031005904}
mProperties = 10
mPermissions = 0
mKeySize = 16
mInstance = 0
mWriteType = 2
I hope if anyone has this familiar problem and may kindly give me some suggestions.
Thanks a lot!
--- EDIT 1
I forgot to say that, the value above is all decimal, it can be view as hex though.
--- EDIT 2
After trying for a long time, problem remains unsolved, yet I've made some experiments.
Since the characteristic to read is both readable and writable, I tried to write something into it, just to see what will happen.
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.d(TAG, String.format("Sync: onCharWrite, status = %d", status));
try {
...
else if (characteristic.getUuid().equals(SYNC_DATA_INPUT_CHAR)) {
Log.d(TAG, String.format("Sync: on write data index: %x, %x", dataIndexs[0], dataIndexs[1]));
//gatt.readCharacteristic(syncDataOutputChar);
syncDataOutputChar.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
syncDataOutputChar.setValue(0, BluetoothGattCharacteristic.FORMAT_SINT32, 0);
gatt.writeCharacteristic(syncDataOutputChar);
}
} catch ...
}
Unexpectedly, it yields a DeadObjectException and quits. This is weird and likely to be some clues leading to the problem. And I also think the Unhandled exception: java.lang.NullPointerException: src == nullin the logs above is worth digging too.
Related
I am writing a Xamarin.Android application, but this question is applicable to native Android and BLE in general. I have a write characteristic that I can write to, and it works as long as I don't send more than 600 characters. Anything over 600 characters gets truncated. Looking at my logs, I can see that the text is being split into 20 character packets, and OnCharacteristicWriteRequest is called for each packet, but stops being called after 600 characters. I am testing with 2 Android tablets. My code to write to the characteristic:
public override void OnServicesDiscovered(BluetoothGatt gatt, [GeneratedEnum] GattStatus status)
{
base.OnServicesDiscovered(gatt, status);
try
{
if (status != GattStatus.Success)
{
Log?.Invoke("discover services failed");
return;
}
Log?.Invoke("services discovered");
if(RequestForAddressExists(gatt.Device.Address))
{
lock (_requestsLocker)
{
Java.Util.UUID serviceUuid = GetRequestedServiceUuid(gatt.Device.Address);
Java.Util.UUID characteristicUuid = GetRequestedCharacteristicUuid(gatt.Device.Address);
BluetoothGattCharacteristic characteristic = gatt.GetService(serviceUuid).GetCharacteristic(characteristicUuid);
Log?.Invoke("characterisitic found");
var request = _requests.FirstOrDefault(r => r.DeviceAddress == gatt.Device.Address);
if (characteristic.Properties.HasFlag(GattProperty.Write))
{
Log?.Invoke("writing characteristic...");
string data = ((WriteCharacteristicRequest)request).Data;
characteristic.SetValue($"{data}{Constants.WriteCharacteristicEndDelimiter}");
characteristic.WriteType = GattWriteType.Default;
gatt.WriteCharacteristic(characteristic);
}
else
{
Log?.Invoke("GattProperty not supported");
_requests.Remove(request);
}
}
}
}
catch (Exception e)
{
Log?.Invoke(e.Message);
}
}
public override void OnCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, [GeneratedEnum] GattStatus status)
{
base.OnCharacteristicWrite(gatt, characteristic, status);
if (status != GattStatus.Success)
{
Log?.Invoke($"OnCharacteristicWrite status not success: {status}");
}
else
{
Log?.Invoke("OnCharacteristicWrite success");
}
gatt.Disconnect();
gatt.Close();
lock (_requestsLocker)
{
var r = _requests.FirstOrDefault(x => x.DeviceAddress == gatt.Device.Address);
if (r != null)
{
_requests.Remove(r);
}
}
}
My code to accept the write request:
public override void OnCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, bool preparedWrite, bool responseNeeded, int offset, byte[] value)
{
base.OnCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log?.Invoke("OnCharacteristicWriteRequest");
string data = System.Text.Encoding.UTF8.GetString(value);
Log?.Invoke(data);
string characteristicId = new Guid(characteristic.Uuid.ToString()).ToString().ToUpperInvariant();
var record = _writeCharacteristicsReceived.FirstOrDefault(c => c.DeviceAddress == device.Address && c.CharacteristicId.ToUpperInvariant() == characteristicId);
if(record != null)
{
record.Data += data;
}
else
{
record = new CharacteristicWriteReceived()
{
CharacteristicId = characteristicId,
DeviceAddress = device.Address,
Data = data
};
_writeCharacteristicsReceived.Add(record);
}
if (record.Data.EndsWith(Constants.WriteCharacteristicEndDelimiter) == true)
{
Log?.Invoke("end found");
_writeCharacteristicsReceived.Remove(record);
record.Data = record.Data.Substring(0, record.Data.Length - Constants.WriteCharacteristicEndDelimiter.Length); // remove the end delimeter
Log?.Invoke(record.Data);
OnCharacteristicWriteReceived?.Invoke(record);
}
if (responseNeeded)
{
BluetoothGattServer.SendResponse(device, requestId, GattStatus.Success, 0, value);
}
}
public override void OnExecuteWrite(BluetoothDevice device, int requestId, bool execute)
{
// need to override OnExecuteWrite and call SendResponse here as well,
// since the execute packet corresponds to the last ATT packet that the client sends as a "finish" marker,
// and the client expects a response to know that the server accepted the writes
base.OnExecuteWrite(device, requestId, execute);
BluetoothGattServer.SendResponse(device, requestId, GattStatus.Success, 0, new byte[0]);
}
The funny thing is, even when the text is truncated, I still get status == GattStatus.Success in my OnCharacteristicWrite. Why is it being truncated? Is there a maximum number of packets that can be sent?
Both devices are continuously advertising and scanning on BLE while writing to this characteristic...could that cause a problem?
A characteristic value can only be 512 bytes long per specification. Writing a longer value is not allowed, even if apparently some stacks don't enforce it.
When you write a value longer than what fits in the MTU (default 23 bytes minus 3 for header), the sender Bluetooth stack splits it up in multiple chunks (Prepared Write) and then sends an Execute request to commit. For each chunk you have the offset parameter so you know at which offset to write the current chunk.
I badly need this to proceed further in my application.
I'm very much familiar with Android BLE and using for years.
I have the below code to enable notification and it is working for years with my peripheral. onCharacteristicChanged() method is called with "OK_N1" when notification is enabled.
private void enableNotification(String serviceUUID, String characteristicUUID) {
if (bluetoothGatt == null) {
return;
}
BluetoothGattService service = bluetoothGatt.getService(UUID.fromString(serviceUUID));
if (service == null) {
return;
}
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicUUID));
bluetoothGatt.setCharacteristicNotification(characteristic, true);
enableDescriptor(characteristic);
}
private void enableDescriptor(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
if (bluetoothGatt == null) {
return;
}
BluetoothGattDescriptor descriptor = bluetoothGattCharacteristic.getDescriptor(
UUID.fromString(PodsServiceCharacteristics.CLIENT_CHARACTERISTIC_CONFIG));
if (descriptor == null)
return;
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
Now, I am using Polidea RxAndroidble(ver 1.7.0) with RxJava2 to make things easy.
I have the following code with Polidea's RxAndroidBle which is not working.
public void enableNotifications(#NotNull String[] characteristics) {
if (isConnected()) {
mNotificationSubscriber = mRxBleConnection.setupNotification(UUID.fromString(characteristics[0]))
.doOnNext(notificationObservable -> notificationHasBeenSetUp())
.flatMap(notificationObservable -> notificationObservable)
.subscribe(this::onNotificationReceived, this::onNotificationSetupFailure);
}
}
private void onNotificationReceived(byte[] bytes) {
Log.i(TAG, "onNotificationReceived");
}
private void onNotificationSetupFailure(Throwable throwable) {
Log.i(TAG, "onNotificationSetupFailure" + throwable.getMessage());
}
private void notificationHasBeenSetUp() {
Log.i(TAG, "notificationHasBeenSetUp");
}
notificationHasBeenSetUp() is called but onNotificationReceived() is not called, where I get "OK_N1" bytes
It is probably because your peripheral sends a notification right after the Client Characteristic Configuration Descriptor is set.
By default RxAndroidBle sets up notifications fully before emitting the Observable<byte[]> i.e. the CCC descriptor has to be successfully written before the emission.
Since library version 1.8.0 it is possible to use NotificationSetupMode.QUICK_SETUP which emits Observable<byte[]> as soon as possible — before the CCC descriptor is written allowing to capture such early notifications/indications.
mRxBleConnection.setupNotification(bluetoothGattCharacteristic, NotificationSetupMode.QUICK_SETUP)
Pre 1.8.0 version answer
To not miss any "early" emissions one may leverage NotificationSetupMode.COMPAT in which the library does not handle writing the CCC descriptor*.
(...)
mNotificationSubscriber = mRxBleConnection.discoverServices()
.flatMap(rxBleDeviceServices -> rxBleDeviceServices.getCharacteristic(characteristicUuid))
.flatMapObservable(bluetoothGattCharacteristic -> {
BluetoothGattDescriptor cccDescriptor = bluetoothGattCharacteristic.getDescriptor(PodsServiceCharacteristics.CLIENT_CHARACTERISTIC_CONFIG);
Completable enableNotificationCompletable = mRxBleConnection.writeDescriptor(cccDescriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
Completable disableNotificationCompletable = mRxBleConnection.writeDescriptor(cccDescriptor, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE).onErrorComplete();
return mRxBleConnection.setupNotification(bluetoothGattCharacteristic, NotificationSetupMode.COMPAT)
.doOnNext(notificationObservable -> notificationHasBeenSetUp())
.flatMap(notificationObservable -> notificationObservable)
.mergeWith(enableNotificationCompletable)
.doOnDispose(disableNotificationCompletable::subscribe) // fire and forget
.share(); // this can be omitted but I put it here in case the resulting `Observable<byte[]>` would be shared among multiple subscribers
})
.subscribe(this::onNotificationReceived, this::onNotificationSetupFailure);
(...)
I think since this is apparently quite common situation it will be handled by the library at some point.
* NotificationSetupMode.COMPAT was introduced mainly for BLE peripherals that do not follow BLE specification and do not have CCC descriptor yet will send notifications all the time
First off, let me say that I'm just starting my Android adventure and am learning on the code posted below.
So i have a Zebra barcode scanner and an Android device, which is supposed to handle the scanned barcodes. The two devices communicate with each other via BT connection (I got it working). Scanned barcodes are being handled by JsonObjectRequest (also working). Depending on the response (or lack of) from external service, scanner has to react in a certain way:
green/red LED on - beeper - green/red LED off
And here is where I am struggling:
If I have only beeper - everything works. If I have a LED on/off - only LED on works. If I have all 3 actions - none gets executed.
Now, strange thing is, that debugger shows those actions received and executed
D/MainActivity: Barcode Received
I/ViewRootImpl: CPU Rendering VSync enable = false
I/BluetoothScanner: executeCommand started. opcode = DCSSDK_SET_ACTION inXML = <inArgs><scannerID>5</scannerID><cmdArgs><arg-int>45</arg-int></cmdArgs></inArgs>
I/BluetoothScanner: 7 SSI bytes sent: 0x05 0xE7 0x04 0x00 0x04 0xFF 0x0C
I/BluetoothScanner: executeCommand returningDCSSDK_RESULT_SUCCESS
I/ViewRootImpl: CPU Rendering VSync enable = false
I/BluetoothScanner: executeCommand started. opcode = DCSSDK_SET_ACTION inXML = <inArgs><scannerID>5</scannerID><cmdArgs><arg-int>17</arg-int></cmdArgs></inArgs>
I/BluetoothScanner: 7 SSI bytes sent: 0x05 0xE6 0x04 0x00 0x11 0xFF 0x00
I/BluetoothScanner: soundBeeper command write successful. Wait for Status.
executeCommand returningDCSSDK_RESULT_SUCCESS
I/ViewRootImpl: CPU Rendering VSync enable = false
I/BluetoothScanner: executeCommand started. opcode = DCSSDK_SET_ACTION inXML = <inArgs><scannerID>5</scannerID><cmdArgs><arg-int>46</arg-int></cmdArgs></inArgs>
I/BluetoothScanner: 7 SSI bytes sent: 0x05 0xE8 0x04 0x00 0x04 0xFF 0x0B
executeCommand returningDCSSDK_RESULT_SUCCESS
Code, that I am using to construct those requests is based on an example app and documentation provided by Zebra see here the Zebra Android SDK and this is how I am calling those actions:
private class MyAsyncTask extends AsyncTask<String,Integer,Boolean> {
int scannerId;
StringBuilder outXML;
DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode;
private CustomProgressDialog progressDialog;
public MyAsyncTask(int scannerId, DCSSDKDefs.DCSSDK_COMMAND_OPCODE opcode){
this.scannerId=scannerId;
this.opcode=opcode;
this.outXML = outXML;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new CustomProgressDialog(MainActivity.this, "Execute Command...");
progressDialog.show();
}
#Override
protected Boolean doInBackground(String... strings) {
return executeCommand(opcode,strings[0],null,scannerId);
}
#Override
protected void onPostExecute(Boolean b) {
super.onPostExecute(b);
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
if(!b){
Toast.makeText(MainActivity.this, "Cannot perform the Action", Toast.LENGTH_SHORT).show();
}
}
}
#Override
public boolean executeCommand(DCSSDKDefs.DCSSDK_COMMAND_OPCODE opCode, String inXML, StringBuilder outXML, int scannerID) {
if (Application.sdkHandler != null)
{
if(outXML == null){
outXML = new StringBuilder();
}
DCSSDKDefs.DCSSDK_RESULT result=Application.sdkHandler.dcssdkExecuteCommandOpCodeInXMLForScanner(opCode,inXML,outXML,scannerID);
if(result== DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_SUCCESS)
return true;
else if(result==DCSSDKDefs.DCSSDK_RESULT.DCSSDK_RESULT_FAILURE)
return false;
}
return false;
}
private final Handler dataHandler = new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
switch(msg.what){
case Constants.BARCODE_RECEIVED:
Barcode barcode = (Barcode) msg.obj;
sendApiHttpRequest(new String(barcode.getBarcodeData()));
break;
}
return false;
}
});
private void sendApiHttpRequest(String ticketId){
String url = "https://#################################/" + ticketId;
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url, null, myJsonListener(), myJsonErrorListener());
// tag the request for ease of debugging
jsonObjectRequest.setTag(TAG);
// Access the RequestQueue through your singleton class.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest);
}
private Response.Listener<JSONObject> myJsonListener() {
return new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
boolean status;
try {
status = response.getBoolean("status");
if (status){
setScanResultOK();
}else{
setScanResultERR();
}
}catch(JSONException e){
setScanResultERR();
Log.e(TAG, "Failure", e);
}
}
};
}
private Response.ErrorListener myJsonErrorListener() {
return new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
setScanResultERR();
Log.i(TAG, "Error : " + error.getLocalizedMessage());
}
};
}
private void setScanResultOK(){
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_ON);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_FAST_WARBLE_BEEP);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_OFF);
TextView textViewScanResult = findViewById(R.id.txt_scan_result);
textViewScanResult.setText(R.string.scan_res_ok);
textViewScanResult.setTextAppearance(getApplicationContext(), R.style.roboto_medium_96dp_green);
}
private void setScanResultERR(){
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_RED_ON);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LOW_LONG_BEEP_3);
prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_RED_OFF);
TextView textViewScanResult = findViewById(R.id.txt_scan_result);
textViewScanResult.setText(R.string.scan_res_err);
textViewScanResult.setTextAppearance(getApplicationContext(), R.style.roboto_medium_96dp_red);
}
private void performOpcodeAction(String inXML) {
if (scannerID != -1) {
new MyAsyncTask(scannerID, DCSSDKDefs.DCSSDK_COMMAND_OPCODE.DCSSDK_SET_ACTION).execute(new String[]{inXML});
} else {
Toast.makeText(this, "Invalid scanner ID", Toast.LENGTH_SHORT).show();
}
}
private void prepareInXML(int value){
String inXML = "<inArgs><scannerID>" + scannerID + "</scannerID><cmdArgs><arg-int>" +
value + "</arg-int></cmdArgs></inArgs>";
performOpcodeAction(inXML);
}
When I set up breakpoints and step through the code, all actions are executed and as soon as I run the app, I get those issues.
Can anyone please help me?
Here's what I understood from your code. Your are sending an HTTP request to a server and based on the response, your are going to perform a sequence of events such as LED and sound state toggling.
As a background, Asynctask is used to execute a piece of code on a background thread. In your case, you want to perform the commands. As from the name, they are asynchronous and will run in parallel with your main thread (at least the doInBackground).
In setScanResultOK and setScanResultERR, you are potentially instantiating 3 asynctasks. Only one of them will run as by default, asynctasks run on a single execution thread. If you want to run them altogether, execute them in a thread pool executor.
Now, you mentioned that you want to run them in sequence. I propose to refactor your code as such.
Create 2 asynctasks, 1 for success and 1 for error.
Perform the multiple prepareInXML calls in doInBackground
Instantiate an asynctask based on the response, and execute.
As an example:
#Override
protected Boolean doInBackground(String... strings) {
if (!prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_ON)) {
return false;
}
if (!prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_FAST_WARBLE_BEEP)) {
return false;
}
return prepareInXML(RMDAttributes.RMD_ATTR_VALUE_ACTION_LED_GREEN_OFF);
}
Of course this will require you to change some function signature to accommodate. Then process all UI changes in onPostExecute.
#Override
protected void onPostExecute(Boolean b) {
super.onPostExecute(b);
if (progressDialog != null && progressDialog.isShowing())
progressDialog.dismiss();
if(!b){
Toast.makeText(MainActivity.this, "Cannot perform the Action", Toast.LENGTH_SHORT).show();
}
/* perform UI changes particular to the type of task (success/fail) */
}
UPDATE:
Try adding a delay in between commands. The no op might be something that is actually a on-sound-off sequence happening real fast for us to notice.
I am trying to write text data to my BLE device. So , i am following Android Bluetooth GATT classes to do the task. But i found writing the text to the Characteristics is fine but while trying to retrieve the Characteristics value , it returns null.
MyCode :
public void writeCharacteristic(BluetoothGattCharacteristic characteristic,
String text) {
String TAGS ="MyBeacon";
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAGS, "BluetoothAdapter not initialized");
return;
} else {
Log.w(TAGS, "Writting ... ");
}
byte[] data = hexStringToByteArray(text);
Log.w(TAGS, "Writting text = " + data);
try {
characteristic.setValue(URLEncoder.encode(text, "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
boolean writeValue = mBluetoothGatt.writeCharacteristic(characteristic);
Log.w(TAGS, "Writting Status = " + writeValue);
}
// Successfully onCharacteristicWrite also gets called //
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
String TAGS ="MyBeacon";
String text = null;
try {
text = new String(characteristic.getValue(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Log.w(TAGS, "onCharacteristicWrite = " + text+" :: "+status);
}
but while trying to read the Characteristics it returns null.
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
final byte[] data = gattCharacteristic.getValue(); // returns null
if (data != null && data.length > 0) {
Log.d("MyBeacon", " Read Data ")
} else {
Log.d("MyBeacon", " Data is null")
}
}
MyBeacon
Also check the issue in other thread too.
Please help me out , suggest me some solution to write and read data successfully to my Beacon.
Syntax should be as follows,
mBluetoothGatt.readCharacteristic(characteristic);
Reading characteristics:
You can read the characteristic using mBluetoothGatt.readCharacteristic(characteristic);
You can have to read the characteristic's descriptor as follows,
mBluetoothGatt.readDescriptor(ccc);
Once you read it, it should return data by calling the onDescriptorRead callback.
Here you can set up (subscribe) to the charactersitic through either notification or indication by calling:
mBluetoothGatt.setCharacteristicNotification(characteristic, true)
once it returns true you will need to write to the descriptor again (the value of notification or indication)
BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(CCC);
clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//clientConfig.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mBluetoothGatt.writeDescriptor(clientConfig);
Once this is done you will get notifications through onCharacteristicChanged callback every time the characteristic changes.
Do update me , if you have any problems while implementing,
I faced a similar issue where the characteristic.getValue returns Null. I was following exactly what is mentioned in the BLE Gatt documentation, and other blogs, but still the issue persisted until finally I understood what I was doing wrong.
At client device end, we setValue into the characteristic that we are interested in using
gatt.WriteCharacteristic(characteristic.setValue("Hello"));
At Server end, the request is received onto the onCharacteristicWriteRequest(....) callback.
Generally we expect the value that we set at client end to be carried by the characteristic parameter but we observe the characteristic.getValue() is null.
Where in the same callback we also have another parameter by name "Value" which actually carries the characteristic value we set at Client end. Please refer this parameter and this should solve the problem.
Did you read it too early? It should be read after onCharacteristicWrite() has been called.
Android BluetoothGatt.class has mClientIf private field. Most of the log messages related to BLE events contain this value. For example:
onClientRegistered() - status=0 clientIf=17
What does the mClientIf field represent? What does the integer value of this field tell?
mClientf is a scannerId from Bluetooth scanner,
If you dig through the source of BluetoothGatt and BluetoothLeScanner you can find the following:
mBluetoothGatt.unregisterClient(scannerId); method is implemented in
GattService.java unregisterClient(int clientIf)
BluetoothLeScanner.java
...
/**
* Application interface registered - app is ready to go
*/
#Override
public void onScannerRegistered(int status, int scannerId) {
Log.d(TAG, "onScannerRegistered() - status=" + status +
" scannerId=" + scannerId + " mScannerId=" + mScannerId);
synchronized (this) {
if (status == BluetoothGatt.GATT_SUCCESS) {
try {
if (mScannerId == -1) {
// Registration succeeds after timeout, unregister client.
mBluetoothGatt.unregisterClient(scannerId);
} else {
mScannerId = scannerId;
mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
mResultStorages,
ActivityThread.currentOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "fail to start le scan: " + e);
mScannerId = -1;
}
} else {
// registration failed
mScannerId = -1;
}
notifyAll();
}
}
...
GattService.java
...
/**
* Unregister the current application and callbacks.
*/
private IBluetoothGatt mService;
.
.
public void unregisterClient(int clientIf) {
GattService service = getService();
if (service == null) return;
service.unregisterClient(clientIf);
}
...
It's hard to say with absolute certainty, but looking at how it's used in the rest of the class, I would say it's a unique ID assigned by the layer underneath called IBluetoothGatt.