ESP32 BLE Server and Client (Bluetooth Low Energy)

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.

ESP32 BLE Server and Client BLuetooth Low Energy Arduino IDE

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.

BLE Client Server Server Advertising

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.

GATT Hierarchy ESP32 BLE Server Client Example
  • 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-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.

For a more detailed introduction about BLE, read our getting started guide:

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.

ESP32 BLE Client Server OLED Display Demonstration

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:

Parts Required

Here’s a list of the parts required to follow this project:

ESP32 BLE Server:

ESP32 BLE Client:

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.

ESP32 BLE Server Connected to the nRF Connect App

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.

ESP32 Wiring Circuit to BME280 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.

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.2  

ESP32 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 temperatureCelsius

BLE 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; // I2C

The 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());
#endif  

After 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");
#endif

Sending 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.

ESP32 BLE Server Starts Serial Monitor

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).

ESP32 BLE Server Scanner App

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.

ESP32 BLE Server Characteristics nRF Connect App

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.

ESP32 Wiring Circuit to OLED SSD1306 Schematic Diagram

Installing the SSD1306, GFX and BusIO Libraries

You need to install the following libraries to interface with the OLED display:

To install the libraries, go SketchInclude 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.6

ESP32 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 temperatureCelsius

BLE 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 pixels

Instantiate 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.

ESP32 BLE Client Server OLED Display Demonstration

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.

ESP32 BLE Client Connected to ESP32 BLE Server Serial Monitor

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.

Learn more about the ESP32 with our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

72 thoughts on “ESP32 BLE Server and Client (Bluetooth Low Energy)”

      • I’m afraid I noticed a couple of other typos in the server sketch. You apparently switched from dht to bme sensors but missed one reference in line 93:
        bmeService->addCharacteristic(&dhtTemperatureFahrenheitCharacteristics);

        You also created new descriptors when, I’m sure, you intended to point to the ones created earlier. Lines 95 and 101:
        bmeTemperatureFahrenheitCharacteristics.addDescriptor(new BLE2902());
        bmeHumidityCharacteristics.addDescriptor(new BLE2902());
        should be:
        bmeTemperatureCharacteristics.addDescriptor(&bmeTemperatureDescriptor);
        bmeHumidityCharacteristics.addDescriptor(&bmeHumidityDescriptor);

        Thanks for all the work you do. You are always the first source I go to when I have a problem with ESP microcontrollers.

        Reply
        • Hi.
          You’re right. Thanks for pointing that out.
          I’ll fix it soon.
          I guess I need a vacation. I should have seen that.
          Regards,
          Sara

          Reply
          • Don’t feel too bad. When I looked for more documentation, every example I found created a new Descriptor. None explained how to modify that Descriptor after it was created. I applaud you for putting together such a good sketch when documentation is apparently so hard to find. That is why I always go to RNT first.

          • Hello Sara and Allen (R Mulvey) too,

            When I correct here in the server code this line adding the humidity descriptor as suggested, my client will crash at receiving data from the server.

            Here the lines:

            // Humidity
            bmeService->addCharacteristic(&bmeHumidityCharacteristics);
            bmeHumidityDescriptor.setValue(“BME humidity”);
            //bmeHumidityCharacteristics.addDescriptor(new BLE2902()); // error?
            // zie: https://randomnerdtutorials.com/esp32-ble-server-client/#comment-698103
            bmeHumidityCharacteristics.addDescriptor(&bmeHumidityDescriptor); // now the client will crash!

            So I came on the idea to change the definition of the humidity descriptor more in the beginning of this server sketch:

            // Humidity Characteristic and Descriptor
            BLECharacteristic bmeHumidityCharacteristics(“ca73b3ba-39f6-4ab3-91ae-186dc9577d99”, BLECharacteristic::PROPERTY_NOTIFY);
            //BLEDescriptor bmeHumidityDescriptor(BLEUUID((uint16_t)0x2903)); // crashes when used in corrected code below
            BLEDescriptor bmeHumidityDescriptor(BLEUUID((uint16_t)0x2902)); // does NOT crash when used in corrected code below

            I don’t know why but that helps! No crashes at at the client.

            But still I have a small but nasty problem at my client side. Once in a while the value for temperature is incorrect and is, in such a case, always exact the same value as the transferred value for the humidity. So I see equal values on the printed line (serial monitor).

            Here you can see this probleem in a screenshot:
            https://share.cleanshot.com/4blHZ2VJ
            At right the data as sent by the server – at left what is printed by the client. Maybe the bug is at client side.

            I hoped to solve this problem with your good suggestion, Allen.
            I hope you can help me, Sara.

            For the rest I’m very happy with your excellent examples BLE-coding, as I’m a novice in this area.

            Kind regards,

            Jop

      • Hey Sara,
        Could you tell me how to stop hexdump.
        If you look in the value field of a characteristic in the nrf connect app, it shows you the hexdump of the actual data followed by the actual data.
        How to stop that?
        I am reading data from rfid tag and sending over ble to nrf connect app but due to the hexdump only half of the data is being shown.

        Reply
  1. Hey.
    Can you explain this rule to me in more detail?
    BLECharacteristic bmeTemperatureCelsiusCharacteristics(“cba1d466-344c-4be3-ab3f-189f80dd7518”, BLECharacteristic::PROPERTY_NOTIFY);

    I almost understand how everything works but where does that long number come from or what does it do.
    Greetings old man Bert

    Reply
    • Hi.
      Basically, you need to set UUIDs for your characteristics to identify them. You can create UUIDs (use the UUID generator website: https://www.uuidgenerator.net/) or use predefined UUIDs (https://www.bluetooth.com/specifications/assigned-numbers/).

      The UUIDs are used to identify the characteristics. There are predefined UUIDs for the most common characteristics used by BLE devices, for example the Battery Level has a default UUID. This is useful because other devices that connect, know exactly what to search for to get the information they want.

      We also define that we want the property of that characteristic to be notify. The property defines how the client can interact with those characteristics, it can be read, write, notify, and others.

      I hope this is clear.

      Regards,
      Sara

      Reply
      • Hey.
        Very clear and understandable.
        Sometimes there is a translation problem because I can’t write english.
        Certain concepts are then not clear.
        You explain everything you do very well and it is very instructive.
        I thank you for that.
        I’m going to work on it.
        Greetings Old man Bert

        Reply
          • You once had a wish list.
            If you still have it I’d love to
            Esp32 Web server Hosting files from MicroSd Card.
            Turns into an Esp32 captive portal Hosting Files from MicroSd Card.
            But I have no idea if that’s possible.
            I do have a version here that does that without SDcart, but that is very inconvenient if you want to change the html.

  2. I found a way to make the client much more stable if the connection is broken and reconnected.

    Make pClient global by moving the following statement to the top area outside all loops and functions:

    BLEClient* pClient = BLEDevice::createClient();

    Then add this to the top of the main loop:

    bool status = pClient->isConnected(); // check BLE connection
    if (!status) {
    doConnect = true;
    temperatureChar[0] = 0; humidityChar[0] = 0; // purge stale data
    Serial.println(“Reconnecting BLE…”);
    printReadings(); // redraw display so it does not display stale data
    }

    It will then connect and reconnect as necessary.

    Reply
  3. Thanks RNT crew,
    … for doing the BLE thing for esp32… and everything else.!

    I am trying to incorporate [AsyncElegantOTA]
    and am confused as to the order of the [#includes].
    — I am only guessing that there is a correct “pecking order” ! ?

    Is there a location where this issue is documented?

    Currently I have:
    #include <BLEDevice.h>
    #include <BLEServer.h>
    #include <BLEUtils.h>
    #include <BLE2902.h>
    #include <Wire.h>

    #include <Arduino.h>
    #include “WiFi.h”
    #include “AsyncTCP.h”
    #include “ESPAsyncWebServer.h”
    #include “AsyncElegantOTA.h”

    #include <Adafruit_Sensor.h>
    #include <Adafruit_BME280.h>

    But haven’t even tested that yet.
    …before I proceed only to find headaches that I can’t identify the cause of.

    I would appreciate someone with more experience would mind checking that and advise whether OK. If not, please re-order.

    Any other pitfalls of trying to do OTA with BLE.
    My server is going outside to a hard-to-reach spot.

    TIA

    Reply
  4. Maybe this will be useful for someone.
    Use NimBLEDevice.h instead BLEDevice.h for ESP32BLE !!!.
    The BLEDevice.h eating too much memory, and if you will use wifi & BLE – the free memory will be dramatically low.
    Found NimBLEDevice.h library,
    Its use up to 44% less memory, compared to BLEDevice.h !!!

    Reply
  5. Hello Sara!

    I’m using your client example to get info from a pressure sensor. I don’t why I can really connect to my sensor that act like a server (tested via the LightBlue application in Android)
    My console output look like this:

    Starting Arduino BLE Client application…
    Device found. Connecting!
    Setup done
    [E][BLEClient.cpp:238] gattClientEventHandler(): Failed to connect, status=Unknown ESP_ERR error
    – Connected to server

    Any idea why?

    Thanks if you have any idea 🙂

    Reply
  6. Dear Sara,

    Only the temperature and humidity value updates shown on the serial monitor. No any other details related to communication between both ESP32 modules shown on the serial monitor(Both server and client are same).
    What can be the reason?

    Thanks,
    Indika

    Reply
    • If you want i can read your code and look that , did you pay attention to the specific address of the sensor in its datasheet?

      Reply
  7. ESP BLE client
    Est il possible que le client se déconnecte du serveur après avoir reçu ses informations, pour laisser la place à un autre client
    Merci pour votre réponse

    Cordialement
    Serge

    Reply
  8. Hi Sara, Rui,

    I am wondering if I could persuade you wonderful folk to have a look into [NimBLE-Arduino] by h2zero.

    The advantages of NimBLE-Arduino over the Standard model BLE are huge in terms of RAM usage.
    — This is even acknowledged on on the http://www.arduino.cc site.

    For my tired old brain, it’s too much to get my head around and my project is now stretching the limits such that I am now using esp32-WROVER-IE.

    some links:
    https://www.arduino.cc/reference/en/libraries/nimble-arduino/
    https://github.com/h2zero/NimBLE-Arduino
    https://h2zero.github.io/esp-nimble-cpp/md__migration_guide.html#autotoc_md46

    If anyone else reading this is already into NimBLE and have a solid grasp of how to migrate, please drop a not in the RNT Labs. I could really use some help.

    In BLE, I find it difficult to determine which are the library “keywords” and which are User defined.
    — The third of the above links, I’ve only just found and that is helping a lot.

    I feel sure you will be impressed by this library…
    AND widely applauded for a tutorial and/or book on this subject. I would be first in line to buy. 🙂

    TIA,
    Chris

    Reply
    • Hi.
      Thank you so much for sharing that.
      Other readers have also said great things about that library.
      I haven’t tried it yet. But, that’s on my endless to-do list.
      Thanks for your comment.
      Regards,
      Sara

      Reply
  9. Hello i want use value temperature in my client to make a condition but i can’t because it’s a pointer do you have a solution or idea for make a condition with data receive ?

    Reply
  10. Which part do i need to change and add if i want to use 2 type of sensors which is Soil Moisture sensor and DS18B20 Temperature sensor to display the soil moisture values, temperature and status if the soil is too wet or too dry on the client’s OLED

    Reply
  11. Dear Sara,
    Excelent Tutorial!
    I need to identify the device I am connected to.
    Is there a way of reading the MAC address or the Manufactorer ID of the device that is connected to my ESP32 BLE?
    Thank you and regards

    Reply
  12. Great tutorial! Thanks.

    I have experienced an issue that after a successful connect, disconnect I could not connect to the device again. I had to restart advertising after disconnect like so:

    //Setup callbacks onConnect and onDisconnect
    class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    };
    void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    pServer->startAdvertising(); // restart advertising after disconnecting
    }
    };

    Now it works like charm.
    Link to where I have found this solution: https://github.com/espressif/arduino-esp32/issues/6016

    Reply
  13. Hi I hope you can help me i get a
    error: ‘temperatureNotifyCallback’ was not declared in this scope
    message when compiling ESP32 BLE Client – Code

    Reply
    • Hi.
      Move the following function in the code so that it comes before the connectToServer() function.
      //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;
      }

      Reply
  14. Hi, amazing tutorial. I have a question though, perhaps you can point me in the right direction. I would like to initialize the ESP32 either as a server or a client at power on. Is that even possible? I cannot find this answer anywhere. Perhaps I have been looking in the wrong places 😉
    Thanks in advance, and i do apologize if my question is a bit off topic.

    Reply
  15. Dear Sara,
    Excellent Tutorial! Keep going!
    I need to connect to several GATT Servers devices around my house and I would like to get all the information using a single GATT Central device. How could implement it ? Any advise or snippet?

    Reply
  16. Hey Sara,

    I am trying to connect to the server (BME280+ESP32) using my phone app (Serial Bluetooth terminal), and failed. it says “no serial profile found”. I am new to the BLE story. It would be nice to receive BME outputs from a phone with just one ESP32. Thanks

    Reply
      • got it working with some revisions. in case others might be interested (it also allows your phone as a display for monitoring and debugging) …

        #include <BLEDevice.h>
        #include <BLEServer.h>
        #include <BLEUtils.h>
        #include <BLE2902.h>
        #include <Wire.h>
        #include <Adafruit_Sensor.h>
        #include <Adafruit_BME280.h>

        BLEServer *pServer = NULL;
        BLECharacteristic * pTxCharacteristic;
        bool deviceConnected = false;
        bool oldDeviceConnected = false;
        uint8_t txValue = 0;
        char txBuffer[100];
        String txString = “BOM”;

        //BLE server name
        #define bleServerName “ESP32_BME280”

        Adafruit_BME280 bme; // I2C

        float temp;
        float tempF;
        float hum;

        // 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”

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

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

        };

        class MyCallbacks: public BLECharacteristicCallbacks {
        void onWrite(BLECharacteristic *pCharacteristic) {
        std::string rxValue = pCharacteristic->getValue();

        if (rxValue.length() > 0) {
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)
        Serial.print(rxValue[i]);

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

        };

        void initBME(){
        if (!bme.begin(0x76)) {
        Serial.println(“Could not find a valid BME280 sensor, check wiring!”);
        while (1);
        }
        }

        void setup() {
        Serial.begin(115200);

        // Init BME Sensor
        initBME();

        // Create the BLE Device
        BLEDevice::init(“ESP_B_UART”);

        // Create the BLE Server
        pServer = BLEDevice::createServer();
        pServer->setCallbacks(new MyServerCallbacks());

        // Create the BLE Service
        BLEService *pService = pServer->createService(SERVICE_UUID);

        // Create a BLE Characteristic
        pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX,BLECharacteristic::PROPERTY_NOTIFY);

        // BLE2902 needed to notify
        pTxCharacteristic->addDescriptor(new BLE2902());

        BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX,BLECharacteristic::PROPERTY_WRITE);

        pRxCharacteristic->setCallbacks(new MyCallbacks());

        // Start the service
        pService->start();

        // Start advertising
        pServer->getAdvertising()->addServiceUUID(pService->getUUID());
        pServer->getAdvertising()->start();
        Serial.println(“Waiting a client connection to notify…”);
        }

        void loop() {
        // 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;
        }

        // when a client is connected, sending data
        if (deviceConnected) {

        // Read temperature as Celsius (the default)
        temp = bme.readTemperature();
        // Fahrenheit
        tempF = 1.8*temp + 32;
        // Read humidity
        hum = bme.readHumidity();

        txString = String(String(temp) + "C " + String(tempF) + "F " + String(hum) + "%\n");

        txString.toCharArray(txBuffer,txString.length()+1);
        pTxCharacteristic->setValue((unsigned char*)txBuffer,txString.length());
        pTxCharacteristic->notify();

        Serial.print("BME280 (TempC TempF Humidity%): ");
        Serial.print(txString);

        delay(2000); // bluetooth stack will go into congestion, if too many packets are sent
        }

        }

        Reply
  17. Good day!
    I would like to ask for advice on the following error when trying to exchange data via bluetooth:
    lld_pdu_get_tx_flush_nb HCI packet count mismatch (0, 1)
    How can it be avoided?
    Thank you in advance
    Regards,
    Bulat

    Reply
  18. Thanks for your work, Sarah.
    Can you advise me how to get rid of the lld_pdu_get_tx_flush_nb HCI packet count mismatch (0, 1) error?

    Reply
  19. Hi,

    I wonder if I could connect the client ESP32 to a PC, and use the serial monitor in Arduino IDE to transfer some numbers to the server ESP32. This is just like entering a command to the remote server ESP32. Am I able to do that? Thank you very much.

    Reply
  20. Hi, I added this to an existing project. My device that sends data to the other one, the recieving device has a bunch of gibberish in the serial monitor for the values, not the normal text or print statements. Is this a data type conversion issue?

    Reply
  21. Hi Rui / Sara,
    I need to send a temperature value from one ESP32 to another using Bluetooth. I reviewed your project for this using BLE. The problem I have is with the connecting. I have to push the reset button on both boards in order for it to connect. If the connection is lost, I again have to reset both boards to get them connected again.
    Can the program be changed so that if a connection is lost then both boards will go into a mode to automatically reconnect? Having to reset both boards will be a problem.
    I can receive the data on my phone once I connect with the BT Terminal app without having to restart the master.
    Any thoughts? Can this also be done with Bluetooth Classic?
    Thank you
    Mike Oshinski

    Reply
  22. Hi, and thank you. I keep getting this error when I fire the client:
    Failed to find our characteristic UUID
    I restarted the whole setup various times. Is there something I should be looking into?

    Reply
  23. I’ve just realized that it had to do with my comment to get temp in Fahrenheit in line 18 of the server
    //Default Temperature is in Celsius
    //Comment the next line for Temperature in Fahrenheit
    #define temperatureCelsius

    Do I need to modify something else?
    and thank you again for all the help you bring

    Reply
  24. Hi Sara, hi Rui first a happy new Year.
    I want to receive the data from an HLK-LD2410C_96C7
    The BLE Scanner show me this data :
    Advertised Device: Name: HLK-LD2410_96C7,
    Address: a7:91:76:49:96:c7,
    manufacturer data: d60508004a4c414953444b,
    serviceUUID: 0000af30-0000-1000-8000-00805f9b34fb, rssi: -98
    Is it possible to connect an ESP32 to the HLK-LD2410C device ?
    Greatings John

    Reply
  25. These sketches have been great to bet me started, but I’ve hit a bump.
    If you reboot both devices at the same time, everything is good. However, they don’t reconnect if either one of them is reset. I have to reset both to get them going again.

    I was able to edit the Server sketch so that it will reconnect to my phone app automatically (basically just rerunning the Advertising portion of the sketch if deviceConnected becomes false.) That works great, but I can’t figure out how to do the same on the client sketch.

    Can you tell me what code needs to be rerun on the client to make it reconnect on it’s own?

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.