Wireless Headphones, Fitness bands, Bluetooth Speakers, In-Ear headphones, Mobile phones, Laptops... there are so many Bluetooth devices around us and most of these devices are battery operated. Have you ever wondered that, when you connect a Bluetooth device to your mobile phone how it automatically understands that the connected device is a computer or audio device or a mobile phone? For some devices our phone might even automatically shows the battery percentage of the connected device on the notification bar. How does all this happen on their own? There should be some common protocol shared between the phone and the Bluetooth device right!
Stay curious, you will get answers for these questions as we try to understand Bluetooth Low Energy (BLE for short), with the popular ESP32 module. Unlike Classic Bluetooth in ESP32 the BLE operates only when a communication is activated and stays in sleep mode otherwise, this makes it the right choice for battery powered applications. BLE can also form mesh networks and act as Beacons. Normally a BLE modules works either as a server or as a client, here we will use ESP32 BLE as server.
Here we have divided the complete ESP32 Bluetooth into three segments for ease of understanding.
1. Serial Bluetooth on ESP32 toggling LED from Mobile Phone
2. BLE server to send Battery level data to Mobile Phone using GATT Service
3. BLE client to scan for BLE devices and act as beacon.
We have already covered the first article; in this article we will learn how to make the ESP32 BLE to work as a server and use the GATT Service to send battery level information. For testing purpose we will send hardcoded values from ESP32 as battery percentage to our mobile phone through BLE GATT service, this way our Mobile will assume that ESP32 is a battery operated Bluetooth device which is trying to send to its battery percentage. Before going into detail we will understad few terminologies related with Bluetooth Low Energy.
Terminologies related to BLE (Bluetooth Low Energy)
BLE Server: As told earlier the BLE can be programmed to work either as a Server or as a client. When working as a server the BLE can only provide data it cannot initiate a connection. Example would be a fitness band. A Server could send information only if the client requests for it.
Most commonly the ESP32’s BLE is used a Server. Each Server will have one or more Service within it and similarly each service will have one or more characteristics associated with it. A Characteristic may have zero, one or more than one Descriptor inside it. Every Service, characteristic or Descriptor will have its own pre-defined unique ID called UUID.
BLE Client: The client can scan connect and listen to other Bluetooth devices. An example would be your mobile phone. Note that most BLE hardware devices can work as server and as client, it the software that decides the role of the device.
Peripheral Device / Central Device: In an BLE network there could be only one Central Device, but can have as many Peripheral devices as required. The Central Device can connect to all peripheral devices at the same time, but the peripheral device can connect only to the Central Device, this way no two peripheral device can share data among each other. A best example for Central device will be our smart Phones and for Peripheral device will be our Bluetooth earphone or fitness bands.
BLE Advertising: A BLE Advertising is the geeky term to instruct the Bluetooth device to be visible to all so that it can pair and establish a connection .It can be considered as a one way communication. Here the server keeps on advertising data expecting a server to receive it. BLE Beacon is a type of BLE Advertisement.
UUID (Universal Unique Identifier): Every BLE Bluetooth device is given a Universal Unique Identifier Number when programmed by the programmer. You can think of this identifier as a sequence of numbers which represents the functionality/role of the BLE device. Again there are two types of UUID. One is the Service UUID and the other is Characteristic UUID.
GATT Service: GATT stands for Generic Attribute Profile; this defines some standard ways using which two BLE devices should always communicate. This Attribute (ATT) Protocol is pre-defined and is common for all BLE devices so this way any two BLE devices can identify each other. So GATT was the answer to our previous question.
The technique using which two BLE device should send data to and forth is defined by the concept called services and characteristics.
BLE Service / BLE characteristic: The Service UUID tells us what type of service the BLE device is going to perform and the Characteristic UUID tells what are the parameters or functions that will be performed by that service. So every Service will have one or more characteristics under them. Okay! Where does the programmer get this UUID from? Every UUID is already defined by the GATT (Generic Attribute Profile) you can visit their website and select the UUID as required for the project. I know it has bounced a bit over our head; let’s try understanding it with an example.
Let’s assume the BLE device of an audio player. Initially when you pair it with your phone, your phone identifies it as an audio device and also displays the battery level on the status bar. So, for this to happen the audio player has to somehow tell your phone that it is willing to share the battery level and the percentage of charge it has in it battery. This is done by using the UUID, there is a specific UUID which tells that the BLE dice is going to provide details about battery level this UUID which tells the type of service is called Service UUID, again there could be so many parameters that has to be exchanged for completing a service like the value of battery is on such parameter, each parameter will have its own UUID and these are called the Characteristic UUID. The common function performed by a characteristic is Read, Write, Notify and Indicate.
BLE Descriptor: The Descriptor is an optional attribute that is present inside the Characteristic. A Descriptor normally specifies how to access a Characteristic.
BLE Beacon: A Bluetooth Beacon is more like a proximity switch which performs some pre-defined action when the user gets into a range (close proximity). It advertises its identity all the time and hence is ready to pair always.
BLE2902: I am still sceptical about this thing, but you can think of it as a piece of software on the client side that informs the server to turn notification On or Off this will help us in saving power
Hope you got a rough idea, the good is that we need not know much since all the handwork is already done for us though the libraries.
Preparing the hardware
The project requires no hardware set-up but make sure you have added ESP32 board details on your Arduino IDE and have tried minimum sample blink program to check if everything is working as expected. You sceptical on how to do it you can follow the Getting started with ESP32 with Arduino tutorial to do the same.
Also to test the BLE services we will be using the nRF android application on our mobile which can be directly downloaded from the PlayStore. It is also available in Itunes Store for Iphone users. If you are planning to work with BLE for a long time, this application will really come in handy for debugging purposes.
Programming ESP32 for Battery Level Indication using GATT service
By this time I assume that you have a fair idea on what GATT service and how it is implemented using Service and characteristic models. Now, let us dive into the program to learn how it is implemented in ESP32 using the Arduino IDE. Before we continue I would like to use this space to thank Andreas Spiess for his video BLE which made things a lot clear on my side.
We begin the program by importing the required libraries into our sketch. There are a lot of things to configure in order to use the ESP32’s BLE functionality hopefully though thanks to Neil Kolban who has already done the hard work for us and has provided the libraries. If you want to understand the functionality of the libraries you can refer to his documentation on github page.
#include <BLEDevice.h> #include <BLEUtils.h> #include <BLEServer.h> //Library to use BLE as server #include <BLE2902.h>
Next we have to define the Server Call-back function for our Bluetooth device. Before that lets understand that what is callback function in BLE.
What is callback function in BLE?
When BLE is operating as Server it is important to define a Server callback function. There are many types of callbacks associated with BLE but to put it simple you consider these as an acknowledgement being performed to make sure that action has been completed. A server callback is used to ensure that the connection between client and server is established successfully.
We use the following lines of code to perform a server callback.
bool _BLEClientConnected = false; class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { _BLEClientConnected = true; }; void onDisconnect(BLEServer* pServer) { _BLEClientConnected = false; } };
Inside the void setup function, we initiate Serial communication at 115200 for debugging and then initialize the Bluetooth Device through InitBLE function.
void setup() { Serial.begin(115200); Serial.println("Battery Level Indicator - BLE"); InitBLE(); }
The initBLE is the place where all the magic happens. We have to create a Bluetooth server and use the Battery Level service in here. But before that we have to define the UUID for Service, Characteristic and Descriptor for reading the battery Level. All the UUID can be obtained from the Bluetooth GATT service website. For our case we are trying to use the Battery service and UUID for it is defined as 0X180F as shown below.
Next, we need to know the Characteristic associated with this service. To know that simply click on Battery Service and you will be taken to Service Characteristics page, where it is mentioned that battery Level is the name of the characteristics and it takes in the value from 0 to 100. Also note that we can perform only two actions with this characteristic, one is to Read which is mandatory to do and the other is Notify which is Optional. So we have to send the battery value to client (Phone) which is mandatory and if needed we can notify the phone about which is optional.
But wait we still did not find the UUID value for the Characteristic Battery Level. To do that get into the Battery Characteristic page and search for the Battery Level name you will find its UUID as 0X2A19, the snapshot of same is shown below.
Now that we have all the values, let us put it the program as shown below. The name BatterySerivce, BatteryLevelCharacteristic and BatteryLevelDescriptor are user defined variables to refer to the Service, Characteristic and Descriptor that we are using in the program. The Value for Descriptor 0X2901 is used when the size of the value is 8-bit, more information can be found Descriptor Description page.
#define BatteryService BLEUUID((uint16_t)0x180F)
BLECharacteristic BatteryLevelCharacteristic(BLEUUID((uint16_t)0x2A19), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor BatteryLevelDescriptor(BLEUUID((uint16_t)0x2901));
Getting back to the initBLE function. We first have to start the BLE server and make it advertise with a name. The following lines are used to start the BLE as server. The name that I have given to my BLe server is “BLE Battery”, but you can choose your own.
BLEDevice::init("BLE Battery"); // Create the BLE Server BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks());
Next we have to start the GATT service since we have already defined the UUID we can simply start the service using the line below.
// Create the BLE Service BLEService *pBattery = pServer->createService(BatteryService);
Once the service is started we can link the descriptor with characteristics, and set the values. The BLE2902 service is also added here as shown below.
pBattery->addCharacteristic(&BatteryLevelCharacteristic); BatteryLevelDescriptor.setValue("Percentage 0 - 100"); BatteryLevelCharacteristic.addDescriptor(&BatteryLevelDescriptor); BatteryLevelCharacteristic.addDescriptor(new BLE2902());
Finally everything is set, now all that is left is to ask the ESP32 to advertise so that other devices like our phone can discover it and connect to it , and when connected to a client it should initiate the Battery service which can be done though the following lines.
pServer->getAdvertising()->addServiceUUID(BatteryService); pBattery->start(); // Start advertising pServer->getAdvertising()->start();
That is it so far so good, the last step is to tell the descriptor what is the value of the battery in percentage that should be sent to the client (Phone). This value can be from 0 -100 as we read earlier, to keep things simple, I have simple hard coded the value of battery to be 57 and then increment it every 5 seconds and start from 0 once it reaches 100. The code to do that is shown below. Note that the value that is being sent is in format unit8_t.
uint8_t level = 57; void loop() { BatteryLevelCharacteristic.setValue(&level, 1); BatteryLevelCharacteristic.notify(); delay(5000); level++; Serial.println(int(level)); if (int(level)==100) level=0; }
Testing your GATT service on ESP32 BLE
The complete code explained above is given at the end of page. Upload the code to your ESP32 board. Once uploaded your phone should discover a Bluetooth device called “BLE Battery” Pair with it.
Then install the nRF android application and open it and connect to the BLE Battery BLE device. Expand the Battery Service section and you should find the following screen.
As you can see the Application has automatically identified that the BLE provides Battery Service and has the characteristics of Battery Level because of the UUID that we used in the program. You can also see that current battery value that is 67% wait for 5 seconds and you can also notice it getting incremented.
The cool thing about using BLE is that now any application that works with BLE will think that your ESP32 is BLE device which notifies battery level. To try it out I used an application called BatON and the application identified the ESP32 as battery powered Bluetooth device and gave the percentage notification on my phone like this
Cool!! Right? I have also shown the complete working in the video below. Now, that you have learnt how to use BLE Battery services with ESP32, you can try other GATT services as well which are very interesting like Pulse rate, HID, Heart Rate etc.. Have fun....
/*Program to use GATT service on ESP32 to send Battery Level
* ESP32 works as server - Mobile as client
* Program by: B.Aswinth Raj
* Dated on: 13-10-2018
* Website: www.circuitdigest.com
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h> //Library to use BLE as server
#include <BLE2902.h>
bool _BLEClientConnected = false;
#define BatteryService BLEUUID((uint16_t)0x180F)
BLECharacteristic BatteryLevelCharacteristic(BLEUUID((uint16_t)0x2A19), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor BatteryLevelDescriptor(BLEUUID((uint16_t)0x2901));
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
_BLEClientConnected = true;
};
void onDisconnect(BLEServer* pServer) {
_BLEClientConnected = false;
}
};
void InitBLE() {
BLEDevice::init("BLE Battery");
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pBattery = pServer->createService(BatteryService);
pBattery->addCharacteristic(&BatteryLevelCharacteristic);
BatteryLevelDescriptor.setValue("Percentage 0 - 100");
BatteryLevelCharacteristic.addDescriptor(&BatteryLevelDescriptor);
BatteryLevelCharacteristic.addDescriptor(new BLE2902());
pServer->getAdvertising()->addServiceUUID(BatteryService);
pBattery->start();
// Start advertising
pServer->getAdvertising()->start();
}
void setup() {
Serial.begin(115200);
Serial.println("Battery Level Indicator - BLE");
InitBLE();
}
uint8_t level = 57;
void loop() {
BatteryLevelCharacteristic.setValue(&level, 1);
BatteryLevelCharacteristic.notify();
delay(5000);
level++;
Serial.println(int(level));
if (int(level)==100)
level=0;
}