ESP32 Web BLE – Điều khiển ESP32 từ Web qua Bluetooth Low Energy

Với bài hướng dẫn chủ đề ESP32 Web BLE lần này, bạn sẽ biết cách tạo một Web Server để tương tác với ESP32 thông qua công nghệ BLE (Bluetooth Low Energy – Bluetooth năng lượng thấp). Cụ thể, bạn có thể điều khiển một GPIO cụ thể trên ESP32 và truy xuất các giá trị nhận được từ ESP32, thông qua việc đọc / ghi các đặc tính BLE.

Giới thiệu Web BLE

Web BLE (đôi khi còn gọi là Web Bluetooth) là một công nghệ cho phép chúng ta kết nối và điều khiển các thiết bị hỗ trợ BLE như ESP32 trực tiếp từ trình duyệt Web (dựa trên JavaScript).

Với Web BLE, bạn có thể tạo ra các ứng dụng Web để tương tác với ESP32 và điều khiển chân GPIO, trao đổi dữ liệu, quản lý thông tin thiết bị từ xa dễ dàng qua giao diện Web (bạn có thể truy cập vào bằng điện thoại hoặc laptop đều được).

Giới thiệu ESP32 Web BLE
Giới thiệu ESP32 Web BLE

Ưu điểm chính của ESP32 Web BLE là có khả năng tương thích đa nền tảng, hỗ trợ trên cả iOS hoặc Android, chỉ cần thiết bị đó có trình duyệt Web hỗ trợ Web BLE.

Nhờ tính tương thích cao, chúng ta không cần phải cài đặt các ứng dụng dành riêng cho thiết bị, đơn giản hóa trải nghiệm người dùng.

Hiện nay, API Web Bluetooth được xem là ổn định, có thể sử dụng được. Và chúng đang được phát triển hơn nữa. Hiện nay, chúng được triển khai trong Firefox, Chrome, Opera, Edge và hỗ trợ cả Android và Windows, tuy nhiên chúng chưa hỗ trợ trên iOS.

Giới thiệu BLE

Trước khi bắt đầu vào chủ đề ESP32 Web BLE, bạn cần làm quen với các khái niệm BLE cơ bản. Trên Website IoTZone đã có khá nhiều bài viết hướng dẫn về chủ đề ESP32 BLE, bạn có thể xem qua tại:

Bộ điều khiển trung tâm và ngoại vi BLE

Khi sử dụng BLE, bạn cần hiểu ý nghĩa của bộ điều khiển trung tâm (BLE Controller) và các ngoại vi BLE (BLE Peripheral):

Bộ điều khiển trung tâm và ngoại vi BLE

Trong dự án này, ESP32 như một thiết bị ngoại vi BLE, chúng chỉ cung cấp dữ liệu hoặc dịch vụ. Còn điện thoại / laptop là một bộ điều khiển BLE, giúp quả lý kết nối và liên lạc với ESP32.

Máy chủ và máy khách BLE

Trong ESP32 Web BLE có 2 loại thiết bị khác nhau: Máy chủ và máy khách. ESP32 có thể là một máy chủ (Server) hoặc là máy khách (Client).

Tuy nhiên, trong hướng dẫn về ESP32 Web BLE này, chúng sẽ hoạt động như một máy chủ, giúp hiển thị cấu trúc GATT chứa dữ liệu. BLE Server giống một nhà cung cấp thông tin dữ liệu và dịch vụ, còn BLE Client thu thập hoặc sử dụng dịch vụ này.

Cách hoạt động của Máy chủ và máy khách BLE

BLE Server hiển thị sự tồn tại của nó để các thiết bị khác có thể tìm thấy Server và đọc hoặc tương tác với thông tin dữ liệu của Server.

BLE Client sẽ quét các thiết bị lân cận và khi tìm thấy đúng Server mình cần, chúng sẽ kết nối và tương tác với chúng bằng cách đọc / ghi dữ liệu.

Giới thiệu GATT

GATT là viết tắt của Generic Attribute Profile – một concept bên trong công nghệ BLE. Cụ thể, chúng có vai trò như một bản thiết kế chi tiết về cách giao tiếp giữa các thiết bị BLE. Bạn có thể hiểu đơn giản chúng như một ngôn ngữ có cấu trúc mà 2 thiết bị BLE dùng để trao đổi thông tin sao cho liền mạch.

Giới thiệu về ESP32 Web BLE GATT
Giới thiệu về ESP32 Web BLE GATT

UUID

Đây là một mã định danh kỹ thuật số được dùng trong ESP32 Web BLE và GATT, nhằm phân biệt và định vị các dịch vụ, bộ mô tả và đặc điểm. Chúng giống như một label riêng biệt, giúp đảm bảo tất cả các thiết bị Bluetooth đều có 1 tên duy nhất.

Mỗi dịch vụ, đặc điểm và mô tả đều có 1 UUID duy nhất, đây là một số 128 bit (16byte), ví dụ:

  • 55072829-bc9e-4c53-938a-74a6d4c78776

Khi sử dụng UUID với ESP32, đa phần chúng ta sẽ dùng UUID tùy chỉnh. Bạn có thể tạo thông qua Website tạo UUID này.

Tổng quan dự án ESP32 Web BLE

Sau khi đã hiểu hơn về BLE, bây giờ hãy cùng xem qua dự án mà IoTZone sẽ hướng dẫn bạn trong bài viết này.

ESP32 sẽ hoạt động như một máy chủ ngoại vi / BLE Server để hiển thị sự tồn tại của chúng. Điện thoại, laptop hoặc máy tính bảng của bạn sẽ giống một BLE Client / Bộ điều khiển để tương tác với ESP32.

Tổng quan dự án ESP32 Web BLE
Tổng quan dự án ESP32 Web BLE

Cấu trúc ESP32 GATT sẽ có một dịch vụ với 2 đặc điểm:

  • Đặc tính cảm biến: Lưu giá trị thay đổi theo thời gian (số đọc cảm biến)
  • Đặc tính LED: Lưu trạng thái của GPIO. Bằng cách thay đổi giá trị của đặc tính này, chúng ta có thể điều khiển đèn LED được kết nối với GPIO đó.
Giới thiệu dự án ESP32 Web BLE
Giới thiệu dự án ESP32 Web BLE

ESP32 sẽ ghi một giá trị mới vào đặc tính cảm biến theo định kỳ. Trình duyệt của bạn sẽ kết nối với ESP32 Web BLE và nhận được thông báo mỗi khi có giá trị thay đổi, và chúng hiển thị giá trị mới trên Website, ví dụ như hình bên dưới:

Màn hình giao diện Web điều khiển ESP32 Web BLE trên điện thoại

Ngoài ra, trình duyệt của bạn cũng kết nối với đặc tính LED và thay đổi giá trị của nó (nút ON / OFF) ở hình trên. ESP32 sẽ kiểm tra giá trị của đặc tính này có thay đổi không, rồi sau đó thay đổi trạng thái của GPIO tương ứng như bật / tắt đèn LED.

Code cho ESP32

Đoạn code sau giúp biến ESP32 thành một thiết bị BLE với 2 đặc tính mà mình đã giới thiệu ở trên.

Bạn chỉ cần upload code sau vào là ESP32 Web BLE sẽ hoạt động ngay lập tức:

#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; // Sử dụng chân GPIO bạn đã 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);

  // Tạo thiết bị BLE
  BLEDevice::init("ESP32");

  // Tạo BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Tạo BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Tạo 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();

  // Bắt đầu quảng cáo
  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");
    }
}

Đây là một trong những đoạn code đơn giản nhất cho ESP32 Web BLE, giúp biến ESP32 thành một thiết bị ESP32 để ghi các thay đổi về đặc tính.

Sau khi upload code lên, bạn hãy mở màn hình Serial Monitor trên Arduino, bạn sẽ thấy chúng đã tạo dịch vụ BLE và đang chờ kết nối từ Client.

Ngoài ra, bạn có thể dùng ứng dụng Web của mình bằng cách truy cập vào URL sau:

Tạo ứng dụng Web BLE

Bạn hãy tạo 1 tập tin HTML có tên là index.html với đoạn code sau (chúng chứa HTML để xây dựng Website và JavaScript để xử lý tác vụ về Web Bluetooth):

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

Kiểm tra hoạt động của ESP32 Web BLE

Lưu file index.html và kéo chúng vào trình duyệt, trên trình duyệt sẽ hiển thị trang sau:

Kiểm tra hoạt động của ESP32 Web BLE
Kiểm tra hoạt động của ESP32 Web BLE

Bạn hãy thử nghiệm hoạt động của Website trên với ESP32 đã nạp code bên trên nhé!

Ví dụ, khi nhấn vào nút Connect to BLE Device, một cửa sổ mới được bật lên và bạn sẽ thấy thiết bị ESP32 của mình trong đó:

Kết nối với ESP32 Web BLE
Kết nối với ESP32 Web BLE

Khi nhấn kết nối xong, bạn sẽ thấy trạng thái BLE đổi thành đã kết nối (Connected) và trên trang Web hiển thị các giá trị được ghi bởi ESP32 trên đặc tính cảm biến.

Đồng thời, trên Serial Monitor cũng hiển thị thông báo đã kết nối thành công và các giá trị được lưu trên đặc tính cảm biến.

Để demo hoạt động của ESP32 Web BLE, bạn hãy nhấn nút ON hoặc OFF trên trang Web và quan sát đèn LED của ESP32 nhé! Đèn LED sẽ được bật hoặc tắt tùy theo thao tác của bạn.

Bật tắt LED thông qua ESP32 Web BLE

Thay đổi giao diện ESP32 Web BLE đẹp hơn

Để giao diện web đẹp hơn, bạn có thể thêm một số đoạn mã CSS như sau:

index.html

Sao chép đoạn code sau vào file index.html của bạn, chúng bao gồm đoạn HTML mình đã cung cấp ở trên nhưng có thêm một số CSS trang trí giao diện Web:

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

style.css

Trên cùng thư mục của index.html, tạo một file có tên style.css như sau:

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;
}

Thêm favicon

Bạn có thể thêm favicon yêu thích của mình bằng cách tải thư mục .zip về và giải nén qua đường link này.

Demo

Sau khi tạo tất cả các file trên ở trong cùng 1 thư mục, hãy mở index.html trên trình duyệt Web của bạn.

Lúc đó, ứng dụng Web sẽ như sau, khá đẹp hơn phải không :3

Trang trí giao diện ESP32 Web BLE

Lời kết

Trên đây là hướng dẫn chi tiết với chủ đề ESP32 Web BLE. Khi nắm vững kỹ năng này, bạn có thể tạo các ứng dụng Web trực quan để tương tác với các thiết bị BLE từ bất kỳ trình duyệt Web nào có hỗ trợ Web BLE.

Hy vọng bài hướng dẫn này hữu ích với bạn. Đừng quên theo dõi các bài hướng dẫn ESP32 khác trên Website IoTZone nhé! Chúng tôi có da dạng bài viết hướng dẫn, với nhiều chủ đề khác nhau cho bạn tham khảo.

IoTZone – Chuyên cung cấp thiết bị điện tử & tài liệu cho Makers

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *