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.


Connected network topology

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 ProfilesServices and Characteristics, as shown below:

Example of a profile

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.

ComponentDescriptionSource
CableMicro-USB cable 
AntennaWi-Fi/Bluetooth AntennaLinkIt ONE development kit
PowerPolymer Li-ion 1000mAh batteryLinkIt ONE development kit
SensorTemperature and humidity sensorGrove 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.

Connecting the antenna and the sensor

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.
  • setup() function that initializes resources, such as the GATT and serial communication.
  • 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:

  1. Open the Arduino IDE and by default a new sketch is created and displayed.
  2. 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 sketch GATT_Sensor.ino and save it.

Creating a project in Arduino IDE

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

sketch is a source code file representing the core controlling logic for the LinkIt ONE development board. It consists of the following:

  • setup() function that initializes resources, such as the Bluetooth module.
  • loop() function that continuously listens to and processes events from hardware sensors and software modules such as those for Bluetooth and sensor. The loop() function runs forever — until the device is shutdown.

Define the variables

  1. Define temperature_service of class Temperature_Service in your GATT_Temperature.ino file. 

    Temperature_Service temperature_service;
  2. 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);
  3. Define a temperature variable for further use. 

    float temperature = 0.0;
  4. 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

  1. Initiate serial communication by calling the begin() method of the Serial class at a baud rate of 115200.

    Serial.begin(115200);
  2. Initiate the sensor communication by calling the begin() method of the DHT class. 

    dht.begin();
  3. Start the GATT server with temperature_service by calling the begin() method of the LGATTServer 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

  1. Apply 5 seconds delay by calling the delay(5000) method, for each iteration of the loop() function.

    delay(5000);
  2. Call event handlers of the LGATTServer class, as follows. 

    LGATTServer.handleEvents();
  3. 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 global temperature variable. 

    if (temperature_service.isConnected())
      {
        float h = 0.0;
        float t = 0.0;
        if (dht.readHT(&t, &h))
        {
          if(t!=temperature){
              temperature = t;
  4. 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);
  5. 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.

  1. Add source and header files.
    • Click on the top right drop down menu then select New Tab, as shown below.

    Adding source and header files to an existing project

    • 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.cppsource 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.

  1. 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 type TYPE_SERVICE, followed by elements of type TYPE_CHARACTERISTIC or typeTYPE_DESCRIPTOR. The last element in the array must be of type TYPE_END.
  2. 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.

GATT communication and temperature data on the Serial Monitor

To observe the data remotely:

  1. Enable Bluetooth connectivity on your handheld device.
  2. 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.
  3. Click CONNECT at MTKBTDEVICE to open the available services for the LinkIt ONE development board.
  4. 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. 

    BLE scanner app The UUID and characteristics of your LinkIt ONE device Read and Notify options available on the app

  5. Although the board is continuously sending notifications nothing is displayed until the client subscribes to the notifications. Click to trigger subscription to the server.
  6. 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 
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.