A Quick Guide on how to use BLE in ESP32?
In this blog, we will study ESP32’s BLE functionality. Along with Classic Bluetooth, the ESP32 platform also supports Bluetooth low Energy or BLE. The ESP32 BLE is designed for wireless battery-powered, low-power communication between various BLE Devices. In order to use BLE in ESP32 and set it up as both a Server and a Client, we will master all the necessary skills. Additionally, we’ll look at how to link a smartphone to an ESP32 BLE server.
BLE (Bluetooth Low Energy)
The incorporation of Bluetooth Low Energy, or simply BLE, was the key component of the Bluetooth 4.0 Specification. A highly optimized version of Classic Bluetooth created exclusively for low-power wireless communication is BLE (formerly branded as Bluetooth Smart).
While the Classic Bluetooth was made to replace cable connectivity with short-range wireless communication, it is sometimes referred to as Basic Rate and Enhanced Dara Rate in technical documentation. Large file transfers, wireless audio, and other uses were taken into consideration when it was designed.
BLE, on the other hand, is created with IoT and battery-operated applications in mind and is intended for low power consumption rather than faster communication speeds. Smartwatches, activity trackers, health monitors, radio beacons, and other gadgets are some of the prominent BLE applications.
Unlike Classic Bluetooth, which is always connected, BLE often sleeps and only wakes up when necessary. So, there is extremely low power usage.
Additionally operating on the same 2.4 GHz ISM frequency range is Bluetooth Low Energy. This implies that Wi-Fi and both Bluetooth variants can be used with a single antenna.
Different Layers in BLE
A BLE device consists of three layers:
- Controller
- Host
- Application
The controller contains the following:
- PHY – The Physical Layer
- LL – The Link Layer
- HCI – Controller side Host-Controller Interface
The host contains the following:
- HCI – Host side Host-Controller Interface
- L2CAP – Logical Link Control and Application Protocol
- SM – Security Manager
- ATT – Attribute Protocol
- GAP – Generic Access Profile
- GATT – Generic Attribute Profile
The user interface, data handling, and logical components of the application are all contained in the application layer, which is the topmost layer.
Important Terms in BLE
- GATT: Generic Attribute Profile is abbreviated as GATT. Through the use of Service and Characteristics, it establishes the requirements for data flow between BLE devices.
- Characteristic: A characteristic is a collection of data termed an attribute, and an attribute is a collection of data that is sent from one device to another. Typically, a characteristic includes the following qualities:
- Value:Â The characteristic’s data value
- Declaration:Â Characteristic properties (location, type like read, write, notify, indicate etc.)
- Description: ASCII String describing the characteristic.
- Service:A service is a group of traits. Each Service has a distinct UUID, which might be 16 or 128 bits in length.
- UUID: Each service and feature in a profile is assigned a 128-bit UUID, or universally unique identifier. To create distinctive IDs, use the website UUIDGenerator. A special 16-bit or 128-bit ID called a UUID is assigned to each service and attribute. An example of a UUID looks like this:
- Â 583f8b30-74b4-4757-8143-56048fd88b25Â
BLE States
A BLE device can have 5 possible states:
- Standby
- Advertising
- Scanning
- Initiating
- Connected
BLE Network Topologies
There are two different types of BLE communication: connection type and broadcast type. The “broadcaster” BLE Device transmits data to any “observer” BLE Device while broadcasting. One-way data flow is what it is.
You require a “Connection” between the BLE Devices in order to communicate in both directions. From peripheral (slave) BLE devices that transmit the packets, a central (master) BLE device repeatedly scans for advertising data packets.
BLE in ESP32
We are aware that Wi-Fi is the ESP32’s primary feature. But in addition, the ESP32 SoC also supports Bluetooth. Dual-mode Bluetooth is used by the ESP32. This indicates that ESP32 is compatible with Bluetooth Classic and Bluetooth Low Energy (BLE).
Particularly, Bluetooth v4.2 Classic Bluetooth (BR/EDR) and BLE protocols are supported by the ESP32 Bluetooth system. We had looked at a few instances of how to use the ESP32 Classic Bluetooth in the previous tutorial. In this lesson, we will investigate ESP32’s BLE capability.
ESP32 BLE Server Client Model
Any BLE device can be set up as either a client or a server. This also applies to ESP32, which can function as a server that announces its presence so that clients can access its data or a client that searches for servers and connects to them in order to get data from them.
Two ESP32 Boards, one configured as a BLE Server and the other as a BLE Slave, will be used in this project.
ESP32 BLE Server
We will first look at setting up an ESP32 as a BLE server. Connect the ESP32 Board to the computer via a USB cable. This board will be referred to as the “ESP32-BLE Server.” Make sure the appropriate ESP32 Development Board and COM port are selected before starting the Arduino IDE.
Code
This is a code snippet from the “BLE server” example. I included comments throughout the code to clarify every step.
This code should be uploaded to the ESP32 server.
| Â | #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” |
| Â | Â |
| Â | /* BLEServer *pServer = BLEDevice::createServer(); |
| Â | BLEService *pService = pServer->createService(SERVICE_UUID); |
| Â | BLECharacteristic *pCharacteristic = pService->createCharacteristic( |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â CHARACTERISTIC_UUID, |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â BLECharacteristic::PROPERTY_READ | |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â BLECharacteristic::PROPERTY_WRITE |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â ); */ |
| Â | Â |
| Â | BLEServer *pServer; |
| Â | BLEService *pService; |
| Â | BLECharacteristic *pCharacteristic; |
| Â | Â |
| Â | Â |
| Â | void setup() |
| Â | { |
| Â | Serial.begin(115200); |
| Â | Serial.println(“Starting BLE Server!”); |
| Â | Â |
| Â | BLEDevice::init(“ESP32-BLE-Server”); |
| Â | pServer = BLEDevice::createServer(); |
| Â | pService = pServer->createService(SERVICE_UUID); |
| Â | pCharacteristic = pService->createCharacteristic( |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â CHARACTERISTIC_UUID, |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â BLECharacteristic::PROPERTY_READ | |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â BLECharacteristic::PROPERTY_WRITE |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â ); |
| Â | Â |
| Â | Â |
| Â | /* 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!”); |
| Â | pService->start(); |
| Â | //BLEAdvertising *pAdvertising = pServer->getAdvertising(); |
| Â | 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(); |
| Â | //pAdvertising->start(); |
| Â | Serial.println(“Characteristic defined! Now you can read it in the Client!”); |
| Â | } |
| Â | Â |
| Â | void loop() |
| Â | { |
| Â | std::string value = pCharacteristic->getValue(); |
| Â | Serial.print(“The new characteristic value is: “); |
| Â | Serial.println(value.c_str()); |
| Â | delay(2000); |
| Â | } |
ESP32 BLE Client
Select ESP32-BLE Client from another ESP32 Board and attach it to the PC. Choose the COM port for this board in the Arduino IDE.
Code
The ESP32 BLE Library includes an example with the name “BLE client.” Here, I applied the same code (with small modifications). Put this code on the ESP32 client device.
| Â | #include “BLEDevice.h” |
| Â | Â |
| Â | /* Specify the Service UUID of Server */ |
| Â | static BLEUUID serviceUUID(“4fafc201-1fb5-459e-8fcc-c5c9c331914b”); |
| Â | /* Specify the Characteristic UUID of Server */ |
| Â | static BLEUUIDÂ Â charUUID(“beb5483e-36e1-4688-b7f5-ea07361b26a8”); |
| Â | Â |
| Â | static boolean doConnect = false; |
| Â | static boolean connected = false; |
| Â | static boolean doScan = false; |
| Â | static BLERemoteCharacteristic* pRemoteCharacteristic; |
| Â | static BLEAdvertisedDevice* myDevice; |
| Â | Â |
| Â | static void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â uint8_t* pData, size_t length, bool isNotify) |
| Â | { |
| Â | Serial.print(“Notify callback for characteristic “); |
| Â | Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); |
| Â | Serial.print(” of data length “); |
| Â | Serial.println(length); |
| Â | Serial.print(“data: “); |
| Â | Serial.println((char*)pData); |
| Â | } |
| Â | Â |
| Â | class MyClientCallback : public BLEClientCallbacks |
| Â | { |
| Â | void onConnect(BLEClient* pclient) |
| Â | { |
| Â | Â Â |
| Â | } |
| Â | Â |
| Â | void onDisconnect(BLEClient* pclient) |
| Â | { |
| Â | Â connected = false; |
| Â | Â Serial.println(“onDisconnect”); |
| Â | } |
| Â | }; |
| Â | Â |
| Â | /* Start connection to the BLE Server */ |
| Â | bool connectToServer() |
| Â | { |
| Â | Serial.print(“Forming a connection to “); |
| Â | Serial.println(myDevice->getAddress().toString().c_str()); |
| Â | Â Â |
|  | BLEClient* pClient = BLEDevice::createClient(); |
| Â | Serial.println(” – Created client”); |
| Â | Â |
| Â | pClient->setClientCallbacks(new MyClientCallback()); |
| Â | Â |
| Â | Â /* Connect to the remote BLE Server */ |
| Â | pClient->connect(myDevice);Â // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private) |
| Â | Serial.println(” – Connected to server”); |
| Â | Â |
| Â | Â /* Obtain a reference to the service we are after in the remote BLE server */ |
| Â | BLERemoteService* pRemoteService = pClient->getService(serviceUUID); |
| Â | if (pRemoteService == nullptr) |
| Â | { |
| Â | Â Serial.print(“Failed to find our service UUID: “); |
| Â | Â Serial.println(serviceUUID.toString().c_str()); |
| Â | Â pClient->disconnect(); |
| Â | Â return false; |
| Â | } |
| Â | Serial.println(” – Found our service”); |
| Â | Â |
| Â | Â |
| Â | /* Obtain a reference to the characteristic in the service of the remote BLE server */ |
| Â | pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); |
| Â | if (pRemoteCharacteristic == nullptr) |
| Â | { |
| Â | Â Serial.print(“Failed to find our characteristic UUID: “); |
| Â | Â Serial.println(charUUID.toString().c_str()); |
| Â | Â pClient->disconnect(); |
| Â | Â return false; |
| Â | } |
| Â | Serial.println(” – Found our characteristic”); |
| Â | Â |
| Â | /* Read the value of the characteristic */ |
| Â | /* Initial value is ‘Hello, World!’ */ |
| Â | if(pRemoteCharacteristic->canRead()) |
| Â | { |
| Â | Â std::string value = pRemoteCharacteristic->readValue(); |
| Â | Â Serial.print(“The characteristic value was: “); |
| Â | Â Serial.println(value.c_str()); |
| Â | } |
| Â | Â |
| Â | if(pRemoteCharacteristic->canNotify()) |
| Â | { |
| Â | Â pRemoteCharacteristic->registerForNotify(notifyCallback); |
| Â | Â |
| Â | } |
| Â | Â |
| Â | Â connected = true; |
| Â | Â return true; |
| Â | } |
| Â | /* Scan for BLE servers and find the first one that advertises the service we are looking for. */ |
| Â | class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks |
| Â | { |
| Â | /* Called for each advertising BLE server. */ |
| Â | void onResult(BLEAdvertisedDevice advertisedDevice) |
| Â | { |
| Â | Â Serial.print(“BLE Advertised Device found: “); |
| Â | Â Serial.println(advertisedDevice.toString().c_str()); |
| Â | Â |
| Â | Â /* We have found a device, let us now see if it contains the service we are looking for. */ |
| Â | Â if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) |
| Â | Â { |
| Â | Â Â BLEDevice::getScan()->stop(); |
| Â | Â Â myDevice = new BLEAdvertisedDevice(advertisedDevice); |
| Â | Â Â doConnect = true; |
| Â | Â Â doScan = true; |
| Â | Â |
| Â | Â } |
| Â | } |
| Â | }; |
| Â | Â |
| Â | Â |
| Â | void setup() |
| Â | { |
| Â | Serial.begin(115200); |
| Â | Serial.println(“Starting Arduino BLE Client application…”); |
| Â | BLEDevice::init(“ESP32-BLE-Client”); |
| Â | Â |
| Â | /* Retrieve a Scanner and set the callback we want to use to be informed when we |
|  |   have detected a new device. Specify that we want active scanning and start the |
| Â | Â Â scan to run for 5 seconds. */ |
| Â | BLEScan* pBLEScan = BLEDevice::getScan(); |
| Â | pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); |
| Â | pBLEScan->setInterval(1349); |
| Â | pBLEScan->setWindow(449); |
| Â | pBLEScan->setActiveScan(true); |
| Â | pBLEScan->start(5, false); |
| Â | } |
| Â | Â |
| Â | Â |
| Â | void loop() |
| Â | { |
| Â | Â |
| Â | /* If the flag “doConnect” is true, then we have scanned for and found the desired |
|  |   BLE Server with which we wish to connect. Now we connect to it. Once we are |
| Â | Â Â connected we set the connected flag to be true. */ |
| Â | if (doConnect == true) |
| Â | { |
| Â | Â if (connectToServer()) |
| Â | Â { |
| Â | Â Â Serial.println(“We are now connected to the BLE Server.”); |
| Â | Â } |
| Â | Â else |
| Â | Â { |
| Â | Â Â Serial.println(“We have failed to connect to the server; there is nothin more we will do.”); |
| Â | Â } |
| Â | Â doConnect = false; |
| Â | } |
| Â | Â |
| Â | /* If we are connected to a peer BLE Server, update the characteristic each time we are reached |
| Â | Â Â with the current time since boot */ |
| Â | if (connected) |
| Â | { |
| Â | Â String newValue = “Time since boot: ” + String(millis()/2000); |
| Â | Â Serial.println(“Setting new characteristic value to \”” + newValue + “\””); |
| Â | Â Â |
| Â | Â /* Set the characteristic’s value to be the array of bytes that is actually a string */ |
| Â | Â pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); |
| Â | Â /* You can see this value updated in the Server’s Characteristic */ |
| Â | } |
| Â | else if(doScan) |
| Â | { |
| Â | Â BLEDevice::getScan()->start(0);Â // this is just example to start scan after disconnect, most likely there is better way to do it in arduino |
| Â | } |
| Â | Â |
| Â | delay(2000); /* Delay a second between loops */ |
| Â | } |
Working
You must keep an eye on the serial ports of both ESP32 boards after uploading code to them in order to check how they are communicating. For this, I’m opening the COM port on the ESP32 BLE Client Device using the Arduino IDE’s Serial Monitor and the serial port on the ESP32 BLE Server Device using another programme (like Putty or Terminal).
Reset both boards by pressing the corresponding ENABLE (EN) buttons after accessing the corresponding serial port terminals for the ESP32 BLE Server and Client.
Starting the BLE Server and promoting its services is done by the ESP32 BLE Server device. If you look at the code for the Client ESP32, you’ll see that we utilised the Service UUID and Characteristic UUID of the Server ESP32. This implies that when the Client ESP32 searches for BLE devices, it connects only if it finds a BLE with the specific UUIDs.
The ESP32 BLE Client first reads the characteristic value from the server (which we set to “Hello, World!” in the Server’s code) and prints it on the console after the connection has been made.
Following that, the client tries to change the server’s characteristic value every few seconds. The client’s terminal is seen in the picture below.
In relation to the Server, it reads its own characteristic value every few seconds after launching the BLE Server. The following graphic shows how the client updates the server’s characteristic value as the server reads it.
Testing ESP32 BLE Server with SmartPhone
Let’s examine how to link our smartphones to an ESP32 BLE server right away. Open the serial monitor in the Arduino IDE and upload the following code to the ESP32 Board, which we’ll refer to as the ESP32 BLE Server.
| Â | #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” |
| Â | Â |
| Â | Â |
| Â | class MyCallbacks: public BLECharacteristicCallbacks |
| Â | { |
| Â | void onWrite(BLECharacteristic *pCharacteristic) |
| Â | { |
| Â | Â std::string value = pCharacteristic->getValue(); |
| Â | Â |
| Â | Â if (value.length() > 0) |
| Â | Â { |
| Â | Â Â Serial.println(“*********”); |
| Â | Â Â Serial.print(“New value: “); |
| Â | Â Â for (int i = 0; i < value.length(); i++) |
| Â | Â Â { |
| Â | Â Â Â Serial.print(value[i]); |
| Â | Â Â } |
| Â | Â |
| Â | Â Â Serial.println(); |
| Â | Â Â Serial.println(“*********”); |
| Â | Â } |
| Â | } |
| Â | }; |
| Â | Â |
| Â | void setup() |
| Â | { |
| Â | Serial.begin(115200); |
| Â | Â |
| Â | Serial.println(“1- Download and install an BLE scanner app in your phone”); |
| Â | Serial.println(“2- Scan for BLE devices in the app”); |
| Â | Serial.println(“3- Connect to ESP32-BLE_Server”); |
| Â | Serial.println(“4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something”); |
| Â | Serial.println(“5- See the magic =)”); |
| Â | Â |
| Â | BLEDevice::init(“ESP32-BLE-Server”); |
| Â | BLEServer *pServer = BLEDevice::createServer(); |
| Â | Â |
| Â | BLEService *pService = pServer->createService(SERVICE_UUID); |
| Â | Â |
| Â | BLECharacteristic *pCharacteristic = pService->createCharacteristic( |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â CHARACTERISTIC_UUID, |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â BLECharacteristic::PROPERTY_READ | |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â BLECharacteristic::PROPERTY_WRITE |
| Â | Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â ); |
| Â | Â |
| Â | pCharacteristic->setCallbacks(new MyCallbacks()); |
| Â | Â |
| Â | pCharacteristic->setValue(“Hello World”); |
| Â | pService->start(); |
| Â | Â |
| Â | BLEAdvertising *pAdvertising = pServer->getAdvertising(); |
| Â | pAdvertising->start(); |
| Â | } |
| Â | Â |
| Â | void loop() |
| Â | { |
| Â | delay(2000); |
| Â | } |
Go to the Google Play Store on the smartphone, download the “BLE Scanner” application by Bluepixel Technologies, open it after installation, and enable Bluetooth and Location (both are required for scanning BLE devices) in the phone (the app will prompt the same), and then click the “scan” icon in the top right corner.
You can notice ‘ESP32-BLE-Server’ in the list of BLE Devices that are listed out by the app as it begins to scan for them.
Since we have just configured ESP32 with one service, you can see the Service UUID and also the Characteristic UUID under the ‘CUSTOM SERVICE’ section. Once you click ‘CONNECT,’ your smart phone connects to the ESP32 BLE server and displays the list of services and their characteristics.
When you tap the ‘R’ icon in the ‘CUSTOM SERVICE’ section, the app reads the value from the server and shows it in the ‘Value’ option, allowing you to view the service’s first characteristic value.
Since the characteristic’s settings have been set to allow both reading and writing, setting its value is as simple as pressing on the “W” icon, which causes a text box to appear where the value may be entered, and then clicking “OK” to set the value in the server.
Instead of continuously monitoring the ‘value’ field like we did in the previous example, we are actually using the callback feature of the BLE Library, which will be called if and only if a ‘write’ option is initiated. If you open the Serial Monitor of the Server, you can see the server prints out newly set value.
Conclusion
I hope all of you understand how to use BLE in ESP32. We MATHA ELECTRONICS will be back soon with more informative blogs soon.