Thursday 15 November 2018

[Technique] ESP32 Bluetooth Low Energy

1. Introduction

Bluetooth Low Energy (BLE) is one kind of wireless protocol. This blog will give you an overview of BLE, specifically how data is organized in BLE, how devices advertise their presence and how to establish communication between them.

1) Generic Access Profile

GAP is used to control the connections and advertising in BLE devices. Specifically, 
  • it determines the roles of devices (Peripheral devices or Central devices). The peripheral device usually acts as the Server or the Slave and The central device acts the Client and the Master
  • There are two ways to send advertising out with GAP. Peripheral devices advertise and central devices send Scan Response Data. 
  • A peripheral will set a specific advertising interval, and every time this interval passes, it will retransmit its main advertising packet. If a listening device is interested in the scan response payload (and it is available on the peripheral) it can optionally request the scan response payload.

  • The Broadcast Network Topology shows as below. One peripheral can communicate with multi central devices but it only connects one central device at a time.  As soon as a peripheral connects to a central device, it will stop advertising itself and other devices will no longer be able to see it or connect to it until the existing connection is broken.

2) Generic Attribute Profile

GAPP defines the way that two Bluetooth Low Energy devices transfer data back and forth using concepts called Services and Characteristics. It makes use of a generic data protocol called the Attribute Protocol (ATT), which is used to store Services, Characteristics and related data in a simple lookup table using 16-bit IDs for each entry in the table. Before GAPP works, GAP must be passed to enable connect. 
  • Once connecting, the Connected Network Topology is shown below. The Two-way communication is established between the central device and peripheral devices. But the central device can only connect one peripheral device at a time.

  • Then a server/client relationship is built. So the data exchange process between a peripheral (the GATT Server) and a central device (the GATT Client) is shown:

  • The GATT data structure can be illustrated as below: the profile stands for a collection of Services and it doesn't actually exist. Service, as the name suggests, stands for one kind of action. It is distinguished by the UUID. A service usually contains one or multi-characteristics. Characteristic is the lowest level concept in GATT transactions. If we see Service is the name of an action, the characteristic is the executor of this action. Similarly to Services, each Characteristic distinguishes itself via a pre-defined 16-bit or 128-bit UUID. Descriptors are defined attributes that describe a characteristic value. It means descriptors are used to set the content of each characteristic.
  • An example of how the GATT data structure works: If you want to buy some potato chips. The Service is kind of like a grocery store, Walmart for example. Inside this Service (Walmart) there are many Characteristics which can be thought of different good sections (e.g. snack section, canned food section). For potato chips, you will choose the snack section. Then the Descriptors which are attributes of Characteristics can be thought of as some brands of potato chips in the snack section of Walmart. If the value of the Descriptor is 'Pringles', it means you pick up some Pringles potato chips. 

2. BLE communication

1) BLE Serial Port Profile (SPP)

BLE serial port is based on SPP and used for establishing serial communication between BLEs, e.g. between Bluetooth modules, Bluetooth modules,  Bluetooth adapters,  and Bluetooth modules and PDAs and your computers.

2) Communication Structure

Before communicating, we will use two different characteristics, TX and RX under the same "service" on both ends to send data to and receive data via these two channels between two ends. The server "notifies" the client via the TX characteristic UUID and data is sent to the server and received via the RX characteristic UUID. However, as the TX on one end connects the RX on the other end, the UUID's of TX and RX are swapped on both ends.

3) Communication Process 

More information can refer to the sources of ESP32_BLE_Arduino: https://github.com/nkolban/ESP32_BLE_Arduino/tree/master/src
  • Create the BLE Device using 'BLEDevice::init("");'. Then advocate roles for the created BLE device: BLEServer, BLEScan and BLEClient. Here are some source codes to create a BLE server with Service and Characteristic:
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLEDevice::init("MyESP32");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());  //set callback to get the status of BLE connection
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                        BLECharacteristic::PROPERTY_READ   |
                                        BLECharacteristic::PROPERTY_WRITE  |
                                        BLECharacteristic::PROPERTY_NOTIFY |
                                        BLECharacteristic::PROPERTY_INDICATE
                                       );
//specially, when the characteristic is created to notify data (no need for receiving data), we have to create a descriptor for characteristic by using:  where BLE2902(), the Client Characteristic Configuration descriptor defines how the characteristic may be configured by a specific client.
// Create a BLE Descriptor
pCharacteristic->addDescriptor(new BLE2902());
    • Data Transfer Methods
There are four basic operations for moving data in BLE: read, write, notify and indicatehttps://www.silabs.com/community/wireless/bluetooth/knowledge-base.entry.html/2018/05/30/ble_basics_masters-i4n9
  1.     If the client needs to send data to the server, use write.
  2.     If the client needs to get data from the server on-demand (i.e. polling), use read.
  3.     If the server needs to send data to the client without the client requesting it first, use notify or indicate. (The client must subscribe to these updates before any data will be transferred.)
  4.     The server cannot pull data from the client directly. If this is necessary, the server must use notify or indicate to send pre-arranged request data to the client, and then the client may follow up with a write operation to send back whatever data that the server needs.
  5. Therefore, generally, the property of characteristic for the server sending data is usually  BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE;  the property of characteristic for the server (actually, when the server is receiving data, it can be imagined as a client) receiving data is usually BLECharacteristic::PROPERTY_READ   |  BLECharacteristic::PROPERTY_WRITE 
    • Callback function
The Callbacks are classes of functions used to handle follow-up things after each transaction. For example, on BLE server end, there is BLEServerCallbacks to handle the connection status. And on BLE client end, there is a corresponding class, BLEClientCallbacks, to handle related things. Moreover, the service and the characteristic all have their own callback functions.
format: class MyServerCallbacks: public BLEServerCallbacks {}
bool deviceConnected = false;
bool oldDeviceConnected = false;

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }

};
  • Initiate the service and characteristic, then start advertising. To connect other BLE client devices, the server needs to advertise periodically. Once the connection which is triggered by the client built, the server stop advertising and only communicate with the connected client. The advertising codes are as follow. 
uint8_t value = 0;  //globe variable
  pCharacteristic->setValue(&value, 1);  //each Characteristic has a value.
  // Start the service
  pService->start();
  // Start advertising
  pServer->getAdvertising()->start();
  • Use callback functions to deal with the message after building the connection. 

4) Demonstration

  • On ESP32 BLE end: Create two characteristics, RX and TX, for receiving and transmitting the data between ESP32 (server) and your mobile device (client)
/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
    Ported to Arduino ESP32 by Evandro Copercini
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic_TX = NULL;
BLECharacteristic* pCharacteristic_RX = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t sending_value = 0;

//monitoring the connection status
class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };
    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

//Dealing with the data from the client
class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.println("receiving data from the client");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);    //here you can make some judgement about the receiving data 
                                     //and trigger some other actions.

        Serial.println();
        Serial.println("*********");
      }
    }
};


void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");

  BLEDevice::init("MyESP32");
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());  //set callback to get the status of BLE connection
  BLEService *pService = pServer->createService(SERVICE_UUID);

  //Create two characteristics: RX (receiver) and TX (transmitter)
  //this is TX charateristic
  pCharacteristic_TX = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID_TX,     
                                         BLECharacteristic::PROPERTY_NOTIFY |
                                         BLECharacteristic::PROPERTY_INDICATE                                         
                                       );
  // Create a BLE Descriptor
  pCharacteristic_TX->addDescriptor(new BLE2902());  // for each notify Characteristic, this step is neccessary  
  pCharacteristic_TX->setValue(&sending_value, 0);  //give the variable an initial value. this varible can be other type.
                                         // this is an interface we can insert our data from sensor or other devices
                                         // to transmit to the mobile BLE client.
  
  //this is RX charateristic
  pCharacteristic_RX = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID_RX,     
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE                                        
                                       );
  pCharacteristic_RX->setCallbacks(new MyCallbacks());   //this callback will be called when data is received by this server
  pCharacteristic_RX->setValue("Hello World");
  
    
  // Start the service
  pService->start();

  // Start advertising to search surounding clients
  pServer->getAdvertising()->start();
  
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}

void loop() {
  // put your main code here, to run repeatedly:
  // notify changed value
    if (deviceConnected) {
        Serial.println("BLE is connecting");
        pCharacteristic_TX->setValue(&sending_value, 1);
        pCharacteristic_TX->notify();
        Serial.print("notifying: ");
        Serial.println(sending_value);
        sending_value++;
        delay(10); // bluetooth stack will go into congestion, if too many packets are sent
    }
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
    
  delay(200);
}
  • On the mobile device end: Firstly, downloading the BLE test apk; then following the introduction below.
    • Click "+" to connect a new BLE

    • Scan surrounding available BLE devices

    • Choose the BLE device named "myESP32"

    • Click the connect and once state turns True, two BLE devices connect. 

    • Choose "PROPERTY_NOTIFY", then you select the corresponding characteristic to receive data. 

    • Choose "PROPERTY_READ" or "PROPERTY_WRITE", then you select the corresponding characteristic to send data. Enter the string in the red area and click SEND.  


No comments:

Post a Comment

[Research] Recurrent Neural Network (RNN)

1. Introduction As we all know, forward neural networks (FNN) have no connection between neurons in the same layer or in cross layers. Th...