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
Related
I'm using RxAndroidBle 2 to watch a characteristic for CSC but I cannot read some characteristics before. It's also the first time I'm using ReactiveX so I have to deal with flatMap, flatMapSingle, etc.
Here's my code to register a handler for watching CSC Measure:
connectionSubscription = device.establishConnection(false)
// Register for notifications on CSC Measure
.flatMap(rxBleConnection -> rxBleConnection.setupNotification(Constants.CSC_MEASURE))
.doOnNext(notificationObservable -> {
// Notification has been set up
Log.d(TAG, "doOnNext = " + notificationObservable.toString());
})
.flatMap(notificationObservable -> {
Log.d(TAG, "flatMap = " + notificationObservable);
return notificationObservable;
}) // <-- Notification has been set up, now observe value changes.
.subscribe(
this::onCharacteristicChanged,
throwable -> {
if (throwable instanceof BleDisconnectedException) {
Log.e(TAG, "getCanonicalName = " + throwable.getClass().getCanonicalName());
}
}
);
And then the code for extracting the value:
private void onCharacteristicChanged(byte[] bytes) {
Integer f = ValueInterpreter.getIntValue(bytes, ValueInterpreter.FORMAT_UINT8, 0);
Log.d(TAG, "flags " + f);
switch (f) {
case 0: // 0x00000000 is not authorized
Log.w(TAG, "flags cannot be properly detected for this CSC device");
break;
case 1:
// sensor is in speed mode (mounted on the rear wheel)
// Cumulative Wheel Revolutions
Integer sumWheelRevs = ValueInterpreter.getIntValue(bytes, ValueInterpreter.FORMAT_UINT32, 1);
// Last Wheel event time
Integer lastWheelEvent = ValueInterpreter.getIntValue(bytes, ValueInterpreter.FORMAT_UINT16, 5);
Log.d(TAG, "Last wheel event detected at " + lastWheelEvent + ", wheel revs = " + sumWheelRevs);
break;
case 2:
// sensor is in cadence mode (mounted on the crank)
// Last Crank Event Time
// Cumulative Crank Revolutions
break;
}
}
I have another piece of working code to read ONE characteristic, but how can I process a list of characteristics?
.flatMapSingle(rxBleConnection -> rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE))
.subscribe(
characteristicValue -> {
String deviceHardware = ValueInterpreter.getStringValue(characteristicValue, 0);
Log.d(TAG, "characteristicValue deviceHardware = " + deviceHardware);
},
throwable -> {
// Handle an error here.
}
)
Constants are defined like this:
public static final UUID CSC_MEASURE = UUID.fromString("00002a5b-0000-1000-8000-00805f9b34fb");
I tried to integrate the answer provided in here but the code doesn't compile anymore. Moreover, the code should combine up to 12 characteristics (simple map of UUID to Int/String/Boolean). I used to have a working code by creating a subclass of BluetoothGattCallback but my code was getting more and more difficult to maintain with standard Android Bluetooth classes.
I tried to integrate the answer provided in here but the code doesn't compile anymore.
I have updated the post you mentioned to match RxAndroidBle based on RxJava2. It should compile now.
I have another piece of working code to read ONE characteristic, but how can I process a list of characteristics?
...
Moreover, the code should combine up to 12 characteristics (simple map of UUID to Int/String/Boolean).
The mentioned post does solve a case where there are 4 characteristics. In case of 12 (or a variable number) there is a Single#zipArray function.
Single.zipArray(
/* Object[] */ results -> YourWrapperObject(results),
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE),
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE1),
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE2),
// ...
rxBleConnection.readCharacteristic(Constants.DEVICE_HARDWARE11)
)
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'm using RXJava2 in my Android app, and I have a somewhat peculiar scenario.
I want to perform unknown amount of jobs (determined by the user), but I want them to start only after a certain value has changed.
My specific requirement is to use a Socket for server communication,
and the flow is the following:
User requests some data - data is retrieved by sending data to the socket and wait for the response.
The module that communicates with the server should open a Socket connection, and only after the connection established, it may fetch the requested data.
While Socket attempt to connect, the user may request some more data.
After the connection established successfully the module should perform all the requests sent by the user while connection process was in progress.
The module also should publish the results that came for each data sent to the socket.
How can this be accomplished using RXJava2?
You could use an UnicastSubject for the queue part and do some flatMap-ping once the connection is established:
UnicastSubject<String> userRequests = new UnicastSubject.create();
Single.fromCallable(() -> new Socket("server", port))
.subscribeOn(Schedulers.io())
.flatMapObservable(socket -> {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] responseBuffer = new byte[4096];
return userRequests
.observeOn(Schedulers.io())
.map(request -> {
out.write(request.getBytes());
int n = in.read(responseBuffer);
if (n >= 0) {
return new String(responseBuffer, 0, n);
}
throw new IOException("Socket closed while waiting for response");
})
.doFinally(() -> socket.close());
});
Since you are working on the Socket level, it is your responsibility to work out the proper encoding of the requests to be written and the proper decoding of the response to be read (i.e., how long (in bytes) the response is to a particular request).
I believe you would need FlowableTransformers.valve() for this, from RxJava2Extensions.
It should work something like this
PublishSubject<String> jobs = PublishSubject.create().toSerialized();
BehaviorSubject<Boolean> isConnected = BehaviorSubject.createDefault(false);
CompositeDisposable disposables = new CompositeDisposable();
public void connect() {
disposables.add(socketService.subscribe((success) -> {
isConnected = true;
}));
}
public void addJob(String job) {
jobs.onNext(job);
}
public void executeQueuedTasks() {
disposables.add(jobs
.toFlowable(BackpressureStrategy.BUFFER)
.compose(FlowableTransformers.valve(isConnected))
.subscribeWith(new DisposableObserver<>() {
...
})
);
}
public void destroy() {
disposables.clear();
}
}
But the UnicastSubject sample above is more likely to work, I wrote this off the top of my head.
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.
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.