ESP32 BLE Server And Client (Bluetooth Low Energy)
Maybe your like
Learn how to make a BLE (Bluetooth Low Energy) connection between two ESP32 boards. One ESP32 is going to be the server, and the other ESP32 will be the client. The BLE server advertises characteristics that contain sensor readings that the client can read. The ESP32 BLE client reads the values of those characteristics (temperature and humidity) and displays them on an OLED display.

Recommended Reading: Getting Started with ESP32 Bluetooth Low Energy (BLE)
What is Bluetooth Low Energy?
Before going straight to the project, it is important to take a quick look at some essential BLE concepts so that you’re able to better understand the project later on. If you’re already familiar with BLE, you can skip to the Project Overview section.
Bluetooth Low Energy, BLE for short, is a power-conserving variant of Bluetooth. BLE’s primary application is short-distance transmission of small amounts of data (low bandwidth). Unlike Bluetooth that is always on, BLE remains in sleep mode constantly except for when a connection is initiated.
This makes it consume very low power. BLE consumes approximately 100x less power than Bluetooth (depending on the use case). You can check the main differences between Bluetooth and Bluetooth Low Energy here.
BLE Server and Client
With Bluetooth Low Energy, there are two types of devices: the server and the client. The ESP32 can act either as a client or as a server.
The server advertises its existence, so it can be found by other devices and contains data that the client can read. The client scans the nearby devices, and when it finds the server it is looking for, it establishes a connection and listens for incoming data. This is called point-to-point communication.

There are other possible communication modes like broadcast mode and mesh network (not covered in this tutorial).
GATT
GATT stands for Generic Attributes and it defines a hierarchical data structure that is exposed to connected BLE devices. This means that GATT defines the way that two BLE devices send and receive standard messages. Understanding this hierarchy is important because it will make it easier to understand how to use BLE with the ESP32.

- Profile: standard collection of services for a specific use case;
- Service: collection of related information, like sensor readings, battery level, heart rate, etc. ;
- Characteristic: it is where the actual data is saved on the hierarchy (value);
- Descriptor: metadata about the data;
- Properties: describe how the characteristic value can be interacted with. For example: read, write, notify, broadcast, indicate, etc.
In our example, we’ll create a service with two characteristics. One for the temperature and another for the humidity. The actual temperature and humidity readings are saved on the value under their characteristics. Each characteristic has the notify property, so that it notifies the client whenever the values change.
UUID
Each service, characteristic, and descriptor have a UUID (Universally Unique Identifier). A UUID is a unique 128-bit (16 bytes) number. For example:
55072829-bc9e-4c53-938a-74a6d4c78776There 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.
For a more detailed introduction about BLE, read our getting started guide:
- Getting Started with ESP32 Bluetooth Low Energy (BLE) on Arduino IDE
Project Overview
In this tutorial, you’re going to learn how to make a BLE connection between two ESP32 boards. One ESP32 is going to be the BLE server, and the other ESP32 will be the BLE client.

The ESP32 BLE server is connected to a BME280 sensor and it updates its temperature and humidity characteristic values every 30 seconds.
The ESP32 client connects to the BLE server and it is notified of its temperature and humidity characteristic values. This ESP32 is connected to an OLED display and it prints the latest readings.
This project is divided into two parts:
- Part 1 – ESP32 BLE server
- Part 2 – ESP32 BLE client
Parts Required
Here’s a list of the parts required to follow this project:
ESP32 BLE Server:
- ESP32 DOIT DEVKIT V1 Board (read Best ESP32 development boards)
- BME280 Sensor
- Jumper wires
- Breadboard
- Smartphone with Bluetooth (optional)
ESP32 BLE Client:
- ESP32 DOIT DEVKIT V1 Board (read Best ESP32 development boards)
- OLED display
- Jumper wires
- Breadboard
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

1) ESP32 BLE Server
In this part, we’ll set up the BLE Server that advertises a service that contains two characteristics: one for temperature and another for humidity. Those characteristics have the Notify property to notify new values to the client.

Schematic Diagram
The ESP32 BLE server will advertise characteristics with temperature and humidity from a BME280 sensor. You can use any other sensor as long as you add the required lines in the code.
We’re going to use I2C communication with the BME280 sensor module. For that, wire the sensor to the default ESP32 SCL (GPIO 22) and SDA (GPIO 21) pins, as shown in the following schematic diagram.

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?
Installing BME280 Libraries
As mentioned previously, we’ll advertise sensor readings from a BME280 sensor. So, you need to install the libraries to interface with the BME280 sensor.
- Adafruit_BME280 library
- Adafruit_Sensor library
You can install the libraries using the Arduino Library Manager. Go to Sketch > Include Library > Manage Libraries and search for the library name.
Installing Libraries (VS Code + PlatformIO)
If you’re using VS Code with the PlatformIO extension, copy the following to the platformio.ini file to include the libraries.
lib_deps = adafruit/Adafruit Unified Sensor @ ^1.1.4 adafruit/Adafruit BME280 Library @ ^2.1.2ESP32 BLE Server – Code
With the circuit ready and the required libraries installed, copy the following code to the Arduino IDE, or to the main.cpp file if you’re using VS Code.
/********* Rui Santos Complete instructions at https://RandomNerdTutorials.com/esp32-ble-server-client/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h> //Default Temperature is in Celsius //Comment the next line for Temperature in Fahrenheit #define temperatureCelsius //BLE server name #define bleServerName "BME280_ESP32" Adafruit_BME280 bme; // I2C float temp; float tempF; float hum; // Timer variables unsigned long lastTime = 0; unsigned long timerDelay = 30000; bool deviceConnected = false; // See the following for generating UUIDs: // https://www.uuidgenerator.net/ #define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59" // Temperature Characteristic and Descriptor #ifdef temperatureCelsius BLECharacteristic bmeTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor bmeTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902)); #else BLECharacteristic bmeTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor bmeTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2902)); #endif // Humidity Characteristic and Descriptor BLECharacteristic bmeHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor bmeHumidityDescriptor(BLEUUID((uint16_t)0x2903)); //Setup callbacks onConnect and onDisconnect class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } }; void initBME(){ if (!bme.begin(0x76)) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } void setup() { // Start serial communication Serial.begin(115200); // Init BME Sensor initBME(); // Create the BLE Device BLEDevice::init(bleServerName); // Create the BLE Server BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // Create the BLE Service BLEService *bmeService = pServer->createService(SERVICE_UUID); // Create BLE Characteristics and Create a BLE Descriptor // Temperature #ifdef temperatureCelsius bmeService->addCharacteristic(&bmeTemperatureCelsiusCharacteristics); bmeTemperatureCelsiusDescriptor.setValue("BME temperature Celsius"); bmeTemperatureCelsiusCharacteristics.addDescriptor(&bmeTemperatureCelsiusDescriptor); #else bmeService->addCharacteristic(&bmeTemperatureFahrenheitCharacteristics); bmeTemperatureFahrenheitDescriptor.setValue("BME temperature Fahrenheit"); bmeTemperatureFahrenheitCharacteristics.addDescriptor(&bmeTemperatureFahrenheitDescriptor); #endif // Humidity bmeService->addCharacteristic(&bmeHumidityCharacteristics); bmeHumidityDescriptor.setValue("BME humidity"); bmeHumidityCharacteristics.addDescriptor(new BLE2902()); // Start the service bmeService->start(); // Start advertising BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pServer->getAdvertising()->start(); Serial.println("Waiting a client connection to notify..."); } void loop() { if (deviceConnected) { if ((millis() - lastTime) > timerDelay) { // Read temperature as Celsius (the default) temp = bme.readTemperature(); // Fahrenheit tempF = 1.8*temp +32; // Read humidity hum = bme.readHumidity(); //Notify temperature reading from BME sensor #ifdef temperatureCelsius static char temperatureCTemp[6]; dtostrf(temp, 6, 2, temperatureCTemp); //Set temperature Characteristic value and notify connected client bmeTemperatureCelsiusCharacteristics.setValue(temperatureCTemp); bmeTemperatureCelsiusCharacteristics.notify(); Serial.print("Temperature Celsius: "); Serial.print(temp); Serial.print(" ºC"); #else static char temperatureFTemp[6]; dtostrf(tempF, 6, 2, temperatureFTemp); //Set temperature Characteristic value and notify connected client bmeTemperatureFahrenheitCharacteristics.setValue(temperatureFTemp); bmeTemperatureFahrenheitCharacteristics.notify(); Serial.print("Temperature Fahrenheit: "); Serial.print(tempF); Serial.print(" ºF"); #endif //Notify humidity reading from BME static char humidityTemp[6]; dtostrf(hum, 6, 2, humidityTemp); //Set humidity Characteristic value and notify connected client bmeHumidityCharacteristics.setValue(humidityTemp); bmeHumidityCharacteristics.notify(); Serial.print(" - Humidity: "); Serial.print(hum); Serial.println(" %"); lastTime = millis(); } } }View raw code
You can upload the code, and it will work straight away advertising its service with the temperature and humidity characteristics. Continue reading to learn how the code works, or skip to the Client section.
There are several examples showing how to use BLE with the ESP32 in the Examples section. In your Arduino IDE, go to File > Examples > ESP32 BLE Arduino. This server sketch is based on the Notify example.
Importing Libraries
The code starts by importing the required libraries.
#include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #include <Wire.h> #include <Adafruit_Sensor.h> #include <Adafruit_BME280.h>Choosing Temperature Unit
By default, the ESP sends the temperature in Celsius degrees. You can comment the following line or delete it to send the temperature in Fahrenheit degrees.
//Comment the next line for Temperature in Fahrenheit #define temperatureCelsiusBLE Server Name
The following line defines a name for our BLE server. Leave the default BLE server name. Otherwise, the server name in the client code also needs to be changed (because they have to match).
//BLE server name #define bleServerName "BME280_ESP32"BME280 Sensor
Create an Adafruit_BME280 object called bme on the default ESP32 I2C pins.
Adafruit_BME280 bme; // I2CThe temp, tempF and hum variables will hold the temperature in Celsius degrees, the temperature in Fahrenheit degrees, and the humidity read from the BME280 sensor.
float temp; float tempF; float hum;Other Variables
The following timer variables define how frequently we want to write to the temperature and humidity characteristic. We set the timerDelay variable to 30000 milliseconds (30 seconds), but you can change it.
// Timer variables unsigned long lastTime = 0; unsigned long timerDelay = 30000;The deviceConnected boolean variable allows us to keep track if a client is connected to the server.
bool deviceConnected = false;BLE UUIDs
In the next lines, we define UUIDs for the service, for the temperature characteristic in celsius, for the temperature characteristic in Fahrenheit, and for the humidity.
// https://www.uuidgenerator.net/ #define SERVICE_UUID "91bad492-b950-4226-aa2b-4ede9fa42f59" // Temperature Characteristic and Descriptor #ifdef temperatureCelsius BLECharacteristic bmeTemperatureCelsiusCharacteristics("cba1d466-344c-4be3-ab3f-189f80dd7518", BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor bmeTemperatureCelsiusDescriptor(BLEUUID((uint16_t)0x2902)); #else BLECharacteristic bmeTemperatureFahrenheitCharacteristics("f78ebbff-c8b7-4107-93de-889a6a06d408", BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor bmeTemperatureFahrenheitDescriptor(BLEUUID((uint16_t)0x2901)); #endif // Humidity Characteristic and Descriptor BLECharacteristic bmeHumidityCharacteristics("ca73b3ba-39f6-4ab3-91ae-186dc9577d99", BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor bmeHumidityDescriptor(BLEUUID((uint16_t)0x2903));I recommend leaving all the default UUIDs. Otherwise, you also need to change the code on the client side—so the client can find the service and retrieve the characteristic values.
setup()
In the setup(), initialize the Serial Monitor and the BME280 sensor.
// Start serial communication Serial.begin(115200); // Init BME Sensor initBME();Create a new BLE device with the BLE server name you’ve defined earlier:
// Create the BLE Device BLEDevice::init(bleServerName);Set the BLE device as a server and assign a callback function.
// Create the BLE Server BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks());The callback function MyServerCallbacks() changes the boolean variable deviceConnected to true or false according to the current state of the BLE device. This means that if a client is connected to the server, the state is true. If the client disconnects, the boolean variable changes to false. Here’s the part of the code that defines the MyServerCallbacks() function.
//Setup callbacks onConnect and onDisconnect class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; } };Start a BLE service with the service UUID defined earlier.
BLEService *bmeService = pServer->createService(SERVICE_UUID);Then, create the temperature BLE characteristic. If you’re using Celsius degrees it sets the following characteristic and descriptor:
#ifdef temperatureCelsius bmeService->addCharacteristic(&bmeTemperatureCelsiusCharacteristics); bmeTemperatureCelsiusDescriptor.setValue("BME temperature Celsius"); bmeTemperatureCelsiusCharacteristics.addDescriptor(new BLE2902());Otherwise, it sets the Fahrenheit characteristic:
#else bmeService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics); bmeTemperatureFahrenheitDescriptor.setValue("BME temperature Fahrenheit"); bmeTemperatureFahrenheitCharacteristics.addDescriptor(new BLE2902()); #endifAfter that, it sets the humidity characteristic:
// Humidity bmeService->addCharacteristic(&bmeHumidityCharacteristics); bmeHumidityDescriptor.setValue("BME humidity"); bmeHumidityCharacteristics.addDescriptor(new BLE2902());Finally, you start the service, and the server starts the advertising so other devices can find it.
// Start the service bmeService->start(); // Start advertising BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pServer->getAdvertising()->start(); Serial.println("Waiting a client connection to notify...");loop()
The loop() function is fairly straightforward. You constantly check if the device is connected to a client or not. If it’s connected, and the timerDelay has passed, it reads the current temperature and humidity.
if (deviceConnected) { if ((millis() - lastTime) > timerDelay) { // Read temperature as Celsius (the default) temp = bme.readTemperature(); // Fahrenheit tempF = temp*1.8 +32; // Read humidity hum = bme.readHumidity();If you’re using temperature in Celsius it runs the following code section. First, it converts the temperature to a char variable (temperatureCTemp variable). We must convert the temperature to a char variable type to use it in the setValue() function.
static char temperatureCTemp[6]; dtostrf(temp, 6, 2, temperatureCTemp);Then, it sets the bmeTemperatureCelsiusCharacteristic value to the new temperature value (temperatureCTemp) using the setValue() function. After settings the new value, we can notify the connected client using the notify() function.
//Set temperature Characteristic value and notify connected client bmeTemperatureCelsiusCharacteristics.setValue(temperatureCTemp); bmeTemperatureCelsiusCharacteristics.notify();We follow a similar procedure for the Temperature in Fahrenheit.
#else static char temperatureFTemp[6]; dtostrf(f, 6, 2, temperatureFTemp); //Set temperature Characteristic value and notify connected client bmeTemperatureFahrenheitCharacteristics.setValue(tempF); bmeTemperatureFahrenheitCharacteristics.notify(); Serial.print("Temperature Fahrenheit: "); Serial.print(tempF); Serial.print(" *F"); #endifSending the humidity also uses the same process.
//Notify humidity reading from DHT static char humidityTemp[6]; dtostrf(hum, 6, 2, humidityTemp); //Set humidity Characteristic value and notify connected client bmeHumidityCharacteristics.setValue(humidityTemp); bmeHumidityCharacteristics.notify(); Serial.print(" - Humidity: "); Serial.print(hum); Serial.println(" %");Testing the ESP32 BLE Server
Upload the code to your board and then, open the Serial Monitor. It will display a message as shown below.

Then, you can test if the BLE server is working as expected by using a BLE scan application on your smartphone like nRF Connect. This application is available for Android and iOS.
After installing the application, enable Bluetooth on your smartphone. Open the nRF Connect app and click on the Scan button. It will find all Bluetooth nearby devices, including your BME280_ESP32 device (it is the BLE server name you defined on the code).

Connect to your BME280_ESP32 device and then, select the client tab (the interface might be slightly different). You can check that it advertises the service with the UUID we defined in the code, as well as the temperature and humidity characteristics. Notice that those characteristics have the Notify property.

Your ESP32 BLE Server is ready!
Go to the next section to create an ESP32 client that connects to the server to get access to the temperature and humidity characteristics and get the readings to display them on an OLED display.
2) ESP32 BLE Client
In this section, we’ll create the ESP32 BLE client that will establish a connection with the ESP32 BLE server, and display the readings on an OLED display.
Schematic
The ESP32 BLE client is connected to an OLED display. The display shows the readings received via Bluetooth.
Wire your OLED display to the ESP32 by following the next schematic diagram. The SCL pin connects to GPIO 22 and the SDA pin to GPIO 21.

Installing the SSD1306, GFX and BusIO Libraries
You need to install the following libraries to interface with the OLED display:
- Adafruit_SSD1306 library
- Adafruit GFX library
- Adafruit BusIO library
To install the libraries, go Sketch> Include Library > Manage Libraries, and search for the libraries’ names.
Installing Libraries (VS Code + PlatformIO)
If you’re using VS Code with the PlatformIO extension, copy the following to the platformio.ini file to include the libraries.
lib_deps = adafruit/Adafruit GFX Library@^1.10.12 adafruit/Adafruit SSD1306@^2.4.6ESP32 BLE Client – Code
Copy the BLE client Sketch to your Arduino IDE or to the main.cpp file if you’re using VS Code with PlatformIO.
/********* Rui Santos Complete instructions at https://RandomNerdTutorials.com/esp32-ble-server-client/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. *********/ #include "BLEDevice.h" #include <Wire.h> #include <Adafruit_SSD1306.h> #include <Adafruit_GFX.h> //Default Temperature is in Celsius //Comment the next line for Temperature in Fahrenheit #define temperatureCelsius //BLE Server name (the other ESP32 name running the server sketch) #define bleServerName "BME280_ESP32" /* UUID's of the service, characteristic that we want to read*/ // BLE Service static BLEUUID bmeServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); // BLE Characteristics #ifdef temperatureCelsius //Temperature Celsius Characteristic static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518"); #else //Temperature Fahrenheit Characteristic static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408"); #endif // Humidity Characteristic static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99"); //Flags stating if should begin connecting and if the connection is up static boolean doConnect = false; static boolean connected = false; //Address of the peripheral device. Address will be found during scanning... static BLEAddress *pServerAddress; //Characteristicd that we want to read static BLERemoteCharacteristic* temperatureCharacteristic; static BLERemoteCharacteristic* humidityCharacteristic; //Activate notify const uint8_t notificationOn[] = {0x1, 0x0}; const uint8_t notificationOff[] = {0x0, 0x0}; #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels //Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); //Variables to store temperature and humidity char* temperatureChar; char* humidityChar; //Flags to check whether new temperature and humidity readings are available boolean newTemperature = false; boolean newHumidity = false; //Connect to the BLE Server that has the name, Service, and Characteristics bool connectToServer(BLEAddress pAddress) { BLEClient* pClient = BLEDevice::createClient(); // Connect to the remove BLE Server. pClient->connect(pAddress); Serial.println(" - Connected to server"); // Obtain a reference to the service we are after in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(bmeServiceUUID); if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(bmeServiceUUID.toString().c_str()); return (false); } // Obtain a reference to the characteristics in the service of the remote BLE server. temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID); humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID); if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) { Serial.print("Failed to find our characteristic UUID"); return false; } Serial.println(" - Found our characteristics"); //Assign callback functions for the Characteristics temperatureCharacteristic->registerForNotify(temperatureNotifyCallback); humidityCharacteristic->registerForNotify(humidityNotifyCallback); return true; } //Callback function that gets called, when another device's advertisement has been received class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { if (advertisedDevice.getName() == bleServerName) { //Check if the name of the advertiser matches advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for pServerAddress = new BLEAddress(advertisedDevice.getAddress()); //Address of advertiser is the one we need doConnect = true; //Set indicator, stating that we are ready to connect Serial.println("Device found. Connecting!"); } } }; //When the BLE Server sends a new temperature reading with the notify property static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { //store temperature value temperatureChar = (char*)pData; newTemperature = true; } //When the BLE Server sends a new humidity reading with the notify property static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { //store humidity value humidityChar = (char*)pData; newHumidity = true; Serial.print(newHumidity); } //function that prints the latest sensor readings in the OLED display void printReadings(){ display.clearDisplay(); // display temperature display.setTextSize(1); display.setCursor(0,0); display.print("Temperature: "); display.setTextSize(2); display.setCursor(0,10); display.print(temperatureChar); display.setTextSize(1); display.cp437(true); display.write(167); display.setTextSize(2); Serial.print("Temperature:"); Serial.print(temperatureChar); #ifdef temperatureCelsius //Temperature Celsius display.print("C"); Serial.print("C"); #else //Temperature Fahrenheit display.print("F"); Serial.print("F"); #endif //display humidity display.setTextSize(1); display.setCursor(0, 35); display.print("Humidity: "); display.setTextSize(2); display.setCursor(0, 45); display.print(humidityChar); display.print("%"); display.display(); Serial.print(" Humidity:"); Serial.print(humidityChar); Serial.println("%"); } void setup() { //OLED display setup // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE,0); display.setCursor(0,25); display.print("BLE Client"); display.display(); //Start serial communication Serial.begin(115200); Serial.println("Starting Arduino BLE Client application..."); //Init BLE device BLEDevice::init(""); // 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 30 seconds. BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->start(30); } 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(*pServerAddress)) { Serial.println("We are now connected to the BLE Server."); //Activate the Notify property of each Characteristic temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); connected = true; } else { Serial.println("We have failed to connect to the server; Restart your device to scan for nearby BLE server again."); } doConnect = false; } //if new temperature readings are available, print in the OLED if (newTemperature && newHumidity){ newTemperature = false; newHumidity = false; printReadings(); } delay(1000); // Delay a second between loops. }View raw code
Continue reading to learn how the code works or skip to the Demonstration section.
Importing libraries
You start by importing the required libraries:
#include "BLEDevice.h" #include <Wire.h> #include <Adafruit_SSD1306.h> #include <Adafruit_GFX.h>Choosing temperature unit
By default the client will receive the temperature in Celsius degrees, if you comment the following line or delete it, it will start receiving the temperature in Fahrenheit degrees.
//Default Temperature is in Celsius //Comment the next line for Temperature in Fahrenheit #define temperatureCelsiusBLE Server Name and UUIDs
Then, define the BLE server name that we want to connect to and the service and characteristic UUIDs that we want to read. Leave the default BLE server name and UUIDs to match the ones defined in the server sketch.
//BLE Server name (the other ESP32 name running the server sketch) #define bleServerName "BME280_ESP32" /* UUID's of the service, characteristic that we want to read*/ // BLE Service static BLEUUID bmeServiceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); // BLE Characteristics #ifdef temperatureCelsius //Temperature Celsius Characteristic static BLEUUID temperatureCharacteristicUUID("cba1d466-344c-4be3-ab3f-189f80dd7518"); #else //Temperature Fahrenheit Characteristic static BLEUUID temperatureCharacteristicUUID("f78ebbff-c8b7-4107-93de-889a6a06d408"); #endif // Humidity Characteristic static BLEUUID humidityCharacteristicUUID("ca73b3ba-39f6-4ab3-91ae-186dc9577d99");Declaring variables
Then, you need to declare some variables that will be used later with Bluetooth to check whether we’re connected to the server or not.
//Flags stating if should begin connecting and if the connection is up static boolean doConnect = false; static boolean connected = false;Create a variable of type BLEAddress that refers to the address of the server we want to connect. This address will be found during scanning.
//Address of the peripheral device. Address will be found during scanning... static BLEAddress *pServerAddress;Set the characteristics we want to read (temperature and humidity).
//Characteristicd that we want to read static BLERemoteCharacteristic* temperatureCharacteristic; static BLERemoteCharacteristic* humidityCharacteristic;OLED Display
You also need to declare some variables to work with the OLED. Define the OLED width and height:
#define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixelsInstantiate the OLED display with the width and height defined earlier.
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);Temperature and Humidity Variables
Define char variables to hold the temperature and humidity values received by the server.
//Variables to store temperature and humidity char* temperatureChar; char* humidityChar;The following variables are used to check whether new temperature and humidity readings are available and if it is time to update the OLED display.
//Flags to check whether new temperature and humidity readings are available boolean newTemperature = false; boolean newHumidity = false;printReadings()
We created a function called printReadings() that displays the temperature and humidity readings on the OLED display.
void printReadings(){ display.clearDisplay(); // display temperature display.setTextSize(1); display.setCursor(0,0); display.print("Temperature: "); display.setTextSize(2); display.setCursor(0,10); display.print(temperatureChar); display.print(" "); display.setTextSize(1); display.cp437(true); display.write(167); display.setTextSize(2); Serial.print("Temperature:"); Serial.print(temperatureChar); #ifdef temperatureCelsius //Temperature Celsius display.print("C"); Serial.print("C"); #else //Temperature Fahrenheit display.print("F"); Serial.print("F"); #endif //display humidity display.setTextSize(1); display.setCursor(0, 35); display.print("Humidity: "); display.setTextSize(2); display.setCursor(0, 45); display.print(humidityChar); display.print("%"); display.display(); Serial.print(" Humidity:"); Serial.print(humidityChar); Serial.println("%"); }Recommended reading: ESP32 OLED Display with Arduino IDE
setup()
In the setup(), start the OLED display.
//OLED display setup // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever }Then, print a message in the first line saying “BME SENSOR”.
display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE,0); display.setCursor(0,25); display.print("BLE Client"); display.display();Start the serial communication at a baud rate of 115200.
Serial.begin(115200);And initialize the BLE device.
//Init BLE device BLEDevice::init("");Scan nearby devices
The following methods scan for nearby devices.
// 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 30 seconds. BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setActiveScan(true); pBLEScan->start(30);MyAdvertisedDeviceCallbacks() function
Note that the MyAdvertisedDeviceCallbacks() function, upon finding a BLE device, checks if the device found has the right BLE server name. If it has, it stops the scan and changes the doConnect boolean variable to true. This way we know that we found the server we’re looking for, and we can start establishing a connection.
//Callback function that gets called, when another device's advertisement has been received class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { if (advertisedDevice.getName() == bleServerName) { //Check if the name of the advertiser matches advertisedDevice.getScan()->stop(); //Scan can be stopped, we found what we are looking for pServerAddress = new BLEAddress(advertisedDevice.getAddress()); //Address of advertiser is the one we need doConnect = true; //Set indicator, stating that we are ready to connect Serial.println("Device found. Connecting!"); } } };Connect to the server
If the doConnect variable is true, it tries to connect to the BLE server. The connectToServer() function handles the connection between the client and the server.
//Connect to the BLE Server that has the name, Service, and Characteristics bool connectToServer(BLEAddress pAddress) { BLEClient* pClient = BLEDevice::createClient(); // Connect to the remove BLE Server. pClient->connect(pAddress); Serial.println(" - Connected to server"); // Obtain a reference to the service we are after in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(bmeServiceUUID); if (pRemoteService == nullptr) { Serial.print("Failed to find our service UUID: "); Serial.println(bmeServiceUUID.toString().c_str()); return (false); } // Obtain a reference to the characteristics in the service of the remote BLE server. temperatureCharacteristic = pRemoteService->getCharacteristic(temperatureCharacteristicUUID); humidityCharacteristic = pRemoteService->getCharacteristic(humidityCharacteristicUUID); if (temperatureCharacteristic == nullptr || humidityCharacteristic == nullptr) { Serial.print("Failed to find our characteristic UUID"); return false; } Serial.println(" - Found our characteristics"); //Assign callback functions for the Characteristics temperatureCharacteristic->registerForNotify(temperatureNotifyCallback); humidityCharacteristic->registerForNotify(humidityNotifyCallback); return true; }It also assigns a callback function responsible to handle what happens when a new value is received.
//Assign callback functions for the Characteristics temperatureCharacteristic->registerForNotify(temperatureNotifyCallback); humidityCharacteristic->registerForNotify(humidityNotifyCallback);After the BLE client is connected to the server, you need to active the notify property for each characteristic. For that, use the writeValue() method on the descriptor.
if (connectToServer(*pServerAddress)) { Serial.println("We are now connected to the BLE Server."); //Activate the Notify property of each Characteristic temperatureCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); humidityCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true);Notify new values
When the client receives a new notify value, it will call these two functions: temperatureNotifyCallback() and humidityNotifyCallback() that are responsible for retrieving the new value, update the OLED with the new readings and print them on the Serial Monitor.
//When the BLE Server sends a new temperature reading with the notify property static void temperatureNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { //store temperature value temperatureChar = (char*)pData; newTemperature = true; } //When the BLE Server sends a new humidity reading with the notify property static void humidityNotifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { //store humidity value humidityChar = (char*)pData; newHumidity = true; Serial.print(newHumidity); }These two previous functions are executed every time the BLE server notifies the client with a new value, which happens every 30 seconds. These functions save the values received on the temperatureChar and humidityChar variables. These also change the newTemperature and newHumidity variables to true, so that we know we’ve received new readings.
Display new temperature and humidity readings
In the loop(), there is an if statement that checks if new readings are available. If there are new readings, we se the newTemperature and newHumidity variables to false, so that we are able to receive new readings later on. Then, we call the printReadings() function to display the readings on the OLED.
//if new temperature readings are available, print in the OLED if (newTemperature && newHumidity){ newTemperature = false; newHumidity = false; printReadings(); }Testing the Project
That’s it for the code. You can upload it to your ESP32 board.
Once the code is uploaded. Power the ESP32 BLE server, then power the ESP32 with the client sketch. The client starts scanning nearby devices, and when it finds the other ESP32, it establishes a Bluetooth connection. Every 30 seconds, it updates the display with the latest readings.

Important: don’t forget to disconnect your smartphone from the BLE server. Otherwise, the ESP32 BLE Client won’t be able to connect to the server.

Wrapping Up
In this tutorial, you learned how to create a BLE Server and a BLE Client with the ESP32. You learned how to set new temperature and humidity values on the BLE server characteristics. Then, other BLE devices (clients) can connect to that server and read those characteristic values to get the latest temperature and humidity values. Those characteristics have the notify property, so that the client is notified whenever there’s a new value.
Using BLE is another communication protocol you can use with the ESP32 boards besides Wi-Fi. We hope you found this tutorial useful. We have tutorials for other communication protocols that you may find useful.
- ESP32 Bluetooth Classic with Arduino IDE – Getting Started
- ESP32 Useful Wi-Fi Library Functions (Arduino IDE)
- ESP-MESH with ESP32 and ESP8266: Getting Started (painlessMesh library)
- Getting Started with ESP-NOW (ESP32 with Arduino IDE)
Learn more about the ESP32 with our resources:
- Learn ESP32 with Arduino IDE (eBook + Video Course)
- More ESP32 projects and tutorials…
Tag » Arduino Ble Client Example
-
ESP32 Bluetooth Low Energy (BLE) On Arduino IDE
-
Arduino-esp32/BLE_o At Master - GitHub
-
ESP32 Bluetooth Low Energy (BLE) Using Arduino IDE
-
ESP32 BLE Server Client Communication Using Arduino IDE
-
ESP32 Bluetooth Low Energy (BLE) On Arduino IDE [Tutorial]
-
ESP32 BLE Client – Connecting To Fitness Band To Trigger A Bulb
-
ESP32 BLE Arduino - Arduino Reference
-
ArduinoBLE - Arduino Reference
-
How To Use BLE In ESP32? ESP32 BLE (Bluetooth ... - Electronics Hub
-
[PDF] How To Use BLE In ESP32? ESP32 BLE (Bluetooth ... - Energia Zero
-
Client And Server Introduction - Seeed Wiki
-
Easiest ESP32 BLE (Bluetooth Low Energy) Tutorial | Arduino
-
ESP32 Technical Tutorials: BLE Client - YouTube
-
ESP32 | BLE TO BLE | SERVER - CLIENT - YouTube