Using GATT in Bluetooth Low Energy on the LinkIt ONE development board
Introduction
This tutorial uses the LinkIt ONE development board’s Bluetooth features to define the board as a general attribute profile (GATT) client (GAP peripheral device).
This tutorial guides you through:
- Understanding how Bluetooth Low Energy and GATT manage connections.
- Creating the software to connect the LinkIt ONE development board as a peripheral to a master device, using Bluetooth Low Energy.
- Acquiring temperature data from a DHT sensor and transmitting the data to a smart device supporting Bluetooth Low Energy and GATT.
In addition to the guide below, you can also follow the tutorial in this video:
Understanding GATT
What is GATT?
The LinkIt ONE development board supports two Bluetooth profiles:
- Bluetooth Serial Port Profile (SPP) over Bluetooth 2.1.
- Generic Attribute Profile (GATT) over Bluetooth 4.0.
The focus of this tutorial is to provide detailed description on the GATT protocol and its support on the LinkIt ONE development platform.
GATT is the architecture that specifies how data is stored and exchanged between Bluetooth Low Energy enabled devices. GATT organizes the data in Services and Characteristics. The Bluetooth Special Interest Group (SIG) has defined sets of Services and Characteristics in order for different vendors to create devices that are inter-connectable.
Connected network topology
The devices are connected in a certain network topology. A peripheral device can only be connected to one central device at a time. A central device can be connected to multiple peripherals at the same time, as shown below.
Roles
There are two different roles to communicate with the GATT protocol.
Server. Serves data to the client.
Client. Receives data from the server.
Both, the central device and the peripheral device can have both roles, depending on the situation. An example application is a heart rate monitor. The heart rate monitor is the peripheral device and the smartphone is the central device. A typical GATT server is a peripheral device. The server sends a notification to the client to let the client know that there is new data. This means that the client doesn’t have to query the server all the time to get new data.
Profile, services and characteristics
GATT transactions in Bluetooth 4.0 and up are based on high-level, nested objects called Profiles, Services and Characteristics, as shown below:
Profiles
Profiles are high level definitions that define how services can be used to enable an application or use case. The complete list of officially adopted GATT-based profiles can be found here.
Services
Services are collections of characteristics and relationships to other services that encapsulate the behavior of part of a device. Services are identified by a unique identifier called UUID. This can be either 16 (official GATT specification) or 128 bit user defined IDs.
The full list of official Bluetooth Low Energy services can be found here.
Characteristics
Each Service has a number of Characteristics. The Characteristics store values and permissions of the service.
Each Characteristic has at least two attributes, the main attribute and a value attribute that contains the value. The main attribute identifies the value attribute by using its UUID.
You can use the Characteristics to send data back to the peripheral device.
Data transfer types
There are four different ways to initiate data transfers.
Read. Read is requested by the Client to the Server. When a server receives a read request, the server sends back the requested Characteristic value.
Write. Write request is initialized by the Client. The new characteristic will be written in the Server.
Notify. Notification is initialized by the Server. If a new value is written to the Characteristic, the new value is pushed to the Client that has subscribed updates for that Characteristic. The notifications are not acknowledged.
Indicate. Indications are the same acknowledged Notifications guaranteed to be received with a slight overhead.
Before you start
If you haven’t built a LinkIt ONE project before, this section describes the steps you need to follow before commencing this project
Install the development environment
Full details on downloading and installing the Arduino IDE and LinkIt ONE SDK then configuring the IDE and upgrading the board firmware are provided in the LinkIt ONE quick start guide. Complete this before you continue if you haven’t already set up your development environment.
An Android device
The application supports smartphones and tablets running Android v. 4.0 or above, with an active data connection (mobile network or Wi-Fi) and Bluetooth 4.0 support. In this particular scenario Android smartphone is used with third-party software - Bluetooth Low Energy device scanner app installed on it.
Build the hardware
This section describes the hardware and electronics needed to build the setup for your project.
What you need
To build your project’s hardware, in addition to LinkIt ONE development board you need the following components.
Component | Description | Source |
---|---|---|
Cable | Micro-USB cable | |
Antenna | Wi-Fi/Bluetooth Antenna | LinkIt ONE development kit |
Power | Polymer Li-ion 1000mAh battery | LinkIt ONE development kit |
Sensor | Temperature and humidity sensor | Grove Starter Kit for LinkIt ONE |
Putting the components together
This section provides step-by-step instructions on putting the project’s hardware together.
Step 1 — Antenna
Attach the dual purpose Wi-Fi/Bluetooth antenna to its corresponding pin on the LinkIt ONE development board.
Step 2 — Sensor
Attach a temperature and humidity sensor to the LinkIt ONE development board using the Grove - Universal 4-pin buckled cable. The final setup of the hardware is shown below.
Create your project’s software
The GATT communication with a handheld device needs an Arduino sketch to connect to send and receive data from sensor attached to the LinkIt ONE development board. This section describes the software required as follows:
- Additional libraries.
- Overview of the Arduino sketch.
- Transmitting sensor data to a remote device using Bluetooth Low Energy GATT connectivity.
Additional libraries
The project requires DHT library that you can download from here and add under libraries directory of your Arduino IDE. Restart the IDE for further use. The DHT library enables to read data from the Grove temperature and humidity pro sensor.
Overview of the Arduino Sketch
To create the device that will collect TPH data and print it on the serial interface, an Arduino sketch is implemented, verified and uploaded to the LinkIt ONE development board.
The content of a sketch is as follows:
- Definitions of header files.
- Definitions of variables.
- A
setup()
function that initializes resources, such as the GATT and serial communication. - A
loop()
function that continuously listens to and processes events from hardware and software modules such as the one for the sensor. The loop() function runs forever — until the device is shutdown.
Start your Arduino project
Before you can write the code for your sketch you need to create it, which is achieved as follows:
- Open the Arduino IDE and by default a new sketch is created and displayed.
- Create a folder named
GATT_Sensor
in your documents folder and on the File menu click Save As, navigate to the new folder, name the sketchGATT_Sensor.ino
and save it.
Add the header files for the supporting libraries
The first part of your code adds the libraries required to the GATT_Sensor.ino
Arduino sketch.
GATT communication
Add the Bluetooth GATT header file.
#include <LGATTUUID.h>
Sensor communication
Add DHT temperature and humidity sensor installed in Add DHT sensor library.
#include <DHT.h>
Custom library
Add a custom defined library with a detailed content described in section The Temperature_Service class.
#include “Temperature_Service.h”
Add source code to the sketch GATT_Sensor.ino
A sketch is a source code file representing the core controlling logic for the LinkIt ONE development board. It consists of the following:
- A
setup()
function that initializes resources, such as the Bluetooth module. - A
loop()
function that continuously listens to and processes events from hardware sensors and software modules such as those for Bluetooth and sensor. Theloop()
function runs forever — until the device is shutdown.
Define the variables
Define temperature_service of class
Temperature_Service
in yourGATT_Temperature.ino
file.Temperature_Service temperature_service;
Define the pin the sensor is connected to and the sensor type. Then define the
dht()
method of the DHT class with the pin and sensor type settings.#define DHTPIN 0 // define pin we're connected to #define DHTTYPE DHT22 // DHT 22 (AM2302) DHT dht(DHTPIN, DHTTYPE);
Define a temperature variable for further use.
float temperature = 0.0;
Add a macro to print multiple strings using
printf()
method of the Serial class.#ifdef APP_LOG #undef APP_LOG #endif #define APP_LOG(...) Serial.printf(__VA_ARGS__); \ Serial.println();
Define the setup()
function
Initiate serial communication by calling the
begin()
method of the Serial class at a baud rate of 115200.Serial.begin(115200);
Initiate the sensor communication by calling the
begin()
method of the DHT class.dht.begin();
Start the GATT server with temperature_service by calling the
begin()
method of theLGATTServer
class (LGATTServer.begin()).// The begin function starts with how many services you want to start, plus the // the service object(s) you want to start. if (!LGATTServer.begin(1, &temperature_service)) { APP_LOG("GATT Server failed"); } else{ APP_LOG("GATT Server started!"); }
Add a loop()
function
Apply 5 seconds delay by calling the
delay(5000)
method, for each iteration of theloop()
function.delay(5000);
Call event handlers of the
LGATTServer
class, as follows.LGATTServer.handleEvents();
The
loop()
function is executed continuously providing temperature data. If the service is connected, the code below reads out the sensor data and checks if the readout differs from the last readout of the temperature and stores it in the globaltemperature
variable.if (temperature_service.isConnected()) { float h = 0.0; float t = 0.0; if (dht.readHT(&t, &h)) { if(t!=temperature){ temperature = t;
Format the temperature value into a string and send an indication to the Client. Send an indication to the client that the temperature value has changed by calling the
sendIndication()
function, as shown below.//Create a new LGATTAttributeValue LGATTAttributeValue value = {0}; //Build a string with the temperature readout String temperature_string = "Temperature "+String(temperature); const char *str = temperature_string.c_str(); value.len = strlen(str); //copy the string into the LGATTAttributeValue memcpy(value.value, str, value.len); //Send an indication to the GATT client boolean ret = temperature_service.sendIndication(value, temperature_service.getHandleNotify(), false);
Print a message on the Serial output, if sending the indication failed or succeeded.
if (!ret) { APP_LOG("[FAILED] send [%d]", ret); } APP_LOG("send [%d][%s]", ret, value.value);
Full source code of the GATT_Sensor.ino
The full source code of the GATT_Sensor.ino
is provided below.
#include "DHT.h" #include "Temperature_Service.h" #include <LGATTUUID.h> #define DHTPIN 0 // what pin we're connected to #define DHTTYPE DHT22 // DHT 22 (AM2302) DHT dht(DHTPIN, DHTTYPE); float temperature = 0.0; #ifdef APP_LOG #undef APP_LOG #endif #define APP_LOG(...) Serial.printf(__VA_ARGS__); \ Serial.println(); Temperature_Service temperature_service; void setup() { Serial.begin(115200); dht.begin(); delay(8000); if (!LGATTServer.begin(1, &temperature_service)) { APP_LOG("GATT Server failed"); } else{ APP_LOG("GATT Server started!"); } } void loop() { // put your main code here, to run repeatedly: delay(5000); LGATTServer.handleEvents(); //LGATTAttributeValue value = {13, "hello, world!"}; if (temperature_service.isConnected()) { float h = 0.0; float t = 0.0; if (dht.readHT(&t, &h)) { if(t!=temperature){ temperature = t; //Create a new LGATTAttributeValue LGATTAttributeValue value = {0}; //Build a string with the temperature readout String temperature_string = "Temperature "+String(temperature); const char *str = temperature_string.c_str(); value.len = strlen(str); //copy the string into the LGATTAttributeValue memcpy(value.value, str, value.len); //Send an indication to the GATT client boolean ret = temperature_service.sendIndication(value, temperature_service.getHandleNotify(), false); if (!ret) { APP_LOG("[FAILED] send [%d]", ret); } APP_LOG("send [%d][%s]", ret, value.value); } } } }
Add classes in Arduino
The project is based on several classes that provide the temperature measurement functionality using the LinkIt ONE development board.
- Add source and header files.
- Click on the top right drop down menu then select New Tab, as shown below.
- Name the new file as
Temperature_Service.cpp
. - Repeat the same step and create
Temperature_Service.h
header file.
The Temperature_Service
class
The Temperature_Service
class is used to create a GATT server with LGATTService that defines the Services and Characteristics and sets properties and permissions as described in Profile, services and characteristics.
Depending on your needs, you can either program LinkIt ONE as a central device that connects to other Bluetooth 4.0 devices and accesses their profiles and services, or program LinkIt ONE as a peripheral device that provides profiles and services for other devices to use. The detailed information about implementing GATT profile as a server or as a client can be found in section 5.5, “Using the Bluetooth GATT profile” in LinkIt ONE developer’s guide. In this tutorial the development board is defined as a server.
The Temperature_Service.h
header file defines the parameters and functions implemented in the Temperature_Service.cpp
source file, as shown below.
#ifndef __LGATT_UART_H__ #define __LGATT_UART_H__ #include "LGATTServer.h" class Temperature_Service : public LGATTService { public: uint16_t getHandle(int32_t type); boolean isConnected(){ return _connected; }; uint16_t getHandleNotify(){ return _handle_notify; } protected: // prepare the data for profile virtual LGATTServiceInfo *onLoadService(int32_t index); // characteristic added virtual boolean onCharacteristicAdded(LGATTAttributeData &data); // connected or disconnected virtual boolean onConnection(const LGATTAddress &addr, boolean connected); // read action comming from master virtual boolean onRead(LGATTReadRequest &data); // write action comming from master virtual boolean onWrite(LGATTWriteRequest &data); private: uint16_t _handle_notify; uint16_t _handle_write; boolean _connected; //LGATTAttributeValue _value; }; #endif
The content of the Temperature_Service.cpp
source file is described below.
- Define a g_temperature_decl array of type
LGATTServiceInfo
, a structure that defines GATT service, characteristic and descriptor. The first array elements must be of typeTYPE_SERVICE
, followed by elements of typeTYPE_CHARACTERISTIC
or typeTYPE_DESCRIPTOR
. The last element in the array must be of type TYPE_END. Add Services and Characteristics by creating an array of
LGATTServiceInfo
objects, as shown below.static LGATTServiceInfo g_temperature_decl[] = { //Create a service with a random 128BIT UUID {TYPE_SERVICE, "E20A39F4-73F5-4BC4-A12F-17D1AD07A961", TRUE, 0, 0, 0}, //Create a characteristic that has READ and NOTIFY properties and has the permission to be read. {TYPE_CHARACTERISTIC, "08590F7E-DB05-467E-8757-72F6FAEB13D4", FALSE, VM_GATT_CHAR_PROP_NOTIFY | VM_GATT_CHAR_PROP_READ, VM_GATT_PERM_READ, 0}, //Finish of the array {TYPE_END, 0, 0, 0, 0, 0} };
The rest of the implementation follows the description in section 5.5.2, “Implementing a General Attribute Profile (GATT) server” of the LinkIt ONE developer’s guide .
Full source code of the Temperature_Service.cpp
#include "vmbtgatt.h" #include "temperature_service.h" #ifdef APP_LOG #undef APP_LOG #define APP_LOG(...) Serial.printf(__VA_ARGS__); \ Serial.println(); #endif static LGATTServiceInfo g_temperature_decl[] = { {TYPE_SERVICE, "E20A39F4-73F5-4BC4-A12F-17D1AD07A961", TRUE, 0, 0, 0}, {TYPE_CHARACTERISTIC, "08590F7E-DB05-467E-8757-72F6FAEB13D4", FALSE, VM_GATT_CHAR_PROP_NOTIFY | VM_GATT_CHAR_PROP_READ, VM_GATT_PERM_READ, 0}, {TYPE_END, 0, 0, 0, 0, 0} }; uint16_t Temperature_Service::getHandle(int32_t type) { if (0 == type) { return _handle_notify; } else if (1 == type) { return _handle_write; } return 0; } // prepare the data for profile LGATTServiceInfo *Temperature_Service::onLoadService(int32_t index) { return g_temperature_decl; } // characteristic added boolean Temperature_Service::onCharacteristicAdded(LGATTAttributeData &data) { const VM_BT_UUID *uuid = &(data.uuid); APP_LOG("LGATTSUart::onCharacteristicAdded f[%d] uuid[12] = [0x%x] len[%d]", data.failed, uuid->uuid[12], uuid->len); if (!data.failed) { if (0x7E == uuid->uuid[12]) { _handle_notify = data.handle; } else if (0x7F == uuid->uuid[12]) { _handle_write = data.handle; } } return true; } // connected or disconnected boolean Temperature_Service::onConnection(const LGATTAddress &addr, boolean connected) { _connected = connected; APP_LOG("LGATTSUart::onConnection connected [%d], [%x:%x:%x:%x:%x:%x]", _connected, addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], addr.addr[1], addr.addr[0]); return true; } // read action comming from master boolean Temperature_Service::onRead(LGATTReadRequest &data) { APP_LOG("LGATTSUart::onRead _connected [%d]", _connected); if (_connected) { LGATTAttributeValue value = {0}; const char *str = "Hello Temperature"; memcpy(value.value, str, strlen(str)); value.len = strlen(str); APP_LOG("LGATTSUart::onRead onRead [%d][%s]", value.len, value.value); data.ackOK(value); } return true; } // write action comming from master boolean Temperature_Service::onWrite(LGATTWriteRequest &data) { APP_LOG("LGATTSUart::onWrite _connected [%d]", _connected); if (_connected) { // if need to rsp to central. if (data.need_rsp) { LGATTAttributeValue value; value.len = 0; data.ackOK(); } } return true; }
Run your application
Follow the instructions in the get started with the LinkIt One development board to upload and run the sketch.
The output from the Serial Monitor is shown below.
To observe the data remotely:
- Enable Bluetooth connectivity on your handheld device.
- Launch the BLE scanner app (see An Android device) on your handheld device (in this case an Android phone) to scan for the available devices.
- Click CONNECT at MTKBTDEVICE to open the available services for the LinkIt ONE development board.
- Once the Services and Characteristics are detected, the app will display a list of available services. Expand the Custom Service by clicking on the small arrow on the left. READ and NOTIFY properties of the characteristic are displayed, as shown below.
- Although the board is continuously sending notifications nothing is displayed until the client subscribes to the notifications. Click N to trigger subscription to the server.
- The temperature string shows on the screen. Note, that the Server only sends the updates if the temperature changed since the last time.
Temperature data updated on your mobile device
Conclusion
In this tutorial you have implemented a GATT client server communication application using the LinkIt ONE development board, temperature sensor and Arduino programming environment. For more information on the MediaTek LinkIt ONE development board please refer to the LinkIt ONE developer’s guide.