I'm analyzing a sample from android, which is explaining a bluetooth low energy usage on android. I've found the following code, which is setting a notification, but I can't get what is happening here with using a properties integer and conditions inside ifs. Could someone explain it a little bit?
Anyway, maybe you have some better source, which can explain a ble concept on android- what and how is working here? The official android tutorial is really poor, and bluetooth official page is giving almost nothing...
#Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
if (mGattCharacteristics != null) {
final BluetoothGattCharacteristic characteristic =
mGattCharacteristics.get(groupPosition).get(childPosition);
final int charaProp = characteristic.getProperties();
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
// If there is an active notification on a characteristic, clear
// it first so it doesn't update the data field on the user interface.
if (mNotifyCharacteristic != null) {
mBluetoothLeService.setCharacteristicNotification(
mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
mBluetoothLeService.readCharacteristic(characteristic);
}
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = characteristic;
mBluetoothLeService.setCharacteristicNotification(
characteristic, true);
}
return true;
}
return false;
}
You could try reading the Bluetooth specification (https://www.bluetooth.com/specifications/adopted-specifications, Core Version 5.0), vol 3 part G. Just note that Android abstracts away the attribute handles.
What most people do however is writing an app designed for a particular hardware, i.e. the gatt db is assumed to be known.
In order to get objects that represents the services, characteristics and descriptors, call gatt.discoverServices(). The result is returned asynchronously using the onServicesDiscovered() callback. This should be done each time a device gets connected (in the onConnectionStateChange callback when newState is GATT_CONNECTED).
To write to a characteristic, first set the value on the BluetoothGattCharacteristic object using the setValue method, then call gatt.writeCharacteristic(characteristic). When the operation is complete, onCharacteristicWrite will be called.
A read operation works in similar way; call gatt.readCharacteristic(characteristic) and the result is ready when the onCharacteristicRead is called. Use getValue() on the characteristic object to get the value.
For notifications to work you must first write BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE (0x0001) to the client characteristic configuration descriptor so that the peripheral will start deliver notifications. You must also tell the Android Bluetooth stack to forward the notifications to your app by calling setCharacteristicNotification.
Note that you can only have one outstanding operation at a time (read/write), meaning you must wait for the callback before you issue a new request.
If you know the hardware you're dealing with you normally don't need to inspect the characteristic properties (characteristic.getProperties()). The bitmask is described in Bluetooth Core V 5.0 specification, Vol 3, Part G, section 3.3.1.1 and describes what features are enabled for each characteristic (for example read, write, notifications).
How to deal with conversion between 16-bit and 128-bit UUIDs is shown in the Bluetooth Core V 5.0 specification, Vol 3, Part B, section 2.5.1. Note that Android's client libraries only use 128-bit UUIDs.
Related
I am sending data to BLE Device as 20 bytes chunks.
I am receiving back large response.
But onCharacteristicRead call back, I get only the last piece of the data.
byte[] messageBytes = characteristic.getValue();
if (messageBytes != null && messageBytes.length > 0) {
for(byte byteChar : messageBytes) {
stringBuilder.append((char)byteChar);
}
}
Can anyone help understand where I am going wrong?
Should I read the data back also as chunks?
If so, how?
Characteristic's value updates everytime you write to it, that's why when you read, it only reflects the latest value (the last one you write).
To read the data continuously, you should enable the notification on the characteristics first.
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID_DESCRIPTOR);
descriptor.setValue(enabled?BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
:BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
Then you can start writing data
byte[] data = <Your data here>;
BluetoothGattService Service = mBluetoothGatt.getService(UUID_TARGET_SERVICE);
BluetoothGattCharacteristic charac = Service
.getCharacteristic(UUID_TARGET_CHARACTERISTIC);
charac.setValue(data);
mBluetoothGatt.writeCharacteristic(charac);
Now everytime you write, the client side will receive a callback of onCharactersticChanged that contains the newly updated value (data). You don't need to call the read operation actually.
Remember mBluetoothGatt can only handle 1 operation at a time, if you execute another one while the previous one is unfinished, it won't put in queue, but will return false.
The Android BLE API seems odd, maybe I'm missing something. What I need to do, is to make a connection to a BLE device, then if things are idle for a while disconnect temporarily, but when the user wants to do something new I want to reconnect.
To connect initially, I call:
Gatt1 = Device.ConnectGatt (Android.App.Application.Context, false, GattCallback);
Then I'm thinking to do my temporary disconnect I call
Gatt1.Disconnect();
And then when I want to re-connect, I call ConnectGatt() again, which gives me a new BluetoothGatt object:
Gatt2 = Device.ConnectGatt (Android.App.Application.Context, false, GattCallback);
So once I've called Gatt1.Disconnect(), I should just throw away Gatt1? It's not useful anymore, since when I re-connect I get a new BluetoothGatt object? Do I need to call some function to tell the API that I'm not using Gatt1 anymore?
(no, I wouldn't actually have two variables, Gatt1 and Gatt2, I'm just using those names to indicate there are two different objects happening)
When I eventually decided I'm completely done with this BLE device, I'm not planning on ever re-connecting, then I need to call Gatt.Close() (right?)
So maybe the code looks more like this?
BluetoothDevice Device = stuff();
BluetoothGatt Gatt = null;
if (connecting)
Gatt = Device.ConnectGatt(...);
else if (disconnecting temporarily)
Gatt.Disconnect();
else if (reconnecting after a temporary disconnection)
{
Gatt = null; // Yes? Do I need to specifically Dispose() this previous object?
Gatt = Device.ConnectGatt(...);
}
else if (disconnecting permanently)
{
Gatt.Close();
Gatt = null;
}
(again, no, I wouldn't write such a function, it's just to illustrate the lifespan of the various BluetoothGatt objects)
You need to also dispose the first BluetoothGatt object (Gatt1) when you are done with it, by calling the close() method on it. Just leaving the garbage collection to clean it up will not work I guess since it has no finalizer which calls the internal Bluetooth stack to clean it. If you don't close the object and just drop the reference, you will eventually run out of BluetoothGatt objects (there can be max 32 totally on the device for all apps together).
Gatt1 = Device.ConnectGatt (Android.App.Application.Context, false, GattCallback);
should be followed by:
Gatt1.connect();
Gatt1.disconnect() is correct for your purposes. When reconnecting, Gatt1 = null is unnecessary. Just call device.connectGatt() and Gatt1.connect() again. When you're completely done:
if(Gatt1!=null) {
Gatt1.disconnect();
Gatt1.close();
}
After reading these suggestions, and doing some more research, I think the answer is this:
BluetoothDevice Device = stuff();
BluetoothGatt Gatt = null;
if (connecting)
Gatt = Device.ConnectGatt(...);
else if (disconnecting temporarily)
Gatt.Disconnect();
else if (reconnecting after a temporary disconnection)
Gatt.Connect();
else if (disconnecting permanently)
{
Gatt.Disconnect();
Gatt.Close();
Gatt = null;
}
with a bunch of additional code to wait for the connect/disconnect actions to finish.
I am working with BLE device.I perform the code using android GATT and it scan device and also I send immediate alert in BLE device from mobile using writeCharacteristic and BLE device is beep.But now I want to Alert in android Mobile when I press BLE device button. That exactly I don't know how to do.
Thank You.
For this you have to listen for characteristics change. When you will press the BLE button, a characteristics must be change inside its hardware (set any flag value depends on hardware's firmware coding). When characteristics changed, characteristics method will called. You can perform required functionality there.
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic
characteristic)
{
Here we received ble button pressed event callback. Do stuff here.
}
For receiving characteristics change callback first you have to enable the notification like this.
BluetoothGattCharacteristic characteristic = gatt.getService(mServiceUuuid).getCharacteristic(mCharOneUuuid);
gatt.setCharacteristicNotification(characteristic, true);
List<BluetoothGattDescriptor> list = characteristic.getDescriptors();
for (int i = 0; i < list.size(); i++) {
BluetoothGattDescriptor desc = list.get(i);
desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(desc);
}
In my application, I got the READ and WRITE to work on specific BluetoothGattCharacteristic objects.
The BluetoothGattCallback onCharacteristicWrite and onCharacteristicRead are properly called.
I have then tried to setup the NOTIFY option so my Android app gets notified when a specific characteristic on the device changes.
I have set this up via the following code:
// Local notifications
mGatt.setCharacteristicNotification(statusTypeCharacteristic, notify);
// Remote notifications
BluetoothGattDescriptor desc = statusTypeCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
Log.d("Descriptor", desc.toString());
boolean test;
test = desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); // return value = true
test = mGatt.writeDescriptor(desc); // return value = true
When the characteristic changes, the callback: onCharacteristicChanged is being called as expected
However, now all READ and WRITE operations do not work anymore.
When I comment the lines dealing with the descriptor, the READ and WRITE work again.
A part I am very unclear is around the UUID used to get the descriptor. Is it correct? Should I scan instead all descriptors from the characteristic and enable notification on one? How do I know which one to set as I have multiple ones coming back?
Ok, so I have figured out the issue.
At the beginning of my application, I am configuring (i.e. writing) to lots of descriptors.
2 issues with it:
1- A descriptor can only be written one at a time
2- No read/write operations can happen when a descriptor is being written to
The fix is to create a queue of write descriptor operations and perform the next descriptor write in the onDescriptorWrite callback.
private void writeGattDescriptor(BluetoothGattDescriptor d) {
//put the descriptor into the write queue
descriptorWriteQueue.add(d);
//if there is only 1 item in the queue, then write it. If more than 1, we handle asynchronously in the
// callback
if(descriptorWriteQueue.size() == 1) {
mGatt.writeDescriptor(d);
}
}
And then in the callback:
#Override
public void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
// Called when the descriptor is updated for notification
Log.d("onDescriptorWrite", descriptor.getUuid().toString());
// pop the item that we just finishing writing
descriptorWriteQueue.remove();
// if there is more to write, do it!
if(descriptorWriteQueue.size() > 0) {
mGatt.writeDescriptor(descriptorWriteQueue.element());
}
// Inform the framework that the scope has connected, configured and ready to process commands
if(descriptorWriteQueue.size() == 0) {
// Do something else, such as reads and writes
}
}
In Android's BLE API (BluetoothGatt) there are methods that deal with reliable writes:
public boolean beginReliableWrite ()
public void abortReliableWrite (BluetoothDevice mDevice)
public boolean executeReliableWrite ()
There is also a Callback for it (in BluetoothGattCallback):
public void onReliableWriteCompleted (BluetoothGatt gatt, int status)
I can't find any documentation on that. What is it? How is it different from "normal" (unreliable?) writes?
Reliable write allows checking back transmitted values and atomic execution of one or mutliple transmitted messages.
A good explaination of the reliable write procedure can be found in the BLE part of Mozillas Boot 2 Gecko Project documentation. Even though it's meant for JavaScript the description of beginReliableWrite() in particular is very helpful for understanding the process:
Once a reliable write transaction has been initiated, all calls to
characteristic.writeValue() are sent to the remote device for
verification and queued up for atomic execution. An Promise that
carries the written value is returned in response to every
characteristic.writeValue() call and the application is responsible
for verifying whether the value has been transmitted accurately. After
all characteristics have been queued up and verified,
executeReliableWrite() will execute all writes. If a characteristic
was not written correctly, calling abortReliableWrite() will cancel
the current transaction without committing any values on the remote LE
device.
You begin the reliable write,
gatt.beginReliableWrite();
set the value of the characteristic and write it.
characteristic.setValue(value);
gatt.writeCharacteristic(characteristic);
The writeCharacteristic() call will trigger its 'normal' callback. The parameter characteristic contains the actual, written value which can be verified:
#Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
...
if(characteristic.getValue() != value) {
gatt.abortReliableWrite();
} else {
gatt.executeReliableWrite();
}
...
}
Executing the reliable write will trigger the onReliableWriteCompleted(BluetoothGatt gatt, int status) callback.