ESP32 BLE Peripheral (Server): Environmental Sensing Service

In this guide, you’ll learn how to set up the ESP32 as a BLE Peripheral (or BLE Server) with an Environmental Sensing Service. This service exposes measurement data from environmental sensors and supports a wide range of environmental parameters like temperature, humidity, pressure, and others. As an example, we’ll use the measurements from a BME280 sensor, but this can be applied to any other sensor. This is a great tutorial to help you understand the BLE protocol.

ESP32 BLE Peripheral (Server): Environmental Sensing Service

New to the ESP32? Start here: Getting Started with the ESP32 Development Board.

Table of Contents:

Introducing Bluetooth Low Energy (BLE)

The ESP32 comes not only with Wi-Fi but also with Bluetooth and Bluetooth Low Energy (BLE).

Bluetooth Low Energy

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 which is always on, BLE remains in sleep mode constantly except for when a connection is initiated.

This makes it consume very little 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 (also called peripheral) 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 and this is the communication mode we’ll use with the ESP32.

BLE Client Server Server Advertising

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 structure
  • 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: describes how the characteristic value can be interacted with. For example: read, write, notify, broadcast, indicate, etc.

For a more in-depth introduction to BLE with the ESP32, read the following guide: Getting Started with ESP32 Bluetooth Low Energy (BLE) on Arduino IDE

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 default UUIDs for all types, services, and profiles specified in the SIG (Bluetooth Special Interest Group). We’ll use the default UUIDs for the Environmental Sensing Service, and for the temperature, humidity, and pressure characteristics.

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.

Project Overview

In our example, we’ll create an Environmental Sensing Service with three characteristics. One for the temperature, another for the humidity, and another for the pressure.

The actual temperature, humidity, and pressure 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.

ESP32 BLE Server: Environmental Sensing Service Structure

We’re going to use the default UUIDs for the Environmental Sensing Profile and corresponding characteristics.

If you go to this page and open the Assigned Numbers Document (PDF), you’ll find all the default assigned UUID numbers. If you search for the Environmental Sensing Service, you’ll find all the permitted characteristics that you can use with that service. You can see that it supports, temperature, humidity, and pressure.

Environmental Sensing Service Supported Characteristics

There’s a table with the UUIDs for all services. You can see that the UUID for the Environmental Sensing service is 0x181A.

Environmental Sensing Service UUI

Then, search for the temperature, humidity, and pressure characteristics UUIDs. You’ll find a table with the values for all characteristics. The UUIDs for the temperature, humidity, and pressure are:

  • pressure: 0x2A6D
  • temperature: 0x2A6E
  • humidity: 0x246F
Pressure, Temperature and Humidity characteristics default shortened UUIDs

Preparing your Smartphone

To check if the ESP32 BLE Server was created properly and receive temperature, humidity, and pressure notifications, we’ll use an app on the smartphone.

Most modern smartphones should have BLE capabilities. You can search for your smartphone specifications to check if it has BLE or not.

Note: the smartphone can act as a client or as a server. In this case, it will be the client that connects to the ESP32 BLE server.

For our tests, we’ll be using a free app called nRF Connect for Mobile from Nordic. It works on Android (Google Play Store) and iOS (App Store). Go to Google Play Store or App Store, search for “nRF Connect for Mobile” and install the app.

nRF Connect App

Parts Required

For this tutorial, you’ll need the following parts:

For this example, we’ll use a BME280 sensor, but you can easily modify the code to use any other sensor you’re familiar with.

Building the Circuit

For this particular example, we’ll use a BME280 sensor. So, you need to wire a BME280 sensor to your ESP32. You can also use any other sensor you’re familiar with.

Schematic Diagram

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 to BME280 Schematic Diagram

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

ESP32 – Creating an Environmental Sensing BLE Service

We’ll program the ESP32 using Arduino IDE. So, you need to have the ESP32 boards installed on the IDE. Follow the next tutorial if you haven’t already:

Here are the steps to create an ESP32 BLE peripheral with an Environmental Sensing BLE service with temperature, humidity, and pressure, characteristics:

  1. Create a BLE device (server) with a name of your choice (we’ll call it ESP32_BME2820, but you can call it any other name).
  2. Create an Environmental Sensing service (UUID: 0x181A).
  3. Add characteristics to that service:
    • pressure: 0x2A6D
    • temperature: 0x2A6E
    • humidity: 0x246F
  4. Add descriptors to the characteristics.
  5. Start the BLE server.
  6. Start advertising so BLE clients can connect and read the characteristics.
  7. Once a connection is established with a client, it will write new values on the characteristics and will notify the client, every time there’s a change.

Copy the following code to the Arduino IDE and upload it to your board.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-ble-server-environmental-sensing-service/
  
  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 <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

//BLE server name
#define bleServerName "ESP32_BME280"

// Default UUID for Environmental Sensing Service
// https://www.bluetooth.com/specifications/assigned-numbers/
#define SERVICE_UUID (BLEUUID((uint16_t)0x181A))

// Temperature Characteristic and Descriptor (default UUID)
// Check the default UUIDs here: https://www.bluetooth.com/specifications/assigned-numbers/
BLECharacteristic temperatureCharacteristic(BLEUUID((uint16_t)0x2A6E), BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor temperatureDescriptor(BLEUUID((uint16_t)0x2902));

// Humidity Characteristic and Descriptor (default UUID)
BLECharacteristic humidityCharacteristic(BLEUUID((uint16_t)0x2A6F), BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor humidityDescriptor(BLEUUID((uint16_t)0x2902));

// Pressure Characteristic and Descriptor (default UUID)
BLECharacteristic pressureCharacteristic(BLEUUID((uint16_t)0x2A6D), BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor pressureDescriptor(BLEUUID((uint16_t)0x2902));

// Create a sensor object
Adafruit_BME280 bme;

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

bool deviceConnected = false;

//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    Serial.println("Device Connected");
  };
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    Serial.println("Device Disconnected");
  }
};

void setup() {
  // Start serial communication 
  Serial.begin(115200);

  // Start 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 corresponding Descriptors
  bmeService->addCharacteristic(&temperatureCharacteristic);
  temperatureCharacteristic.addDescriptor(&temperatureDescriptor);
  
  bmeService->addCharacteristic(&humidityCharacteristic);
  humidityCharacteristic.addDescriptor(&humidityDescriptor);

  bmeService->addCharacteristic(&pressureCharacteristic);
  pressureCharacteristic.addDescriptor(&pressureDescriptor);
  
  // Start the service
  bmeService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
  if (deviceConnected) {
    // Read temperature as Celsius (the default)
    float t = bme.readTemperature();
    // Read humidity
    float h = bme.readHumidity();
    // Read pressure
    float p = bme.readPressure()/100.0F;
    
    //Notify temperature reading
    uint16_t temperature = (uint16_t)t;
    //Set temperature Characteristic value and notify connected client
    temperatureCharacteristic.setValue(temperature);
    temperatureCharacteristic.notify();
    Serial.print("Temperature Celsius: ");
    Serial.print(t);
    Serial.println(" ºC");
   
    //Notify humidity reading
    uint16_t humidity = (uint16_t)h;
    //Set humidity Characteristic value and notify connected client
    humidityCharacteristic.setValue(humidity);
    humidityCharacteristic.notify();   
    Serial.print("Humidity: ");
    Serial.print(h);
    Serial.println(" %");

    //Notify pressure reading
    uint16_t pressure = (uint16_t)p;
    //Set humidity Characteristic value and notify connected client
    pressureCharacteristic.setValue(pressure);
    pressureCharacteristic.notify();   
    Serial.print("Pressure: ");
    Serial.print(p);
    Serial.println(" hPa");
    
    delay(10000);
  }
}

View raw code

How does the Code Work?

Continue reading to learn how the code works or skip to the Demonstration section.

Importing libraries

You start by importing the required libraries: the libraries to use BLE and the libraries to interface with the BME280 sensor.

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

BLE server name

In the following line, you can define the name of your BLE device. We’ll call it ESP32_BME2820, but you can call it any other name.

//BLE server name
#define bleServerName "ESP32_BME280"

Bluetooth UUIDs

We’re using default UUIDs for services and characteristics already defined in the SIG (Bluetooth Special Interest Group). To report temperature, humidity, and pressure there’s a service called Environmental Sensing that supports temperature, humidity, and pressure characteristics as we’ve seen previously.

You can find all the default UUIDs for profiles and characteristics in the following document: https://www.bluetooth.com/specifications/assigned-numbers/ .

We create an Environmental Sensing Service on the following line:

#define SERVICE_UUID (BLEUUID((uint16_t)0x181A))

Then, we create the temperature characteristic as follows:

BLECharacteristic temperatureCharacteristic(BLEUUID((uint16_t)0x2A6E), BLECharacteristic::PROPERTY_NOTIFY);

This line of code is creating a BLE characteristic named temperatureCharacteristic with a UUID of 0x2A6E (representing the “Temperature” characteristic) and configuring it to support notifications (PROPERTY_NOTIFY) – this will allow other BLE devices to subscribe to and receive notifications when the temperature value changes on the ESP32.

Then, we create a descriptor for the temperature characteristic. A descriptor provides additional information about a characteristic and how it should be accessed or interpreted. Our descriptor has the default UUID 0x2902 and it represents the “Client Characteristic Configuration” descriptor.

BLEDescriptor temperatureDescriptor(BLEUUID((uint16_t)0x2902));

The “Client Characteristic Configuration” descriptor, with UUID 0x2902, is commonly used in BLE to configure how notifications and indications are handled by the client (usually a central device like a smartphone) when it subscribes to a characteristic’s notifications.

This descriptor allows the client to configure how it wants to receive updates (e.g., notifications) from the associated characteristic, giving more control over the communication between the ESP32 and the connected BLE devices.

We proceed in a similar way to create the temperature and pressure characteristics and corresponding descriptors.

// Humidity Characteristic and Descriptor (default UUID)
BLECharacteristic humidityCharacteristic(BLEUUID((uint16_t)0x2A6F), BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor humidityDescriptor(BLEUUID((uint16_t)0x2902));

// Pressure Characteristic and Descriptor (default UUID)
BLECharacteristic pressureCharacteristic(BLEUUID((uint16_t)0x2A6D), BLECharacteristic::PROPERTY_NOTIFY);
BLEDescriptor pressureDescriptor(BLEUUID((uint16_t)0x2902));

Initialize the BME280 Sensor

The following line creates an Adafruit_BME280 object to refer to the sensor called bme.

// Create a sensor object
Adafruit_BME280 bme;

The initBME() function initializes the sensor. It will be called later in the setup().

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

Learn more about the BME280 sensor: ESP32 with BME280 Sensor using Arduino IDE (Pressure, Temperature, Humidity).

BLE Callback functions

Then, we need to set up callback functions for when the BLE device connects (onConnect) or disconnects (onDisconnect).

//Setup callbacks onConnect and onDisconnect
class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    Serial.println("Device Connected");
  };
  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    Serial.println("Device Disconnected");
  }
};

We create a boolean variable called deviceConnected that keeps track of whether a Bluetooth device is currently connected to the ESP32.

bool deviceConnected = false;

The onConnect function changes the deviceConnected variable to true.

void onConnect(BLEServer* pServer) {
  deviceConnected = true;
  Serial.println("Device Connected");
};

And the onDisconnect, changes it to false.

void onDisconnect(BLEServer* pServer) {
  deviceConnected = false;
  Serial.println("Device Disconnected");
}

setup()

In the setup(), start the serial port at a baud rate of 115200:

Serial.begin(115200);

Initialize the BME280 sensor by calling the initBME() function we created previously.

// Start 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 the callback functions.

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

Creating the BLE Service and Characteristics

Continuing with the setup(), start a BLE service with the service UUID defined earlier.

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

Then, create the temperature, humidity, and pressure BLE characteristics using the UUIDs you defined earlier and assign the corresponding descriptors.

// Create BLE Characteristics and corresponding Descriptors
bmeService->addCharacteristic(&temperatureCharacteristic);
temperatureCharacteristic.addDescriptor(&temperatureDescriptor);
  
bmeService->addCharacteristic(&humidityCharacteristic);
humidityCharacteristic.addDescriptor(&humidityDescriptor);

bmeService->addCharacteristic(&pressureCharacteristic);
pressureCharacteristic.addDescriptor(&pressureDescriptor);

Starting the Service and the Advertising

Finally, you start the service, and the server begins the advertising so other devices can find it.

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

// Start advertising
pServer->getAdvertising()->start();

loop()

The loop() function is fairly straightforward. You constantly check if the device is connected or not. If it’s connected, it reads the current temperature, humidity, and pressure.

if (deviceConnected) {
  // Read temperature as Celsius 
  float t = bme.readTemperature();
  // Read humidity
  float h = bme.readHumidity();
  // Read pressure
  float p = bme.readPressure()/100.0F;

Then, convert the readings to uint16_t format (unsigned 16-bit integer), a suitable format to use in BLE.

uint16_t temperature = (uint16_t)t;

The following two lines update the current characteristic value (using .setValue()) and send it to the connected client (using .notify()).

//Set temperature Characteristic value and notify connected client
temperatureCharacteristic.setValue(temperature);
temperatureCharacteristic.notify();

There are also three lines to print the temperature in the Serial Monitor for debugging purposes.

Serial.print("Temperature Celsius: ");
Serial.print(t);
Serial.println(" ºC");

Sending the humidity and pressure uses the same process.

//Notify humidity reading
uint16_t humidity = (uint16_t)h;
//Set humidity Characteristic value and notify connected client
humidityCharacteristic.setValue(humidity);
humidityCharacteristic.notify();   
Serial.print("Humidity: ");
Serial.print(h);
Serial.println(" %");

//Notify pressure reading
uint16_t pressure = (uint16_t)p;
//Set humidity Characteristic value and notify connected client
pressureCharacteristic.setValue(pressure);
pressureCharacteristic.notify();   
Serial.print("Pressure: ");
Serial.print(p);
Serial.println(" hPa");

The delay function waits 10 seconds between readings.

delay(10000);

Demonstration

Upload the code to your board. After uploading, open the Serial Monitor, and restart the ESP32 by pressing the RST/EN button. You should get a similar message in the Serial Monitor.

ESP32 BLE Server Advertising Serial Monitor

This means everything is working as expected and the ESP32 is waiting for a BLE client to connect.

Then, go to your smartphone, open the nRF Connect app from Nordic, and start scanning for new devices. You should find a device called ESP32_BME280—this is the BLE server name you defined earlier.

nRF Connect App Scanning Devices

Connect to it. You’ll see that it displays the Environmental Sensing service with the temperature, humidity, and pressure characteristics. Click on the arrows to activate the notifications.

ESP32 Environmental Sensing Service

Then, click on the second icon at the left to change the format. You can change to unsigned int for all characteristics. You’ll start seeing the temperature, humidity, and pressure values being reported every 10 seconds.

ESP32 Environmental Sensing Service and Characteristics

You should also get the readings on the Serial Monitor.

ESP32 Environmental Service BLE Advertising Serial Monitor

Congratulations! You’ve successfully created an ESP32 BLE Peripheral that advertises the Environmental Sensing Service. Now, you can develop an app, or program another ESP32 to interface with the ESP32 BLE device.

Wrapping Up

In this tutorial, you learned how to create a BLE device with the ESP32 with the default UUIDs defined by the SIG. As an example, we created an Environmental Sensing Service with temperature, humidity, and pressure characteristics. The Environmental Sensing Service also supports many other characteristics. So, you can easily modify this project to work with other sensors. Or you can also create a different Service to advertise other types of characteristics—the workflow is the same.

We hope you found this tutorial useful and that it has helped you understand more about BLE protocol with the ESP32.

We have other BLE and Bluetooth Classic tutorials that you may find useful:

Learn more about the ESP32 with our resources:

Thanks for reading.



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!

13 thoughts on “ESP32 BLE Peripheral (Server): Environmental Sensing Service”

    • Hi.
      I’m sorry. That was a copy/paste error on my part.
      That’s only available on the DHT sensor.
      For the BME280 to display in Fahrenheit you need to do the conversion yourself in the code.
      tempF=1.8 * bme.readTemperature() + 32;

      Regards,
      Sara

      Reply
  1. Great new project! It worked right away. Plug and Play! I didn’t even bother reading most of your article. Maybe I should: the temperature and humidity readings in the app are only 10 percent of the expected values, and I get no pressure reading. I promise I will read the article and start all over. 🙂

    Reply
  2. Your code appears to be mostly working. However I get the following error message once connected: Incorrect data length (16bit expected) (0x) 00
    I am using a Samsung Galaxy 5 smart phone.

    Reply
    • Your instructions say “Then, click on the second icon at the left to change the format.”
      I don’t see those icons on my version of nRF Connect.
      is therre some other data format I can use besides int16_t ??

      Reply
  3. Where/how are the units for the temperatureCharacteristic.setValue() defined? If I search for examples with 2A6E, it seems the default is degrees C with DecimalExponent -2 (meaning units are .01degC). I wasn’t able to see where the DecimalExponent is defined. An older xml viewer seems to have the -2 exponent associated with 2A6E. I came across an alternate API for BLEDescriptor with an argument for units with DecimalExponent, but didn’t see documentation on the default.

    Also, shouldn’t the temperature be int16_t?

    Reply
  4. Very good article. Ofcourse the Nordc app is handy to start with, but when I get around to it, i will build a dedicated app that just shows the sensor values without all the unnecessary ‘noise’. But your article already is a great start

    Reply
  5. Hi !
    Thanks for all the interistng projects – I have learnt very much also for my BLE project. I’m trying to setup a battery driven sensor. To save battery I must include deep-sleep at the server, however sometimes, the client lost the connection.
    I’m wondering wether you could include this topic in your project?
    Best regards Franz

    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.