Previously in this series we have discovered a BLE sensor and connected to it. All that remains is to actually get some data from it but that isn’t quite as straightforward as it may seem, at first. In this article we’ll look at GATT characteristics and how they facilitate data exchange between the host and the sensor.



In the previous article we looked at how the sensor device contains a GATT server, and we have established a connection to this. The GATT server contains one or more GATT services which represent different types of data which can be exchanged. For example, on the SensorTag there are different GATT services representing each of the different sensor components within the SensorTag (the humidity sensor, barometric pressure sensor, etc.).

Each GATT service contains or or more GATT characteristics. A GATT Characteristic is an atomic piece of data which can be transferred via BLE. A GATT characteristic contains arbitrary data, with a type identifier which indicates the type of the arbitrary data. It can also contain zero or more GATT descriptors.

A GATT descriptor contains meta data about the characteristic such as the units of the value of the characteristic, or whether we require notification when the value of the characteristic changes.

In short, a GATT server may contain one or more GATT services; each GATT service may contain one or more GATT characteristics; and each GATT characteristic may contain zero or more GATT descriptors.

One common aspect to GATT services, characteristics, and descriptors is that they are all identified using a Universally Unique IDentifier (UUID). A UUID is, as it’s name implies, is simple a unique identifier which is used to retrieve GATT services, characteristics, and descriptors.

For the SensorTag these UUIDs can be found in the GATT Server reference document. This can be a little confusing at first glance, but it is actually quite straightforward. All of the SensorTag UUIDs have the base: F0000000-0451-4000-B000-00000000. The four bold digits are the only ones which will change for different GATT services, characteristics, or descriptors. If we look down to the Humidity sensor we can see that its UUID is listed as AA20 (in the yellow row with the type GATT_PRIMARY_SERVICE_UUID). The full UUID for this service is F000AA20-0451-4000-B000-00000000.

The service has two characteristics:

The first is represents the actual sensor data and has the UUID AA21, which translates to F000AA21-0451-4000-B000-00000000. The data is 4 bytes which represent the temperature and humidity values: TempLSB:TempMSB:HumidityLSB:HumidityMSB – we’ll look at how to decode this later on. This characteristic also has a descriptor (the GATT_CLIENT_CHAR_CFG_UUID entry) which does not appear to have a UUID defined in the document. This is because this is a standard UUID which is in common use because it serves a common function, that is to register that we require notification when this value changes. Once again, we’ll cover this in a little more depth later on.

The second characteristic is a flag that we need to set to turn this specific sensor on, and has the UUID AA22, which translates to F000AA22-0451-4000-B000-00000000. Yet again, we’ll look at how we need to actually use this in due course.

It is worth a small digression at this point to explain a little more about UUIDs and how, in some cases, they may not be device specific as they are on the SensorTag. In many sensors there may be services with a common UUID and structure supported by different manufacturers who all adhere to a standard GATT service standard for sensors which perform the same function (for example there are standard GATT service definitions for heart rate monitors, etc.). Also, it is possible to include GATT service UUIDS in the device discovery phase to only match devices which offer specific services. Unfortunately the SensorTag does not support this, so I have been unable to cover it during this tutorial. For those interested, this is done by calling a different form of BluetoothAdapter#onStartLeScan() .

So, now that we understand how the service is organised, we can just go ahead and start reading the data, can’t we? Unfortunately it is not quite that simple. Remember that the GATT server connection consists of two parts: the GATT server on the sensor, and a local proxy? While we might know about the service, the local proxy does not, so we need to instruct it to retrieve the list of services from the sensor. This is done using the discoverServices() method:

BleService.java private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); Log.v(TAG, "Connection State Changed: " + (newState == BluetoothProfile.STATE_CONNECTED ? "Connected" : "Disconnected")); if (newState == BluetoothProfile.STATE_CONNECTED) { setState(State.CONNECTED); gatt.discoverServices(); } else { setState(State.IDLE); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if(status == BluetoothGatt.GATT_SUCCESS) { Log.v(TAG, "onServicesDiscovered: " + status); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private BluetoothGattCallback mGattCallback = new BluetoothGattCallback ( ) { @ Override public void onConnectionStateChange ( BluetoothGatt gatt , int status , int newState ) { super . onConnectionStateChange ( gatt , status , newState ) ; Log . v ( TAG , "Connection State Changed: " + ( newState == BluetoothProfile . STATE_CONNECTED ? "Connected" : "Disconnected" ) ) ; if ( newState == BluetoothProfile . STATE_CONNECTED ) { setState ( State . CONNECTED ) ; gatt . discoverServices ( ) ; } else { setState ( State . IDLE ) ; } } @ Override public void onServicesDiscovered ( BluetoothGatt gatt , int status ) { if ( status == BluetoothGatt . GATT_SUCCESS ) { Log . v ( TAG , "onServicesDiscovered: " + status ) ; } } }

Once again, this is an asynchronous method, so we can call it from the UI thread without fear, and we need to define an appropriate callback method which will be called once the service discovery is complete. It is important to check the status value in the callback method as sometimes it will be called during the service discovery but before it has actually been completed. Checking for GATT_SUCCESS will ensure that we only proceed once the service discovery has actually completed.

Once the supported services have been loaded in to the local proxy, we can actually access to the characteristics that they contain, and we’ll cover that in the concluding article in this series.

The source code for this article is available here.

© 2014, Mark Allison. All rights reserved.

Related

Copyright © 2014 Styling Android. All Rights Reserved.

Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.