ESP32 Web Bluetooth (BLE): Getting Started Guide

This guide provides a beginner-friendly introduction to using Web Bluetooth with the ESP32. We’ll explain what Web Bluetooth is and walk you through creating a web application for interacting with an ESP32 Bluetooth Low Energy (BLE) device. Within the web app, you’ll be able to control the ESP32 GPIOs and retrieve values sent by the ESP32 through writing to and reading from its BLE characteristics.

Web Bluetooth with the ESP32 Getting Started Guide

Table of Contents

Throughout this tutorial, we’ll cover the following:

Introducing Web Bluetooth

Web Bluetooth (also sometimes referred to as Web BLE) is a technology that allows you to connect and control BLE-enabled devices, like the ESP32, directly from your web browser using JavaScript.

With Web BLE, you can create web applications that interact with your ESP32 devices via Bluetooth, enabling you to control GPIO pins, exchange data, and manage your devices remotely through a web interface (this means any device that supports a web browser like your computer or smartphone).

Explaining Web Bluetooth with ESP32

One of the key advantages of Web BLE is its cross-platform compatibility. Unlike traditional mobile apps developed for Android or iOS, Web BLE applications are web-based and can run on any device with a modern web browser that supports Web BLE.

This cross-platform compatibility removes the need for users to download and install dedicated mobile apps, simplifying the user experience and reducing development efforts.

This means you can use a smartphone, tablet, or desktop computer to connect and control ESP32 devices using a Web BLE application.

The Web Bluetooth API is still under development, but it is generally considered to be stable and usable. It has been implemented in Chrome, Edge, Opera, and Firefox, and it is supported on Android and Windows. However, it is not yet supported on iOS.

Note: At the time of writing this tutorial, Web BLE is not supported on iOS.

Bluetooth Low Energy Introduction – Basic Concepts

Before proceeding, it’s important to get familiar with some basic BLE concepts. We also recommend that you take a quick look at our BLE getting started guides and tutorials:

BLE Peripheral and Controller (Central Device)

When using Bluetooth Low Energy (BLE), it’s important to understand the roles of BLE Peripheral and BLE Controller (also referred to as the Central Device).

BLE Peripheral and BLE Controller (Central)

In our particular example, the ESP32 takes the role of the BLE Peripheral, serving as the device that provides data or services. Your smartphone or computer acts as the BLE Controller, managing the connection and communication with the ESP32.

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. But, in our particular example, it will act as a server, exposing its GATT structure containing data. The BLE Server acts as a provider of data or services, while the BLE Client consumes or uses these services.

ESP32 BLE Client and BLE Server

The server advertises its existence, so it can be found by other devices and contains data that the client can read or interact with. The client scans the nearby devices, and when it finds the server it is looking for, it establishes a connection and can interact with that device by reading or writing on its characteristics.

GATT

GATT, which stands for Generic Attribute Profile, is a fundamental concept in Bluetooth Low Energy (BLE) technology. Essentially, it serves as a blueprint for how BLE devices communicate with each other. Think of it as a structured language that two BLE devices use to exchange information seamlessly.

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

For a more detailed introduction to these BLE concepts, read: Getting Started with ESP32 Bluetooth Low Energy (BLE) on Arduino IDE.

UUID

A UUID is a unique digital identifier used in BLE and GATT to distinguish and locate services, characteristics, and descriptors. It’s like a distinct label that ensures every component in a Bluetooth device has a unique name.

Each service, characteristic, and descriptor has 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 and default UUIDs for services, and characteristics specified in the SIG (Bluetooth Special Interest Group). This means, that if you have a BLE device that uses the default UUIDs for its services and characteristics, you’ll know exactly how to interact with that device to get or interact with the information you’re looking for.

Most of the time, with the ESP32, you’ll use your custom UUIDs. You can generate them using this UUID generator website.

Project Overview

Now that you’re more familiar with BLE concepts, let’s go through a quick overview of the project we’ll build.

The ESP32 will act as a BLE Peripheral/BLE Server that advertises its existence. Your computer, smartphone, or tablet will act as a BLE Controller/Client that interacts with the ESP32 device.

ESP32 Web BLE

The ESP32 GATT structure will have one service with two characteristics. One characteristic (let’s call it sensor characteristic) will be the place to save a value that changes over time (like sensor readings). The other characteristic (let’s call it LED characteristic) will be the place to save the state of a GPIO. By changing the value of that characteristic, we’ll be able to control an LED connected to that GPIO.

ESP32 Profile GATT example

The ESP32 will write a new value to the sensor characteristic periodically. Your browser will connect to the ESP32 Bluetooth device and will receive notifications whenever the value changes and will display the new value on the web page (see Fetched Value in the picture below).

ESP32 Web BLE App

Additionally, your browser will also connect to the LED characteristic and will change its value (see the ON and OFF buttons above). The ESP32 checks that the value of that characteristic changed, and it will change the state of the GPIO accordingly, turning the LED either on or off.

ESP32 BLE Device – Arduino Code

The following code turns the ESP32 into a BLE device with one service and two characteristics as we’ve mentioned previously.

Upload the following code to your board, and it will work straight away.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-web-bluetooth/
  
  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>

BLEServer* pServer = NULL;
BLECharacteristic* pSensorCharacteristic = NULL;
BLECharacteristic* pLedCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

const int ledPin = 2; // Use the appropriate GPIO pin for your setup

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "19b10000-e8f2-537e-4f6c-d104768a1214"
#define SENSOR_CHARACTERISTIC_UUID "19b10001-e8f2-537e-4f6c-d104768a1214"
#define LED_CHARACTERISTIC_UUID "19b10002-e8f2-537e-4f6c-d104768a1214"

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

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

class MyCharacteristicCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* pLedCharacteristic) {
        std::string value = pLedCharacteristic->getValue();
        if (value.length() > 0) {
            Serial.print("Characteristic event, written: ");
            Serial.println(static_cast<int>(value[0])); // Print the integer value

            int receivedValue = static_cast<int>(value[0]);
            if (receivedValue == 1) {
                digitalWrite(ledPin, HIGH);
            } else {
                digitalWrite(ledPin, LOW);
            }
        }
    }
};

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // 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
  pSensorCharacteristic = pService->createCharacteristic(
                      SENSOR_CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // Create the ON button Characteristic
  pLedCharacteristic = pService->createCharacteristic(
                      LED_CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_WRITE
                    );

  // Register the callback for the ON button characteristic
  pLedCharacteristic->setCallbacks(new MyCharacteristicCallbacks());

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pSensorCharacteristic->addDescriptor(new BLE2902());
  pLedCharacteristic->addDescriptor(new BLE2902());

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

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
    // notify changed value
    if (deviceConnected) {
        pSensorCharacteristic->setValue(String(value).c_str());
        pSensorCharacteristic->notify();
        value++;
        Serial.print("New value notified: ");
        Serial.println(value);
        delay(3000); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
    }
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        Serial.println("Device disconnected.");
        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;
        Serial.println("Device Connected");
    }
}

View raw code

This is one of the simplest BLE code examples that turn the ESP32 into a BLE device that writes and listens to changes on characteristics. If you understand how this code works, you can easily modify it to serve more complex projects with more characteristics and services.

We recommend continuing reading to learn how the code works.

How the Code Works

In summary, this code sets up a BLE server with two characteristics—one for reading sensor data and another for controlling an LED. When a BLE client connects, it can read sensor data and send commands (write on the characteristic) to turn the LED on or off. The code also handles device connection and disconnection events.

Including Libraries

First, you need to import the following libraries to deal with Bluetooth.

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

Creating Global Variables

Then you create some global variables to use later in your code.

BLEServer* pServer = NULL;
BLECharacteristic* pSensorCharacteristic = NULL;
BLECharacteristic* pLedCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 0;

const int ledPin = 2; // Use the appropriate GPIO pin for your setup
  • pServer: Pointer to the BLEServer object.
  • pSensorCharacteristic: Pointer to the BLECharacteristic for reading the sensor value.
  • pLedCharacteristic: Pointer to the BLECharacteristic for controlling an LED.
  • deviceConnected: A boolean variable to track whether a BLE device is connected.
  • oldDeviceConnected: A boolean variable to track the previous connection status.
  • value: An integer variable used to store and send sensor values.
  • ledPin: The GPIO pin number to which an LED is connected.

UUID Definitions

Then, you set the UUIDs for the Service and Characteristics. Those UUIDs were created using the uuidgenerator website. You can generate your own UUIDs for your application, but for this example, we recommend using the same UUIDs we’re using.

#define SERVICE_UUID        "19b10000-e8f2-537e-4f6c-d104768a1214"
#define SENSOR_CHARACTERISTIC_UUID "19b10001-e8f2-537e-4f6c-d104768a1214"
#define LED_CHARACTERISTIC_UUID "19b10002-e8f2-537e-4f6c-d104768a1214"

BLE Server and Callbacks

Then, you create several callback functions. The MyServerCallbacks defines a callback function for device connection and disconnection events. In this case, we change the value of the deviceConnected variable to true or false depending on the connection state.

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

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

The MyCharacteristicCallbacks defines a callback function to read the value of a characteristic when it changes. In our code, we’ll set this callback function for the LED_CHARACTERISTIC to detect when the value of the characteristic has changed. We’ll turn the LED on and off accordingly to the value of that characteristic.

class MyCharacteristicCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* onCharacteristic) {
        std::string value = onCharacteristic->getValue();
        if (value.length() > 0) {
            Serial.print("Characteristic event, written: ");
            Serial.println(static_cast<int>(value[0])); // Print the integer value

            int receivedValue = static_cast<int>(value[0]);
            if (receivedValue == 1) {
                digitalWrite(ledPin, HIGH);
            } else {
                digitalWrite(ledPin, LOW);
            }
        }
    }
};

setup()

In the setup(), initialize serial communication for debugging.

Serial.begin(115200);

Set the ledPin as an output.

pinMode(ledPin, OUTPUT);

Initialize the ESP32 as BLE device called ESP32. You can call it any other name, but for this project we recommend using this name to be compatible with the app we’ll build later on.

Then, create a BLE server and set its callbacks for connection and disconnection.

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

Create a BLE service with the UUID we’ve defined earlier.

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

Create a BLE characteristic (inside the service we just created) for sensor values and set its properties (read, write, notify, indicate).

// Create a BLE Characteristic
pSensorCharacteristic = pService->createCharacteristic(
                    SENSOR_CHARACTERISTIC_UUID,
                    BLECharacteristic::PROPERTY_READ   |
                    BLECharacteristic::PROPERTY_WRITE  |
                    BLECharacteristic::PROPERTY_NOTIFY |
                    BLECharacteristic::PROPERTY_INDICATE
                  );

Create a BLE characteristic for the LED characteristic and set its property (write).

// Create the ON button Characteristic
pLedCharacteristic = pService->createCharacteristic(
                    LED_CHARACTERISTIC_UUID,
                    BLECharacteristic::PROPERTY_WRITE
                  );

Register the callback for the LED characteristic, so that we detect when a new value was written on that characteristic.

pLedCharacteristic->setCallbacks(new MyCharacteristicCallbacks());

Add BLE descriptors (BLE2902) to both characteristics.

Note: BLE2902 is a specific descriptor that is often used for Client Characteristic Configuration (CCC). CCC descriptors are used to configure how a client (the device connecting to the server) wants to be notified or indicated of changes in a characteristic’s value. In simpler terms, they control whether the client should receive notifications or indications when the value of the associated characteristic changes.

By adding BLE2902 descriptors to both characteristics, you make it possible for clients to configure how they want to be notified or updated when the values of these characteristics change. Clients can use these descriptors to enable or disable notifications or indications, depending on their preferences or requirements for real-time updates from the ESP32 server.

Start the BLE service.

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

And finally, configure advertising settings and start advertising.

// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");

loop()

In the loop(), if a BLE device is connected (deviceConnected is true), it updates the sensor value, notifies the client, and increments the value.

if (deviceConnected) {
    pSensorCharacteristic->setValue(String(value).c_str());
    pSensorCharacteristic->notify();
    value++;
    Serial.print("New value notified: ");
    Serial.println(value);
    delay(3000); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
}

If a device disconnects and oldDeviceConnected is true, it restarts advertising and logs a message.
If a device connects and oldDeviceConnected is false, it logs a message (you can add your own logic here for actions to be taken on connection).

// disconnecting
if (!deviceConnected && oldDeviceConnected) {
    Serial.println("Device disconnected.");
    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;
    Serial.println("Device Connected");
}

Uploading the Code

Upload the code to your ESP32 board. After uploading, open the Serial Monitor and restart your board.

You’ll see that it initialized the BLE service and is waiting for a connection from a client.

ESP32 BLE Server waiting client to notify

Now that you’ve set the ESP32 as a BLE Client, we’ll create the web app so that we can interact with the ESP32 via Bluetooth using our web browser.

Alternatively, you can use our web app by going to this URL:

ESP32 Web BLE App

Connect to your ESP32 BLE device and see the values sent by the ESP32 being displayed on the interface and control the ESP32 on-board LED with the ON and OFF buttons. If you want to learn how to build the web app, continue reading.

Creating the Web BLE App

Create an HTML file called index.html with the following code (it contains both the HTML to build the web page and Javascript to handle Web Bluetooth).

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-web-bluetooth/

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

<!DOCTYPE html>
<html>
<head>
    <title>ESP32 Web BLE App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" href="">
</head>
<body>
  <h1>ESP32 Web BLE Application</h1>
  <button id="connectBleButton">Connect to BLE Device</button>
  <button id="disconnectBleButton">Disconnect BLE Device</button>
  <p>BLE state: <strong><span id="bleState" style="color:#d13a30;">Disconnected</span></strong></p>
  <h2>Fetched Value</h2>
  <p><span id="valueContainer">NaN</span></p>
  <p>Last reading: <span id="timestamp"></span></p>
  <h2>Control GPIO 2</h2>
  <button id="onButton">ON</button>
  <button id="offButton">OFF</button>
  <p>Last value sent: <span id="valueSent"></span></p>
  <p><a href="https://randomnerdtutorials.com/">Created by RandomNerdTutorials.com</a></p>
  <p><a href="https://RandomNerdTutorials.com/esp32-web-bluetooth/">Read the full project here.</a></p>
</body>
<script>
    // DOM Elements
    const connectButton = document.getElementById('connectBleButton');
    const disconnectButton = document.getElementById('disconnectBleButton');
    const onButton = document.getElementById('onButton');
    const offButton = document.getElementById('offButton');
    const retrievedValue = document.getElementById('valueContainer');
    const latestValueSent = document.getElementById('valueSent');
    const bleStateContainer = document.getElementById('bleState');
    const timestampContainer = document.getElementById('timestamp');

    //Define BLE Device Specs
    var deviceName ='ESP32';
    var bleService = '19b10000-e8f2-537e-4f6c-d104768a1214';
    var ledCharacteristic = '19b10002-e8f2-537e-4f6c-d104768a1214';
    var sensorCharacteristic= '19b10001-e8f2-537e-4f6c-d104768a1214';

    //Global Variables to Handle Bluetooth
    var bleServer;
    var bleServiceFound;
    var sensorCharacteristicFound;

    // Connect Button (search for BLE Devices only if BLE is available)
    connectButton.addEventListener('click', (event) => {
        if (isWebBluetoothEnabled()){
            connectToDevice();
        }
    });

    // Disconnect Button
    disconnectButton.addEventListener('click', disconnectDevice);

    // Write to the ESP32 LED Characteristic
    onButton.addEventListener('click', () => writeOnCharacteristic(1));
    offButton.addEventListener('click', () => writeOnCharacteristic(0));

    // Check if BLE is available in your Browser
    function isWebBluetoothEnabled() {
        if (!navigator.bluetooth) {
            console.log("Web Bluetooth API is not available in this browser!");
            bleStateContainer.innerHTML = "Web Bluetooth API is not available in this browser!";
            return false
        }
        console.log('Web Bluetooth API supported in this browser.');
        return true
    }

    // Connect to BLE Device and Enable Notifications
    function connectToDevice(){
        console.log('Initializing Bluetooth...');
        navigator.bluetooth.requestDevice({
            filters: [{name: deviceName}],
            optionalServices: [bleService]
        })
        .then(device => {
            console.log('Device Selected:', device.name);
            bleStateContainer.innerHTML = 'Connected to device ' + device.name;
            bleStateContainer.style.color = "#24af37";
            device.addEventListener('gattservicedisconnected', onDisconnected);
            return device.gatt.connect();
        })
        .then(gattServer =>{
            bleServer = gattServer;
            console.log("Connected to GATT Server");
            return bleServer.getPrimaryService(bleService);
        })
        .then(service => {
            bleServiceFound = service;
            console.log("Service discovered:", service.uuid);
            return service.getCharacteristic(sensorCharacteristic);
        })
        .then(characteristic => {
            console.log("Characteristic discovered:", characteristic.uuid);
            sensorCharacteristicFound = characteristic;
            characteristic.addEventListener('characteristicvaluechanged', handleCharacteristicChange);
            characteristic.startNotifications();
            console.log("Notifications Started.");
            return characteristic.readValue();
        })
        .then(value => {
            console.log("Read value: ", value);
            const decodedValue = new TextDecoder().decode(value);
            console.log("Decoded value: ", decodedValue);
            retrievedValue.innerHTML = decodedValue;
        })
        .catch(error => {
            console.log('Error: ', error);
        })
    }

    function onDisconnected(event){
        console.log('Device Disconnected:', event.target.device.name);
        bleStateContainer.innerHTML = "Device disconnected";
        bleStateContainer.style.color = "#d13a30";

        connectToDevice();
    }

    function handleCharacteristicChange(event){
        const newValueReceived = new TextDecoder().decode(event.target.value);
        console.log("Characteristic value changed: ", newValueReceived);
        retrievedValue.innerHTML = newValueReceived;
        timestampContainer.innerHTML = getDateTime();
    }

    function writeOnCharacteristic(value){
        if (bleServer && bleServer.connected) {
            bleServiceFound.getCharacteristic(ledCharacteristic)
            .then(characteristic => {
                console.log("Found the LED characteristic: ", characteristic.uuid);
                const data = new Uint8Array([value]);
                return characteristic.writeValue(data);
            })
            .then(() => {
                latestValueSent.innerHTML = value;
                console.log("Value written to LEDcharacteristic:", value);
            })
            .catch(error => {
                console.error("Error writing to the LED characteristic: ", error);
            });
        } else {
            console.error ("Bluetooth is not connected. Cannot write to characteristic.")
            window.alert("Bluetooth is not connected. Cannot write to characteristic. \n Connect to BLE first!")
        }
    }

    function disconnectDevice() {
        console.log("Disconnect Device.");
        if (bleServer && bleServer.connected) {
            if (sensorCharacteristicFound) {
                sensorCharacteristicFound.stopNotifications()
                    .then(() => {
                        console.log("Notifications Stopped");
                        return bleServer.disconnect();
                    })
                    .then(() => {
                        console.log("Device Disconnected");
                        bleStateContainer.innerHTML = "Device Disconnected";
                        bleStateContainer.style.color = "#d13a30";

                    })
                    .catch(error => {
                        console.log("An error occurred:", error);
                    });
            } else {
                console.log("No characteristic found to disconnect.");
            }
        } else {
            // Throw an error if Bluetooth is not connected
            console.error("Bluetooth is not connected.");
            window.alert("Bluetooth is not connected.")
        }
    }

    function getDateTime() {
        var currentdate = new Date();
        var day = ("00" + currentdate.getDate()).slice(-2); // Convert day to string and slice
        var month = ("00" + (currentdate.getMonth() + 1)).slice(-2);
        var year = currentdate.getFullYear();
        var hours = ("00" + currentdate.getHours()).slice(-2);
        var minutes = ("00" + currentdate.getMinutes()).slice(-2);
        var seconds = ("00" + currentdate.getSeconds()).slice(-2);

        var datetime = day + "/" + month + "/" + year + " at " + hours + ":" + minutes + ":" + seconds;
        return datetime;
    }


</script>

</html>

View raw code

How does it Work?

This HTML and JavaScript code represents a web application that allows you to connect to an ESP32 device over Bluetooth Low Energy (BLE). The application provides a user interface to interact with the ESP32, including reading values and controlling an LED. Let’s break down the code and understand how it works.

Below you can see the application we’ll build (on the right with CSS, and on the left without CSS).

HTML Page

First, we create a simple HTML page. We first create two buttons: one to connect the browser to BLE devices and another to disconnect.

<button id="connectBleButton"> Connect to BLE Device</button>
<button id="disconnectBleButton"> Disconnect BLE Device</button>

Then, we have a paragraph that will then display the BLE connection state.

<p>BLE state: <strong><span id="bleState" style="color:#d13a30;">Disconnected</span></strong></p>

Then, we have a section to display the value written by the ESP32 on the sensor value characteristic.

<h2>Fetched Value</h2>
<p><span id="valueContainer">NaN</span></p>

We’ll also display the timestamp of when the value was received in the following paragraph.

<p>Last reading: <span id="timestamp"></span></p>

Finally, we have a section with two buttons. One to write 1 on the LED characteristic (to turn it on) and another to write 0 on the LED characteristic (to turn it off).

<button id="onButton">ON</button>
<button id="offButton">OFF</button>

We also have a paragraph to display the last value that was sent to control the LED.

<p>Last value sent: <span id="valueSent"></span></p>

JavaScript – Web BLE

Next, inside the <script></script> tags, we have the Javascript code responsible for handling the buttons and connecting to the ESP32 via Bluetooth and interacting with its characteristics.

DOM Elements

First, we select the HTML elements and assign them to a variable name for easier manipulation along the code.

const connectButton = document.getElementById('connectBleButton');
const disconnectButton = document.getElementById('disconnectBleButton');
const onButton = document.getElementById('onButton');
const offButton = document.getElementById('offButton');
const retrievedValue = document.getElementById('valueContainer');
const latestValueSent = document.getElementById('valueSent');
const bleStateContainer = document.getElementById('bleState');
const timestampContainer = document.getElementById('timestamp');

BLE Device Specifications

Then, we add the BLE Device specifications we want to connect to. We already set up the ESP32 as a BLE server. So, we can set its specs here, the name, the UUID of the service, and the UUIDs of the characteristics we want to interact with—these should be the same you’ve set on your Arduino code.

//Define BLE Device Specs
var deviceName ='ESP32';
var bleService = '19b10000-e8f2-537e-4f6c-d104768a1214';
var ledCharacteristic = '19b10002-e8f2-537e-4f6c-d104768a1214';
var sensorCharacteristic= '19b10001-e8f2-537e-4f6c-d104768a1214';

Defining Global Variables

Then, we create some global variables to handle Bluetooth communication and device discovery later on in our code.

var bleServer;
var bleServiceFound;
var sensorCharacteristicFound;

Assigning Events to Buttons

Then, we add event listeners to the buttons to trigger actions when they are clicked.

Connect To BLE Device Button

The Connect To BLE Device button will trigger the connectToDevice() function. But first, we check if the Web BLE Javascript API is available in your browser before proceeding and we display a message on the bleStateContainer in case Web BLE is not supported.

// Connect Button (search for BLE Devices only if BLE is available)
connectButton.addEventListener('click', (event) => {
    if (isWebBluetoothEnabled()){
        connectToDevice();
    }
});

To check if the Web Bluetooth is enabled in your browser, we created a function called isWebBluetoothEnabled(). The method that checks if Web BLE is enabled is nagivator.bluetooth.

// Check if BLE is available in your Browser
function isWebBluetoothEnabled() {
    if (!navigator.bluetooth) {
        console.log("Web Bluetooth API is not available in this browser!");
         bleStateContainer.innerHTML = "Web Bluetooth API is not available in this browser/device!";
         return false
    }
    console.log('Web Bluetooth API supported in this browser.');
    return true
}

Disconnect BLE Device Button

The Disconnect BLE Device Button will call the disconnectDevice function.

// Disconnect Button
disconnectButton.addEventListener('click', disconnectDevice);

ON and OFF Buttons

The on and off buttons will trigger the writeOnCharacteristic function. In case of the ON button, we’ll pass a 1 as a parameter, and in case of the OFF button, we’ll pass 0 as a parameter.

// Write to the ESP32 LED Characteristic
onButton.addEventListener('click', () => writeOnCharacteristic(1));
offButton.addEventListener('click', () => writeOnCharacteristic(0));

Connecting to BLE Device and search Services and Characteristics

The connectToDevice() function is triggered when you click on the Connect to BLE Device button. This function searches for our specific BLE Device, its Service and Characteristics.

function connectToDevice(){

The following lines of code search for BLE Devices with the name and Service that we’ve defined. There are other filters you can define to search for BLE devices, or you can opt to not add any filters and return all the BLE devices found.

navigator.bluetooth.requestDevice({
    filters: [{name: deviceName}],
    optionalServices: [bleService]
})

Once we’ve connected to your device, we display on the HTML interface that we are now connected to our device. We also add an event listener to our device, in case it disconnects (it will call the onDisconnected function).

.then(device => {
    console.log('Device Selected:', device.name);
    bleStateContainer.innerHTML = 'Connected to device ' + device.name;
    bleStateContainer.style.color = "#24af37";
    device.addEventListener('gattservicedisconnected', onDisconnected);
    return device.gatt.connect();
})

From the device, we can get our GATT server (the hierarchical structure that stores data in BLE protocol). We save our GATT server on our bleServer global variable. From the GATTT server, we can get the service with the UUID we’ve defined at the beginning of the code, bleService.

.then(gattServer =>{
    bleServer = gattServer;
    console.log("Connected to GATT Server");
    return bleServer.getPrimaryService(bleService);
 })

Once we’ve found our service, we save it on the global variable bleServiceFound, and get the sensor characteristic from the service.

.then(service => {
    bleServiceFound = service;
    console.log("Service discovered:", service.uuid);
    return service.getCharacteristic(sensorCharacteristic);
})

Now that we’ve found our sensor characteristic, we assigned it to the global sensorCharacteristicFound variable. We add an event listener to our characteristic to handle what happens when the characteristic value changes—we call the handleCharacteristicChange function. We also start notifications on that characteristic. Finally, we return the current value written on the characteristic.

.then(characteristic => {
    console.log("Characteristic discovered:", characteristic.uuid);
    sensorCharacteristicFound = characteristic;
    characteristic.addEventListener('characteristicvaluechanged', handleCharacteristicChange);
    characteristic.startNotifications();
    console.log("Notifications Started.");
    return characteristic.readValue();
})

Next, we display the value read from the characteristic on the corresponding HTML element.

.then(value => {
    console.log("Read value: ", value);
    const decodedValue = new TextDecoder().decode(value);
    console.log("Decoded value: ", decodedValue);
    retrievedValue.innerHTML = decodedValue;
})

Handle Characteristic Change

The handleCharacteristicChange() function will be called when the sensor characteristic value changes. That function gets the new value written on the characteristic and places it on the corresponding HTML element. Additionally, we also get the date and time to display when was the last time that the characteristic value has changed.

function handleCharacteristicChange(event){
    const newValueReceived = new TextDecoder().decode(event.target.value);
    console.log("Characteristic value changed: ", newValueReceived);
    retrievedValue.innerHTML = newValueReceived;
    timestampContainer.innerHTML = getDateTime();
}

Write on Characteristics

When you click on the ON or OFF buttons, the writeOnCharacteristicfunction will be called. That function first checks if we are connected to the BLE server. If we are, it gets the LED characteristic from the BLE Service we’ve found previously, and it writes the value we’ve passed as an argument on the characteristic.

if (bleServer && bleServer.connected) {
    bleServiceFound.getCharacteristic(ledCharacteristic)
     .then(characteristic => {
        console.log("Found the LED characteristic: ", characteristic.uuid);
        const data = new Uint8Array([value]);
        return characteristic.writeValue(data);
})

If we’re successful in writing to the characteristic, we update the last value written on the HTML interface.

.then(() => {
    latestValueSent.innerHTML = value;
    console.log("Value written to LEDcharacteristic:", value);
})

In case we try to write to the characteristic without being connected to BLE first, we’ll create a pop-up window informing that we need to connect to BLE first.

} else {
    console.error ("Bluetooth is not connected. Cannot write to characteristic.")
    window.alert("Bluetooth is not connected. Cannot write to characteristic. \n Connect to BLE first!")
}

Disconnect BLE Device

When you click on the Disconnect BLE Device button, the disconnectDevice() function is called. This function first checks if we’re connected to the server. Then, we stop the notifications on the sensorCharacteristic and we disconnect from the GATT server. Additionally, we also display some messages on the HTML interface and a pop-up window in case Bluetooth is not connected.

function disconnectDevice() {
    console.log("Disconnect Device.");
    if (bleServer && bleServer.connected) {
        if (sensorCharacteristicFound) {
            sensorCharacteristicFound.stopNotifications()
                .then(() => {
                    console.log("Notifications Stopped");
                    return bleServer.disconnect();
                })
                .then(() => {
                    console.log("Device Disconnected");
                    bleStateContainer.innerHTML = "Device Disconnected";
                    bleStateContainer.style.color = "#d13a30";

                })
                .catch(error => {
                    console.log("An error occurred:", error);
                });
        } else {
            console.log("No characteristic found to disconnect.");
        }
    } else {
        // Throw an error if Bluetooth is not connected
        console.error("Bluetooth is not connected.");
        window.alert("Bluetooth is not connected.")
    }
}

Testing the Web BLE App

Save your index.html file and drag it to your browser. The following page will open.

ESP32 Web BLE Application

With the ESP32 running the code we’ve provided previously, let’s test the web app.

Start by clicking on the Connect to BLE Device button. A window will pop up and you should see the ESP32 BLE Device. Connect to that device.

Web BLE Connect to ESP32 Device

You’ll see that the BLE Status will change to connected and you’ll start receiving the values written by the ESP32 on the sensor characteristic.

ESP32 Web BLE App (no CSS)

Simultaneously, you should get the following messages on the Arduino Serial Monitor showing that the connection was successful and the values being written on the sensor characteristic.

ESP32 BLE Server Notify Clients Serial Monitor

Getting back to the app, if you click the ON and OFF buttons you’ll be able to control the ESP32 LED on and off.

Control ESP32 GPIOs via Web BLE

On the Serial Monitor, you’ll see that it detects the changes in the LED characteristic value.

ESP32 BLE Server - detect characteristic change.

Consequently, it will control the ESP32 onboard LED according to the value written on that characteristic.

Styling the Web BLE App

To make your web interface look better, we’ll add some CSS.

index.html

Copy the following to your index.html file. This is the same HTML we provided previously, but we added some CSS classes to style the web page.

<!--
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-web-bluetooth/

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

<!DOCTYPE html>
<html>
<head>
    <title>ESP32 Web BLE App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" href="favicon.ico">
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="UTF-8">
</head>
<body>
    <div class="topnav">
        <h1>ESP32 Web BLE Application</h1>
    </div>
    <div class="content">
        <div class="card-grid">
            <div class="card">
                <p>
                    <button id="connectBleButton" class="connectButton"> Connect to BLE Device</button>
                    <button id="disconnectBleButton" class="disconnectButton"> Disconnect BLE Device</button>
                </p>
                <p class="gray-label">BLE state: <strong><span id="bleState" style="color:#d13a30;">Disconnected</span></strong></p>
            </div>
        </div>
        <div class="card-grid">
            <div class="card">
                <h2>Fetched Value</h2>
                <p class="reading"><span id="valueContainer">NaN</span></p>
                <p class="gray-label">Last reading: <span id="timestamp"></span></p>
            </div>

            <div class="card">
                <h2>Control GPIO 2</h2>
                <button id="onButton" class="onButton">ON</button>
                <button id="offButton" class="offButton">OFF</button>
                <p class="gray-label">Last value sent: <span id="valueSent"></span></p>
            </div>
        </div>
    </div>
    <div class="footer">
        <p><a href="https://randomnerdtutorials.com/">Created by RandomNerdTutorials.com</a></p>
        <p><a href="https://RandomNerdTutorials.com/esp32-web-bluetooth/">Read the full project here.</a></p>
    </div>
</body>
<script>
    // DOM Elements
    const connectButton = document.getElementById('connectBleButton');
    const disconnectButton = document.getElementById('disconnectBleButton');
    const onButton = document.getElementById('onButton');
    const offButton = document.getElementById('offButton');
    const retrievedValue = document.getElementById('valueContainer');
    const latestValueSent = document.getElementById('valueSent');
    const bleStateContainer = document.getElementById('bleState');
    const timestampContainer = document.getElementById('timestamp');

    //Define BLE Device Specs
    var deviceName ='ESP32';
    var bleService = '19b10000-e8f2-537e-4f6c-d104768a1214';
    var ledCharacteristic = '19b10002-e8f2-537e-4f6c-d104768a1214';
    var sensorCharacteristic= '19b10001-e8f2-537e-4f6c-d104768a1214';

    //Global Variables to Handle Bluetooth
    var bleServer;
    var bleServiceFound;
    var sensorCharacteristicFound;

    // Connect Button (search for BLE Devices only if BLE is available)
    connectButton.addEventListener('click', (event) => {
        if (isWebBluetoothEnabled()){
            connectToDevice();
        }
    });

    // Disconnect Button
    disconnectButton.addEventListener('click', disconnectDevice);

    // Write to the ESP32 LED Characteristic
    onButton.addEventListener('click', () => writeOnCharacteristic(1));
    offButton.addEventListener('click', () => writeOnCharacteristic(0));

    // Check if BLE is available in your Browser
    function isWebBluetoothEnabled() {
        if (!navigator.bluetooth) {
            console.log('Web Bluetooth API is not available in this browser!');
            bleStateContainer.innerHTML = "Web Bluetooth API is not available in this browser/device!";
            return false
        }
        console.log('Web Bluetooth API supported in this browser.');
        return true
    }

    // Connect to BLE Device and Enable Notifications
    function connectToDevice(){
        console.log('Initializing Bluetooth...');
        navigator.bluetooth.requestDevice({
            filters: [{name: deviceName}],
            optionalServices: [bleService]
        })
        .then(device => {
            console.log('Device Selected:', device.name);
            bleStateContainer.innerHTML = 'Connected to device ' + device.name;
            bleStateContainer.style.color = "#24af37";
            device.addEventListener('gattservicedisconnected', onDisconnected);
            return device.gatt.connect();
        })
        .then(gattServer =>{
            bleServer = gattServer;
            console.log("Connected to GATT Server");
            return bleServer.getPrimaryService(bleService);
        })
        .then(service => {
            bleServiceFound = service;
            console.log("Service discovered:", service.uuid);
            return service.getCharacteristic(sensorCharacteristic);
        })
        .then(characteristic => {
            console.log("Characteristic discovered:", characteristic.uuid);
            sensorCharacteristicFound = characteristic;
            characteristic.addEventListener('characteristicvaluechanged', handleCharacteristicChange);
            characteristic.startNotifications();
            console.log("Notifications Started.");
            return characteristic.readValue();
        })
        .then(value => {
            console.log("Read value: ", value);
            const decodedValue = new TextDecoder().decode(value);
            console.log("Decoded value: ", decodedValue);
            retrievedValue.innerHTML = decodedValue;
        })
        .catch(error => {
            console.log('Error: ', error);
        })
    }

    function onDisconnected(event){
        console.log('Device Disconnected:', event.target.device.name);
        bleStateContainer.innerHTML = "Device disconnected";
        bleStateContainer.style.color = "#d13a30";

        connectToDevice();
    }

    function handleCharacteristicChange(event){
        const newValueReceived = new TextDecoder().decode(event.target.value);
        console.log("Characteristic value changed: ", newValueReceived);
        retrievedValue.innerHTML = newValueReceived;
        timestampContainer.innerHTML = getDateTime();
    }

    function writeOnCharacteristic(value){
        if (bleServer && bleServer.connected) {
            bleServiceFound.getCharacteristic(ledCharacteristic)
            .then(characteristic => {
                console.log("Found the LED characteristic: ", characteristic.uuid);
                const data = new Uint8Array([value]);
                return characteristic.writeValue(data);
            })
            .then(() => {
                latestValueSent.innerHTML = value;
                console.log("Value written to LEDcharacteristic:", value);
            })
            .catch(error => {
                console.error("Error writing to the LED characteristic: ", error);
            });
        } else {
            console.error ("Bluetooth is not connected. Cannot write to characteristic.")
            window.alert("Bluetooth is not connected. Cannot write to characteristic. \n Connect to BLE first!")
        }
    }

    function disconnectDevice() {
        console.log("Disconnect Device.");
        if (bleServer && bleServer.connected) {
            if (sensorCharacteristicFound) {
                sensorCharacteristicFound.stopNotifications()
                    .then(() => {
                        console.log("Notifications Stopped");
                        return bleServer.disconnect();
                    })
                    .then(() => {
                        console.log("Device Disconnected");
                        bleStateContainer.innerHTML = "Device Disconnected";
                        bleStateContainer.style.color = "#d13a30";

                    })
                    .catch(error => {
                        console.log("An error occurred:", error);
                    });
            } else {
                console.log("No characteristic found to disconnect.");
            }
        } else {
            // Throw an error if Bluetooth is not connected
            console.error("Bluetooth is not connected.");
            window.alert("Bluetooth is not connected.")
        }
    }

    function getDateTime() {
        var currentdate = new Date();
        var day = ("00" + currentdate.getDate()).slice(-2); // Convert day to string and slice
        var month = ("00" + (currentdate.getMonth() + 1)).slice(-2);
        var year = currentdate.getFullYear();
        var hours = ("00" + currentdate.getHours()).slice(-2);
        var minutes = ("00" + currentdate.getMinutes()).slice(-2);
        var seconds = ("00" + currentdate.getSeconds()).slice(-2);

        var datetime = day + "/" + month + "/" + year + " at " + hours + ":" + minutes + ":" + seconds;
        return datetime;
    }


</script>

</html>

View raw code

style.css

On the same folder of your index.html file, create a file called style.css with the following.

html {
    font-family: Arial, Helvetica, sans-serif;
    display: inline-block;
    text-align: center;
}
h1 {
    font-size: 1.8rem;
    color: white;
}
.topnav {
    overflow: hidden;
    background-color: #0A1128;
}
body {
    margin: 0;
}
.content {
    padding: 50px;
}
.card-grid {
    max-width: 800px;
    margin: 0 auto;
    margin-bottom: 30px;
    display: grid;
    grid-gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.card {
    background-color: white;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}
button {
    color: white;
    padding: 14px 20px;
    margin: 8px 0;
    border: none;
    cursor: pointer;
    border-radius: 4px;
}

.onButton{
    background-color: #1b8a94;
}

.offButton{
    background-color: #5f6c6d;
}

.connectButton{
    background-color: #24af37;
}

.disconnectButton{
    background-color: #d13a30;
}

.gray-label {
    color: #bebebe;
    font-size: 1rem;
}

.reading {
    font-size: 1.8rem;
}

View raw code

favicon

Additionally, add the following favicon to your folder.

Demonstration

After creating all the required files in the same folder, open the index.html file on your web browser.

This is what the application will look like. It works exactly the same way as we’ve seen previously, but it looks much better.

ESP32 Web BLE Application

Taking it Further – Hosting your Web BLE App

At the moment, you can only connect to the ESP32 via BLE by opening the index.html file on the web browser of your computer. If you want to open it on your smartphone or any other device you would need to copy that file to the device and then, open it on the web browser. This is not very convenient.

The best way to have access to your web app on any device is to host your files on a server. To work with BLE, the files need to be served via HTTPS.

To host our web app, we’ll use GitHub pages. If you don’t have a GitHub account, create one before proceeding.

1. On your account dashboard, click on the + icon and create a New repository.

Github Create New Repository

2. The following page will load. Give a name to your repository, and make sure it is set to Public. Then, click on Create repository.

GitHub give repository name

3. Then, click on Add file > Upload files to repository.

GitHub repository upload files

4. Then, drag your index.html, style.css, and favicon.ico files to the repository. Then, click on Commit changes.

GitHub repository drag files

5. Then, go to Settings > Pages and make sure you have the options highlighted in red below. Then, click the Save button.

GitHub Pages Host Repository

After submitting, wait a few minutes for the web page to be available. Your web app will be in the following domain:

YOUR_GITHUB_USERNAME.github.io/YOUR_REPOSITORY_NAME

In my case, it is available on the following domain:

https://ruisantosdotme.github.io/esp32-web-ble/

Demonstration

Now, you can access your Web BLE App on any device (that supports Web BLE) with a web browser by going to that URL. Then, you can connect to the ESP32 via BLE using that device and read and write on its characteristics.

ESP32 Web BLE App Computer Browser

Now you can access the Web App on your smartphone or tablet and control your ESP32 BLE Device from there.

ESP32 Web BLE App Smartphone

Wrapping Up

In this tutorial, you learned about the Web BLE technology. In simple terms, it is a JavaScript API that allows us to create web apps to interact with BLE devices from any web browser that supports Web BLE.

You learned how to set the ESP32 as a BLE device with a Service and Characteristics and you create a web application to interact with the ESP32 characteristics. This way, we are now able to control the ESP32 via BLE using our browser.

We hope you’ve found this tutorial useful and that it helped you start into this relatively new technology.

We have other ESP32 tutorials related to Bluetooth that you may like:

Do you want to learn more about the ESP32? Check out all 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!

35 thoughts on “ESP32 Web Bluetooth (BLE): Getting Started Guide”

  1. Hi Sarah
    How can I print the article to my printer or to pdf, without losing lines at the top and bottom of every page?
    As an example:
    the first page ends with “Part 1: ESP 32….
    the second page starts with the bottom third of the line “Part 3: Hosting….
    I’m sure I’m not alone with this issue!
    Kind Regards J

    Reply
  2. That is quite a tutorial. My compliments. Just want to ask one small clarification. When you describe testing the HTML on your computer by dragging it in to the browser, I presume this only works if that computer also has Bluetooth, otherwise it will not connect to anything. or did i totally misunderstand?

    Reply
  3. As usual, a very well-written tutorial. I hadn’t heard of Web BLE before, so now I’m starting to think about how I might use this compared to the other ways I interact with my IoT devices. Thanks for providing the info.

    Reply
  4. OK after a couple of paragraphs I’m confused about Client and Server.

    Under ‘BLE Peripheral and Controller (Central Device)’ you say :
    ‘In our particular example, the ESP32 takes the role of the BLE Peripheral,’
    But in the next paragraph under ‘BLE Server and Client’ you say :
    ‘… in our particular example, it will act as a server, …’
    Perhaps the first should say: ‘In a general example ( as shown in the above diagram)…’ , and the second would be correct.

    Reply
    • Hi.
      In BLE, peripheral and server are the same device. It is a BLE peripheral, but it has all the information, so it’s also the server.
      With your computer or smartphone, you control the BLE peripheral, so this is the controller. It is also the client because it doesn’t contain any information.
      I hope this is clear.
      Regards,
      Sara

      Reply
  5. A great tutorial as always. Please help us understand that the web page on gihub will only work in our esp32 bluetooth range. We cannot use it to connect from another city or remote location.

    Reply
    • Hi.
      Yes. That’s right.
      The web application can control the ESP32 via bluetooth, so it must be within the device’s bluetooth range.
      REgards,
      Sara

      Reply
  6. Hi, thanks – as always very well described projects – you are by far the best on the net in creating & explaining makerprojects, in particular in the IoT area, which is my main interest.
    Just one question – still no progress in BLE support in micropython? I know it has been limited and somewhat unstable for a long time. There is a lot for Arduino (such as this one), but almost nothing for micropython. Any suggestions, tips or updates on the progress on BLE/micropython is appreciated.
    regards,

    Reply
    • Hi.
      We have some followers who use BLE successfully with micropython.
      However, we didn’t explore that yet in our blog.
      Regards,
      Sara

      Reply
  7. Hello Sara and Ruis,

    many thanks for your web-ble tutorial. I tried it and it works
    if – and only if the web browser supports web ble functionality.
    I had to enable the browser flag #enable-experimental-web-platform-features,
    e.g. on chrome or opera.

    For me only chrome on my mobil phone and my tablet works
    fine. On raspberry pi 5 there are problems to connect to the esp
    via ble. On other systems like windows 10 and other browsers
    like firefox and opera it seems not to work correctly. I installed
    therefore the last beta versions of chrome and opera.
    Maybe the browsers will implement the web-ble feature in future
    releases …

    Thanks again an best greetings,
    Peter

    Reply
  8. Hi Sara,

    today I tried the chrome browser on raspberry pi 5 again
    and it worked!!!!
    So I can use web ble on chrome browsers on android
    and linux (raspberry pi os bookworm 64 bit). great!
    I will use it in my lectures on esp32 and iot in the summer.

    On the server side (esp32) it should be no problem to use
    micropython. The last versions support ble and work fine.

    Thanks again to you and Ruis for the super tutorial,
    Peter

    Reply
  9. Hi Sara,

    thanks to both of you for this deep tutorial – realy cool!. One question left on my side for Bluetooth Low Energy (BLE).

    There is another standard – quite the most popular standard this time – using BLE: Zigbee! Worldwide there are hundrets of smarthome components using zigbee and that is quite comfortable.

    How cool could it be to use a ESP32 to create a own BLE-Device in example to read temperature, air-quality, switchin power-applications and other stuff. There are so much extremly cool tutorials on RNT that show and explain this with e.g. ESP8266 and ESP32.

    The questions is now: How far is it to combine BLE and ESP32 in that way to create own devices that communicate over Zigbee using BLE? Maybe this would be a relatively smal step. Is it real to hope for such a tutorial?

    Best regards and thanks,
    Manfred

    Reply
  10. Excellent tutorial, even though I’m not sure I fully understand, but, I do get the basics.
    I tried to modify the code to add additional buttons to Control Relays. This did not work. Got an onWrite overload error trying to compile. I did a bunch of copy/paste and changed the corresponding “LED/led” characteristics to “R1” for example.
    Can you direct me to additional code example that might use two or four buttons instead of the single LED buttons

    Thanks

    Reply
    • Hello Ste7

      You requested for a little bit code expanding the example.
      In my first experiments I added a third button to toggle the led.
      I created a new toggle button, defined a constant for it and
      added an event listener to write out ‘#’ to toggle the led.

      Here are the code snippets:

      add the toggle button
      OFF
      TOGGLE // added

      Last value sent:

      define a DOM constant for the toggle button
      const offButton = document.getElementById(‘offButton’);
      const toggleButton = document.getElementById(‘toggleButton’); // added
      const retrievedValue = document.getElementById(‘valueContainer’);
      add an event listener for the toggle button to write out ‘#’
      offButton.addEventListener(‘click’, () => writeOnCharacteristic(45));
      toggleButton.addEventListener(‘click’, () => writeOnCharacteristic(35)); // added

      I hope this helps,
      Peter

      Reply
  11. I found your new article very interesting.
    Can you compare the pros and cons to using this method against Web Sockets?
    I see the 3 second delay and wonder if that is too much for an IOT device running something like my Monorail project where I need status updates and remote motor control.

    Again, thank you both for your most excellent gifts of your time and creativity.

    Reply
  12. Hello Sara,
    thanks for this great tutorial,
    could you give me/us a hint how to connect to 2 or more ESP32 BLE at the same time?
    Just can’t find a solution, yet… .
    would be great,
    Regards,
    Tobias

    Reply
  13. Hello Sara.

    Do you have an example with more value displayed and also with a textBox. I would like to display information from more sensors and also sending values to ESP32.

    Thanks

    Martin

    Reply
  14. I tried changing the UUIDs in your example, but I always get the error that no Characteristics matching UUID were found in my browser.

    I changed the UUID under #define X_UUID in the Arduino code and in the var of the javascript code. Curiously if I just change the UUID in the javascript back to your examples (and not in the Arduino Code) it doesn’t throw an error which makes no sense.

    When I check the UUID with the nRF Connect App the services have the UUIDs I defined in the Arduino Code.

    Any idea what might be happening here?

    Reply
  15. Maybe I am tired, where does the file, “index.html ” go in my laptop?

    Does it get inserted into my Arduino/esp32 file location on laptop?

    Reply
    • Hi.
      Create the HTML file on any place on your computer.
      It will not be uploaded to your board.
      You just need to create the file on your computer and then open that file to see the webpage.
      Regards,
      Sara

      Reply
  16. Hello,
    After uploading the code without modifying anything i got a problem that the watchdog was triggered these are the logs i have on the serial monitor:

    rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
    mode:DIO, clock div:1
    load:0x3fff0030,len:1344
    load:0x40078000,len:13924
    ho 0 tail 12 room 4
    load:0x40080400,len:3600
    entry 0x400805f0

    E (21720) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
    E (21720) task_wdt: – IDLE (CPU 0)
    E (21720) task_wdt: Tasks currently running:
    E (21720) task_wdt: CPU 0: btController
    E (21720) task_wdt: CPU 1: IDLE
    E (21720) task_wdt: Aborting.

    abort() was called at PC 0x400e04e5 on core 0

    Reply
    • Hi.

      I just tested the project and it is working as expected.
      Make sure you’re using a proper USB cable that can provide enough power.

      Which ESP32 board are you using? And which ESP32 version do you have installed in your Arduino IDE?(Go to Tools > Board > Boards Manager>ESP32 and check the version you have installed)
      Regards,
      Sara

      Reply
  17. Hey
    Thank you so much for this tutorial I am getting this error which is super weird because my code was previously working…
    Compilation error: ‘class BLECharacteristic’ has no member named ‘getValue’; did you mean ‘setValue’?
    The code is identical to the tutorial, so I am not sure where it went wrong. I did update my Arduino IDE could that be why? Also can I set the mtu value to 500? if I do where would I do that. Any help would be amazing I have a project deadline coming up and I am super stressed now because what was previously working is not anymore 🙁

    Reply
    • Hi.
      Make sure you have an ESP32 board selected before compiling the code.
      Also, check the ESP32 boards version you’re using in Tools > Boards > Boards Manager > ESP32.
      Regards,
      Sara

      Reply
      • Yes, I have it is on COM12 (where the board is plugged in) and using board is esp32 Dev Module. I just double checked it and still gives the same error.

        Reply
          • Hey Sara, I figured out what the Issue was, I had downloaded an ArduinoBLE library which was interfering with the EspBLE functions when I deleted the library it stopped giving me the errors. Thank you for your help though 🙂

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.