Last active
November 3, 2023 18:16
-
-
Save vprusa/d956927704f0bda9de0d4b196341589e to your computer and use it in GitHub Desktop.
UART terminal (with performance test) sketch for google chrome's Web Bluetooth GATT communication with ESP32 BLE
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//https://googlechrome.github.io/samples/web-bluetooth/get-characteristics.html?service=6e400001-b5a3-f393-e0a9-e50e24dcca9e&characteristic_read=6e400002-b5a3-f393-e0a9-e50e24dcca9e | |
var bluetoothDevice; | |
var bluetoothDeviceWriteChar; | |
var bluetoothDeviceNotifyChar; | |
var timeNow, timeWriteLast, timeNotifyLast; | |
function str2ab(str) { | |
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char | |
var bufView = new Uint8Array(buf); | |
for (var i=0, strLen=str.length; i < strLen; i++) { | |
bufView[i] = str.charCodeAt(i); | |
} | |
return buf; | |
} | |
function ab2str(buf) { | |
return String.fromCharCode.apply(null, new Uint8Array(buf)); | |
} | |
function sendValue() { | |
if(bluetoothDeviceWriteChar) { | |
var toSend = $("#toSend").val(); | |
//console.log(toSend); | |
//console.log(bluetoothDeviceWriteChar); | |
var toSendAB = str2ab(toSend); | |
bluetoothDeviceWriteChar.writeValue(toSendAB); | |
} | |
} | |
function startPerfTest() { | |
console.log("startPerfTest"); | |
// TODO send chars/strings and calculate avg wait time | |
} | |
function showReceivedValue(value, timeNow, timeDiff) { | |
//log(value); | |
$('#content table tr:last').after( | |
"<tr><td>" + timeNow + "</td><td>" + timeDiff + "</td><td>" + value + "</td></tr>" | |
); | |
} | |
function connect() { | |
exponentialBackoff(5 /* max retries */, 2 /* seconds delay */, | |
function toTry() { | |
time('Connecting to Bluetooth Device... '); | |
return bluetoothDevice.gatt.connect(); | |
}, | |
function success() { | |
log('> Bluetooth Device connected.'); | |
}, | |
function fail() { | |
time('Failed to reconnect.'); | |
}); | |
} | |
function onDisconnected() { | |
log('> Bluetooth Device disconnected'); | |
connect(); | |
} | |
function onReconnectButtonrClick(){ | |
connect(); | |
} | |
function onButtonClick() { | |
let serviceUuid = document.querySelector('#service').value; | |
if (serviceUuid.startsWith('0x')) { | |
serviceUuid = parseInt(serviceUuid); | |
} | |
let name = document.querySelector('#name').value; | |
let characteristicWriteUuid = document.querySelector('#characteristic-write').value; | |
if (characteristicWriteUuid.startsWith('0x')) { | |
characteristicWriteUuid = parseInt(characteristicWriteUuid); | |
} | |
let characteristicNotifyUuid = document.querySelector('#characteristic-notify').value; | |
if (characteristicNotifyUuid.startsWith('0x')) { | |
characteristicNotifyUuid = parseInt(characteristicNotifyUuid); | |
} | |
log('Requesting Bluetooth Device...'); | |
function resolve(){ | |
if (bluetoothDevice) { | |
return bluetoothDevice; | |
} | |
} | |
let promise = new Promise( | |
function(resolve, reject) { | |
if(bluetoothDevice) { | |
connect(); | |
// TODO fix return values and reconnect ... | |
console.log(bluetoothDevice); | |
resolve(bluetoothDevice.gatt); | |
} else { | |
resolve( | |
navigator.bluetooth.requestDevice({ filters: [{services: [serviceUuid], name: [name]}]}).then(device => { | |
log('Connecting to GATT Server...'); | |
bluetoothDevice = device; | |
bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected); | |
return device.gatt.connect(); | |
}) | |
); | |
} | |
} | |
) | |
promise.then(server => { | |
log('Getting Service...'); | |
return server.getPrimaryService(serviceUuid); | |
}) | |
.then(service => { | |
log('Getting Characteristics...'); | |
if (characteristicWriteUuid) { | |
// Get all characteristics that match this UUID. | |
console.log(service); | |
// this requires both characteristics... | |
return Promise.all([service.getCharacteristics(characteristicWriteUuid), service.getCharacteristics(characteristicNotifyUuid)]); | |
} | |
// Get all characteristics. | |
return service.getCharacteristics(); | |
}) | |
.then(characteristics => { | |
log('> Characteristics: ' + | |
characteristics.map(c => c[0].uuid + " ("+(c[0].properties.write === true ? "WRITE" : (c[0].properties.notify === true ? "NOTIFY":"?"))+")").join('\n' + ' '.repeat(19))); | |
console.log(characteristics); | |
bluetoothDeviceWriteChar = characteristics[0][0]; | |
bluetoothDeviceNotifyChar = characteristics[1][0]; | |
//timeNow = window.performance.now(); | |
timeNow = $.now(); | |
console.log("Time timeNow: " + timeNow); | |
bluetoothDeviceNotifyChar.addEventListener("characteristicvaluechanged",async function(ev){ | |
console.log(ev); | |
var received = ab2str(ev.currentTarget.value.buffer); | |
if(received == -1 ) {return;} | |
console.log(received); | |
//timeNow = window.performance.now(); | |
timeNow = $.now(); | |
console.log(timeNow); | |
var timeDiff = timeNow - timeNotifyLast; | |
console.log("Time diff: " + timeDiff); | |
console.log(timeDiff); | |
if(lastTS){ | |
var difference = ev.timeStamp - lastTS; | |
console.log("Time difference: " + difference); | |
} | |
showReceivedValue(received, timeNow, timeDiff); | |
lastTS = ev.timeStamp; | |
timeNotifyLast = timeNow; | |
}); | |
bluetoothDeviceNotifyChar.startNotifications(); | |
}) | |
.catch(error => { | |
log('Argh! ' + error); | |
}); | |
} | |
var lastTS; | |
/* Utils */ | |
// This function keeps calling "toTry" until promise resolves or has | |
// retried "max" number of times. First retry has a delay of "delay" seconds. | |
// "success" is called upon success. | |
function exponentialBackoff(max, delay, toTry, success, fail) { | |
toTry().then(result => success(result)) | |
.catch(_ => { | |
if (max === 0) { | |
return fail(); | |
} | |
time('Retrying in ' + delay + 's... (' + max + ' tries left)'); | |
setTimeout(function() { | |
exponentialBackoff(--max, delay * 2, toTry, success, fail); | |
}, delay * 1000); | |
}); | |
} | |
function time(text) { | |
log('[' + new Date().toJSON().substr(11, 8) + '] ' + text); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Video: https://www.youtube.com/watch?v=oCMOYS71NIU | |
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp | |
Ported to Arduino ESP32 by Evandro Copercini | |
6e400003-b5a3-f393-e0a9-e50e24dcca9e | |
Create a BLE server that, once we receive a connection, will send periodic notifications. | |
The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E | |
Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" | |
Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY" | |
6e400001-b5a3-f393-e0a9-e50e24dcca9e | |
6e400002-b5a3-f393-e0a9-e50e24dcca9e | |
6e400003-b5a3-f393-e0a9-e50e24dcca9e | |
The design of creating the BLE server is: | |
1. Create a BLE Server | |
2. Create a BLE Service | |
3. Create a BLE Characteristic on the Service | |
4. Create a BLE Descriptor on the characteristic | |
5. Start the service. | |
6. Start advertising. | |
In this example rxValue is the data received (only accessible inside that function). | |
And txValue is the data to be sent, in this example just a byte incremented every second. | |
*/ | |
#include <BLEDevice.h> | |
#include <BLEServer.h> | |
#include <BLEUtils.h> | |
#include <BLE2902.h> | |
unsigned long timeNow, timeLastRead, timeLastWrite; | |
BLEServer *pServer = NULL; | |
BLECharacteristic * pTxCharacteristic; | |
bool deviceConnected = false; | |
bool oldDeviceConnected = false; | |
uint8_t txValue = 0; | |
// See the following for generating UUIDs: | |
// https://www.uuidgenerator.net/ | |
#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" | |
#defiine TEST_PERF | |
class MyServerCallbacks: public BLEServerCallbacks { | |
void onConnect(BLEServer* pServer) { | |
deviceConnected = true; | |
}; | |
void onDisconnect(BLEServer* pServer) { | |
deviceConnected = false; | |
} | |
}; | |
class MyCallbacks: public BLECharacteristicCallbacks { | |
void onWrite(BLECharacteristic *pCharacteristic) { | |
std::string rxValue = pCharacteristic->getValue(); | |
timeNow = micros(); | |
if (rxValue.length() > 0) { | |
Serial.print(timeNow); | |
Serial.print(" "); | |
unsigned long timeDiff = timeNow-timeLastRead; | |
Serial.print(timeDiff); | |
//Serial.println("*********"); | |
Serial.print(" Received: "); | |
for (int i = 0; i < rxValue.length(); i++) | |
Serial.print(rxValue[i]); | |
Serial.println(); | |
//Serial.println("*********"); | |
timeLastRead=timeNow; | |
} | |
} | |
}; | |
void setup() { | |
Serial.begin(115200); | |
// Create the BLE Device | |
BLEDevice::init("UART Service"); | |
// 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 | |
pTxCharacteristic = pService->createCharacteristic( | |
CHARACTERISTIC_UUID_TX, | |
BLECharacteristic::PROPERTY_NOTIFY | |
); | |
pTxCharacteristic->addDescriptor(new BLE2902()); | |
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( | |
CHARACTERISTIC_UUID_RX, | |
BLECharacteristic::PROPERTY_WRITE | |
); | |
pRxCharacteristic->setCallbacks(new MyCallbacks()); | |
// Start the service | |
pService->start(); | |
// Start advertising | |
pServer->getAdvertising()->start(); | |
Serial.println("Waiting a client connection to notify..."); | |
timeNow = micros(); | |
Serial.print("Current time is: "); | |
Serial.println(timeNow); | |
} | |
uint8_t* string2char(String str){ | |
return reinterpret_cast<uint8_t*>(&str[0]); | |
} | |
// counter is here as a prevention from waiting first measurement and when something unexpected happens once per time... | |
int congestionWaitCounter = -1; | |
void loop() { | |
if (deviceConnected) { | |
if (Serial.available()){ | |
timeNow = micros(); | |
String str = Serial.readString(); | |
#ifdef TEST_PERF | |
for(int i =0; i<10; i++){ | |
#endif | |
timeNow = micros(); | |
Serial.print(timeNow); | |
Serial.print(" "); | |
unsigned long timeDiff = timeNow-timeLastWrite; | |
Serial.print(timeDiff); | |
Serial.print(" wrote: "); | |
Serial.println(str); | |
pTxCharacteristic->setValue(string2char(str), str.length()); | |
pTxCharacteristic->notify(); | |
timeLastWrite=timeNow; | |
#ifdef TEST_PERF | |
} | |
#endif | |
} | |
} | |
// 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 | |
Serial.println("connected"); | |
oldDeviceConnected = deviceConnected; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<META charset="UTF-8" /> | |
<title> | |
BLE GATT UART test | |
</title> | |
<script | |
src="https://code.jquery.com/jquery-3.4.1.min.js" | |
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" | |
crossorigin="anonymous"></script> | |
<script src="characteristics.js"></script> | |
<style> | |
#content table th { | |
padding-left: 10px ; | |
} | |
</style> | |
</head> | |
<body> | |
<form id="connectForm" method="GET"> | |
<input id="name" type="text" autofocus="" placeholder="Bluetooth Name" value="UART Service"> | |
<input id="service" type="text" placeholder="Bluetooth Service" value="6e400001-b5a3-f393-e0a9-e50e24dcca9e"> | |
<input id="characteristic-write" type="text" placeholder="Bluetooth Characteristic" value="6e400002-b5a3-f393-e0a9-e50e24dcca9e"> | |
<input id="characteristic-notify" type="text" placeholder="Bluetooth Characteristic" value="6e400003-b5a3-f393-e0a9-e50e24dcca9e"> | |
<button onclick="onButtonClick()">Get characteristics</button> | |
<button onclick="onReconnectButtonrClick()">Reconnect</button> | |
</form> | |
<form id="terminalInput" method="GET"> | |
<input id="toSend" type="text" placeholder="" value="test"> | |
<button onclick="sendValue()">Send</button> | |
<button onclick="startPerfTest()">Perf</button> | |
</form> | |
<p>This page contains testing for GATT...</p> | |
<div id="output" class="output"> | |
<div id="content"> | |
<table><tr><th>Time</th><th>Diff</th><th>Msg</th></tr></table> | |
</div> | |
<div id="status"></div> | |
<pre id="log"></pre> | |
</div> | |
<script> | |
[].forEach.call( | |
document.querySelectorAll('form'), | |
function (el) { | |
console.log(el); | |
el.addEventListener('submit', function(event) { | |
event.stopPropagation(); | |
event.preventDefault(); | |
}); | |
} | |
); | |
</script> | |
<script> | |
//log = ChromeSamples.log; | |
function elemLog(msg){ | |
$("#log").append(msg); | |
$("#log").append("<br>"); | |
} | |
log = elemLog | |
function isWebBluetoothEnabled() { | |
if (navigator.bluetooth) { | |
return true; | |
} else { | |
//ChromeSamples.setStatus('Web Bluetooth API is not available.\n' + | |
// 'Please make sure the "Experimental Web Platform features" flag is enabled.'); | |
return false; | |
} | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment