본문 바로가기

ESPRESSIF/ESP32-S3

[ESP32S3-SSM] BLE 테스트 - Web Bluetooth API (Web BLE) 테스트

ESP32S3으로 소형의 테스트 보드를 제작 했으니 BLE 기능을 테스트 해보자

기존에 BLE 테스트는 BLE앱을 이용해서 테스트 했었는데 이번에는 좀더 새로운 기술인 Web Bleutooth로 테스트 해보면 좋을것 같다.

 

Web Bluetooth API는 웹 브라우저에서 Bluetooth 장치와의 상호작용을 가능하게 해주는 웹 API이다. 이를 통해 웹 애플리케이션이 Bluetooth를 이용해 주변 장치와 연결하고 데이터를 주고받을 있다. 웹 페이지가 Bluetooth 장치와 통신할 수 있기 때문에 별도의 애플리케션을 작성할 필요없이 웹브라우저에서 BLE장치를 테스트 할 수 있다.

 

크롬브라우저에서 Web Bluetooth를 사용하려면 Web Serial 테스트에서 설정했던 기본 설정이 필요 하다. 

https://nexp.tistory.com/3690

 

RP2040 - Serial Web 테스트

Serial Web은 Chrome 89에서 제공하는 웹페이지 상에서 USB 하드웨어 장치에 접근할 수 있는 API 이다. Web Serial API는 웹사이트가 JavaScript를 사용하여 직렬 장치에서 읽고 쓸 수 있다. Serial Web의 장점은

nexp.tistory.com

 

 

기본 BLE 코드는 ESP32-C3 에서 테스트 했던 BLE_UART로 테스트 하면 된다.

예전에 했던 코드로 컴파일 하면 에러가  많이 발생한다.
BLE 라이브러리 링크가 좀 이상한것 같은데…

 

D:\WORK\Arduino\ESP32S3\esp32_web_ble1\ble.h: In member function 'virtual void PowerCharacteristicCallbacks::onWrite(BLECharacteristic*)':
D:\WORK\Arduino\ESP32S3\esp32_web_ble1\ble.h:39:51: error: conversion from 'String' to non-scalar type 'std::string' {aka 'std::__cxx11::basic_string<char>'} requested
   39 |       std::string value = characteristic->getValue();
      |                           ~~~~~~~~~~~~~~~~~~~~~~~~^~

 

 

아래와 같이 수정하니 정상동작 한다.

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

 



다른 프로그램에서는 잘동작하는데… Web에서는 인식을 하지 못하는 현상이 있다.

서비스 등록시 READ/NOTIFY 가능하도록 하니 잘 동작 한다.

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

*/


  pRxCharacteristic = pService->createCharacteristic(
                                CHARACTERISTIC_UUID_RX,
                                BLECharacteristic::PROPERTY_READ   |
                                BLECharacteristic::PROPERTY_WRITE  |
                                BLECharacteristic::PROPERTY_NOTIFY
                              );

 

 

ESP32S3의 BLE 코드는 BLE to UART 서비스를 이용하였다. 데이터 송수신이 표준화 되어 있어 다양한 어플리케이션에 적용하기에 좋다. 

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

// ----------------------------------------------------------------------------
// Definition of macros
// ----------------------------------------------------------------------------
#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

#define HTTP_PORT 80
#define LED1_PIN    17
#define SW1_PIN    0

#define ADC1_PIN     10   
#define Led1On()	digitalWrite(LED1_PIN, 1)
#define Led1Off()	digitalWrite(LED1_PIN, 0)


BLEServer* pServer = NULL;

BLECharacteristic* pRxCharacteristic = NULL;
BLECharacteristic* pTxCharacteristic = NULL;
BLECharacteristic* pBrightnessCharacteristic = NULL;


bool deviceConnected = false;
bool oldDeviceConnected = false;

uint8_t power = 1;


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

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

class RxCharacteristicCallbacks: public BLECharacteristicCallbacks
{
    void onWrite(BLECharacteristic *characteristic) {
      std::string value = characteristic->getValue();
      if (value.length() > 0) {
        Serial.print("Led: ");

        if (value.length() == 1) {
          power = value[0];
          if (power > 1) power = 1;

          if(power)Led1On();
          else Led1Off();
          Serial.println(power);
        }
      }
    }
};


void setupBLE() {
  uint8_t paletteCount = 1;
  // Create the BLE Device
  BLEDevice::init("ESP32");

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

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

  Serial.printf("%x", BLEUUID(SERVICE_UUID));
  Serial.printf("%x", BLEUUID(CHARACTERISTIC_UUID_RX));
  
  // Create BLE Characteristics
  pRxCharacteristic = pService->createCharacteristic(
                                CHARACTERISTIC_UUID_RX,
                                BLECharacteristic::PROPERTY_READ   |
                                BLECharacteristic::PROPERTY_WRITE  |
                                BLECharacteristic::PROPERTY_NOTIFY
                              );

  pRxCharacteristic->setCallbacks(new RxCharacteristicCallbacks());
  pRxCharacteristic->setValue(&power, 1);
  pRxCharacteristic->addDescriptor(new BLE2902());


  pTxCharacteristic = pService->createCharacteristic(
                                CHARACTERISTIC_UUID_TX,
                                BLECharacteristic::PROPERTY_NOTIFY
                              );
  pTxCharacteristic->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 for a client connection to notify...");
}

// ----------------------------------------------------------------------------
// Initialization
// ----------------------------------------------------------------------------

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

    setupBLE();

	pinMode (LED1_PIN , OUTPUT);
    Led1On();

    pinMode(SW1_PIN, INPUT_PULLUP);    
}

// ----------------------------------------------------------------------------
// Main control loop
// ----------------------------------------------------------------------------
uint8_t txValue = 0;
int adc_send_flag;

void loop() {
 // disconnecting
  if (!deviceConnected && oldDeviceConnected)
  {
    delay(500);                  // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }

  // connecting
  if (deviceConnected && !oldDeviceConnected)
  {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
    Serial.println("connecting");
  }
}

 

 

 

Web Bluetooth 웹페이지 코드는 먼저 가장 간단히 BLE 장치를 검색하고 접속하며 LED를 On/Off 하는 버튼을 추가한 HTML코드를 작성 하였다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="EUC-KR">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web BLE 예제</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        button {
            padding: 10px 15px;
            font-size: 16px;
            margin-top: 10px;
        }
        #output {
            margin-top: 20px;
            white-space: pre-wrap;
        }
    </style>
</head>
<body>
    <h1>Web BLE 예제</h1>
    <button id="scanButton">BLE 장치 검색</button>
    <button id="sendButton" disabled>1 전송</button>
    <div id="output"></div>

    <script>
        const SERVICE_UUID = 0x6e40; // 16비트 UUID
        let characteristic; // 특성 저장
        let server; // 서버 저장
        let isConnected = false; // 연결 상태 저장
        let currentValue = 1; // 전송할 현재 값 (1 또는 0)

        document.getElementById('scanButton').addEventListener('click', connectOrDisconnectDevice);
        document.getElementById('sendButton').addEventListener('click', sendData);

        async function connectOrDisconnectDevice() {
            const output = document.getElementById('output');
            output.textContent = ''; // 초기화

            if (!isConnected) {
                try {
                    const device = await navigator.bluetooth.requestDevice({
                        filters: [{ services: [SERVICE_UUID] }],
                        optionalServices: ['6e400001-b5a3-f393-e0a9-e50e24dcca9e']
                    });

                    output.textContent += '장치 선택됨: ' + device.name + '\n';

                    server = await device.gatt.connect();
                    output.textContent += '서버에 연결됨.\n';

                    const service = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
                    output.textContent += '서비스에 접근함.\n';

                    characteristic = await service.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e');
                    output.textContent += '특성에 접근함.\n';

                    const value = await characteristic.readValue();
                    output.textContent += '읽은 값: ' + new Uint8Array(value.buffer) + '\n';

                    document.getElementById('sendButton').disabled = false; // 버튼 활성화
                    isConnected = true; // 연결 상태 업데이트

                } catch (error) {
                    output.textContent += '오류: ' + error + '\n';
                }
            } else {
                // 이미 연결되어 있는 경우 disconnect
                if (server) {
                    await server.disconnect();
                    output.textContent += '서버 연결 해제됨.\n';
                    document.getElementById('sendButton').disabled = true; // 버튼 비활성화
                    isConnected = false; // 연결 상태 업데이트
                    characteristic = null; // 특성 초기화
                }
            }
        }

        async function sendData() {
            if (!characteristic) {
                console.error('특성이 연결되어 있지 않습니다. 재연결 시도합니다.');
                try {
                    await reconnect();
                } catch (error) {
                    console.error('재연결 실패:', error);
                    return;
                }
            }

            // 현재 상태에 따라 1 또는 0을 전송
            const data = new Uint8Array([currentValue]);

            try {
                await characteristic.writeValue(data);
                document.getElementById('output').textContent += '전송된 값: ' + currentValue + '\n';
                
                // 값 토글
                currentValue = currentValue === 1 ? 0 : 1;

            } catch (error) {
                console.error('전송 오류:', error);
            }
        }

        async function reconnect() {
            const output = document.getElementById('output');
            if (!server) {
                throw new Error('서버가 없습니다. 장치를 다시 연결해야 합니다.');
            }

            const device = await server.device; // 서버에서 장치 정보 가져오기
            if (device) {
                output.textContent += '장치 재연결 시도 중...\n';
                server = await device.gatt.connect();
                output.textContent += '서버에 다시 연결됨.\n';

                const service = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
                characteristic = await service.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e');
                output.textContent += '특성에 다시 접근함.\n';
            }
        }
    </script>
</body>
</html>

 

 

 

 

기본 코드에 CSS 스타일을 적용해서 좀더 보기 좋은 웹페이지를 만들었다.

 

 

 

이제 웹페이지로 데이터 전송하는 테스트를 해 보자.

스위치를 누르면 값이 전송 되고 주기적으로 ADC값을 전송하는 코드를 추가 하자

    // 스위치가 눌렸는지 확인
    if (digitalRead(SW1_PIN) == 0) { // 스위치가 눌리면 LOW
        // 클라이언트에게 STATUS_OK 메시지 전송
        if(deviceConnected)
        {
            adc_send_flag ^= 1;

            pTxCharacteristic->setValue(&txValue, 1);
            pTxCharacteristic->notify();            

            Serial.print("txValue:");
            Serial.println(txValue);

            txValue++;
        }
        else
        {
          Serial.println("not deviceConnected");  
        }
        
        delay(300); // debounce delay
    }

    if(deviceConnected && adc_send_flag)
    {
        txValue = map(analogRead(ADC1_PIN), 0, 1023, 0, 255);
        pTxCharacteristic->setValue(&txValue, 1);
        pTxCharacteristic->notify();            

        Serial.print("adc:");
        Serial.println(txValue);
        delay(100); // debounce delay
    }

 

 

Web Bluetooth 웹페이지에서는 수신된 데이터를 출력하고 그래프로 표시 하도록 했다.

 

 

Web Bluetooth의 가장 큰 장점은 애플리케이션을 쉽게 변경 할수 있어 다양한 BLE 관련 테스트를 간단하게 할수 있다.

 

 

CSS 스타일을 변경하면 다양한 분위기의 웹페이지를 생성할수 있다.



반응형