ESPRESSIF/ESP32-S3

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

nexp 2024. 3. 16. 21:12

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 스타일을 변경하면 다양한 분위기의 웹페이지를 생성할수 있다.



반응형