Getting Started with ESP32 Bluetooth Low Energy (BLE) on Arduino IDE

Not only does the ESP32 have Wi-Fi, but it also has Bluetooth and Bluetooth Low Energy (BLE). This is a quick introduction to Bluetooth Low Energy (BLE) with the ESP32. We’ll start by looking at what BLE is and what it can be used for, and then we’ll look at some examples utilising the ESP32 and the Arduino IDE. We’ll make an ESP32 BLE server and an ESP32 BLE scanner to find it as a simple introduction.

Introducing Bluetooth Low Energy

What is Bluetooth Low Energy?

Bluetooth Low Energy, or BLE for short, is a power-saving Bluetooth version. The principal application of BLE is the transfer of small amounts of data over short distances (low bandwidth). Unlike Bluetooth, which is always on, BLE is always in sleep mode unless a connection is established.

As a result, it uses relatively little power. BLE uses about 100 times less electricity than Bluetooth.

Device 1 and Device2

Additionally, BLE supports not only point-to-point communication, but also broadcast mode, and mesh network.

Take a look at the table below that compares BLE and Bluetooth Classic in more detail.

BLE is ideal for applications that need to exchange modest amounts of data on a regular basis and run on a coin cell because of its features. BLE is used extensively in the healthcare, fitness, tracking, security, and home automation industries, for example.

Healthcare, fitness, tracking, security and home automation

BLE Server and Client

There are two types of Bluetooth Low Energy devices: the server and the client. The ESP32 may function as both a client and a server. The server broadcasts its existence so that other devices can find it, and it holds the data that the client can read. The client searches nearby devices for the server it seeks, then creates a connection and listens for incoming data. . This is called point-to-point communication.

.

Server  and Client

As mentioned previously, BLE also supports broadcast mode and mesh network:

  • Broadcast mode: the server transmits data to many clients that are connected;
  • Mesh network: all the devices are connected, this is a many to many connection.

Even though the broadcast and mesh network setups are possible to implement, they were developed very recently, so there aren’t many examples implemented for the ESP32 at this moment.

GATT

Generic Attributes (GATT) is a data structure that is available to linked BLE devices and defines a hierarchical data structure. GATT specifies how two Bluetooth Low Energy (BLE) devices send and receive standard messages. Understanding the hierarchy is crucial since it will make it easier to learn how to use the BLE and create applications.

BLE Service

A profile, which is made up of one or more services, is at the top of the hierarchy. Typically, a BLE device will have multiple services.

Every service has at least one feature and can also refer to other services. A service is nothing more than a collection of data, such as sensor readings. There are predefined services for several types of data defined by the SIG (Bluetooth Special Interest Group) like: Battery Level, Blood Pressure, Heart Rate, Weight Scale, etc.

BLE Characteristic

The attribute is always held by a service, and it is where the hierarchy’s real data is stored (value). The characteristic has two attributes: the characteristic declaration (which contains data metadata) and the characteristic value.

Additionally, descriptors can be added to the characteristic value to expand on the metadata provided in the characteristic declaration.

The properties describe how the characteristic value can be interacted with. Basically, it contains the operations and procedures that can be used with the characteristic:

  • Broadcast
  • Read
  • Write without response
  • Write
  • Notify
  • Indicate
  • Authenticated Signed Writes
  • Extended Properties

UUID

Each service, characteristic and descriptor have an UUID (Universally Unique Identifier). An UUID is a unique 128-bit (16 bytes) number. For example:

55072829-bc9e-4c53-938a-74a6d4c78776

There are shortened UUIDs for all types, services, and profiles specified in the SIG (Bluetooth Special Interest Group).

But if your application needs its own UUID, you can generate it using this UUID generator website.

In summary, the UUID is used for uniquely identifying information. For example, it can identify a particular service provided by a Bluetooth device.

BLE with ESP32

The ESP32 can act as a BLE server or as a BLE client. There are several BLE examples for the ESP32 in the ESP32 BLE library for Arduino IDE. This library comes installed by default when you install the ESP32 on the Arduino IDE.

In your Arduino IDE, you can go to File > Examples > ESP32 BLE Arduino and explore the examples that come with the BLE library.

Note: To see the ESP32 examples, go to Tools > Board and choose the ESP32 board.

We’ll develop an ESP32 BLE server and then an ESP32 BLE scanner to find that server as a quick introduction to the ESP32 with BLE on the Arduino IDE. The examples that come with the BLE library will be used and explained.

To follow this example, you need two ESP32 development boards. We’ll be using the ESP32 DOIT DEVKIT V1 Board.

ESP32 BLE Server

To create an ESP32 BLE Server, open your Arduino IDE and go to File > Examples > ESP32 BLE Arduino and select the BLE_server example. The following code should load:

 
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
 
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
 
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
 
void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE work!");
 
  BLEDevice::init("Long name works now");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );
 
  pCharacteristic->setValue("Hello World says Neil");
  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Characteristic defined! Now you can read it in your phone!");
}
 
void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
}

For creating a BLE server, the code should follow the next steps:

  1. Create a BLE Server. In this case, the ESP32 acts as a BLE server.
  2. Create a BLE Service.
  3.  Create a BLE Characteristic on the Service.
  4. Create a BLE Descriptor on the Characteristic.
  5. Start the Service.
  6.  Start advertising, so it can be found by other devices.

How the code works

Let’s take a quick look at how the BLE server example code works.

It starts by importing the necessary libraries for the BLE capabilities.

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

Then, you need to define a UUID for the Service and Characteristic.

#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

You can leave the default UUIDs, or you can go to uuidgenerator.net to create random UUIDs for your services and characteristics.

In the setup(), it starts the serial communication at a baud rate of 115200.

Serial

Then, you create a BLE device called “MyESP32”. You can change this name to whatever you like.

// Create the BLE Device
BLEDevice

In the following line, you set the BLE device as a server.

BLEServer *pServer = BLEDevice::createServer();

After that, you create a service for the BLE server with the UUID defined earlier.

 BLEService *pService = pServer->createService(SERVICE_UUID);

Then, you set the characteristic for that service. As you can see, you also use the UUID defined earlier, and you need to pass as arguments the characteristic’s properties. In this case, it’s: READ and WRITE.

BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                     CHARACTERISTIC_UUID,
                                     BLECharacteristic::PROPERTY_READ |
                                     BLECharacteristic::PROPERTY_WRITE
                                     

After creating the characteristic, you can set its value with the setValue() method.

pCharacteristic

In this case we’re setting the value to the text “Hello World says Neil”. You can change this text to whatever your like. In future projects, this text can be a sensor reading, or the state of a lamp, for example.

Finally, you can start the service, and the advertising, so other BLE devices can scan and find this BLE device.

BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising

This is just a simple example on how to create a BLE server. In this code nothing is done in the loop(), but you can add what happens when a new client connects (check the BLE_notify example for some guidance).

ESP32 BLE Scanner

It’s easy to make an ESP32 BLE scanner. Obtain a second ESP32 (while the other is running the BLE server sketch). Select the BLE scan example from File > Examples > ESP32 BLE Arduino in your Arduino IDE. The code below should load.

/*
   Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
   Ported to Arduino ESP32 by Evandro Copercini
*/
 
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
 
int scanTime = 5; //In seconds
BLEScan* pBLEScan;
 
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
    }
};
 
void setup() {
  Serial.begin(115200);
  Serial.println("Scanning...");
 
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);  // less or equal setInterval value
}
 
void loop() {
  // put your main code here, to run repeatedly:
  BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
  Serial.print("Devices found: ");
  Serial.println(foundDevices.getCount());
  Serial.println("Scan done!");
  pBLEScan->clearResults();   // delete results fromBLEScan buffer to release memory
  delay(2000);
}

This code sets up the ESP32 as a Bluetooth Low Energy device and searches for nearby devices. This code should be uploaded to your ESP32. To ensure that you’re uploading the code to the correct ESP32 board, temporarily disconnect the other ESP32 from your computer.

Once the code is uploaded and you should have the two ESP32 boards powered on:

  • One ESP32 with the “BLE_server” sketch;
  • Other with ESP32 “BLE_scan” sketch
scan server

Go to the Serial Monitor with the ESP32 running the “BLE_scan” example, press the ESP32 (with the “BLE_scan” sketch) ENABLE button to restart and wait a few seconds while it scans.The scanner found two devices: one is the ESP32 (it has the name “MyESP32), and the other is our MiBand2.

Testing the ESP32 BLE Server with Your Smartphone

Most modern smartphones should have BLE capabilities.

You can scan your ESP32 BLE server with your smartphone and see its services and characteristics. For that, we’ll be using a free app called nRF Connect for Mobile from Nordic, it works on Android (Google Play Store) and iOS (App Store).

Go to Google Play Store or App Store and search for “nRF Connect for Mobile”. Install the app and open it.

nrf connect for mobile.

Don’t forget go to the Bluetooth settings and enable Bluetooth adapter in your smartphone. You may also want to make it visible to other devices to test other sketches later on.

Location enabled

Once everything is ready in your smartphone and the ESP32 is running the BLE server sketch, in the app, tap the scan button to scan for nearby devices. You should find an ESP32 with the name “MyESP32”.

Click the “Connect” button.

As you can see in the figure below, the ESP32 has a service with the UUID that you’ve defined earlier. If you tap the service, it expands the menu and shows the Characteristic with the UUID that you’ve also defined.

The characteristic has the READ and WRITE properties, and the value is the one you’ve previously defined in the BLE server sketch. So, everything is working fine.