ESP32S3으로 소형의 테스트 보드를 제작 했으니 BLE 기능을 테스트 해보자
기존에 BLE 테스트는 BLE앱을 이용해서 테스트 했었는데 이번에는 좀더 새로운 기술인 Web Bleutooth로 테스트 해보면 좋을것 같다.
Web Bluetooth API는 웹 브라우저에서 Bluetooth 장치와의 상호작용을 가능하게 해주는 웹 API이다. 이를 통해 웹 애플리케이션이 Bluetooth를 이용해 주변 장치와 연결하고 데이터를 주고받을 있다. 웹 페이지가 Bluetooth 장치와 통신할 수 있기 때문에 별도의 애플리케션을 작성할 필요없이 웹브라우저에서 BLE장치를 테스트 할 수 있다.
크롬브라우저에서 Web Bluetooth를 사용하려면 Web Serial 테스트에서 설정했던 기본 설정이 필요 하다.
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 스타일을 변경하면 다양한 분위기의 웹페이지를 생성할수 있다.