Last active
August 8, 2024 11:10
-
-
Save loginov-rocks/8aeb19f207b1da53eaa553faa7aa8a51 to your computer and use it in GitHub Desktop.
How to build a web app for your own BLE device: Habrahabr
This file contains hidden or 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"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link href="styles.css" rel="stylesheet"> | |
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png"> | |
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png"> | |
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png"> | |
<link rel="manifest" href="manifest.json"> | |
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#5bbad5"> | |
<meta name="theme-color" content="#ffffff"> | |
</head> | |
<body> | |
<button id="connect" type="button">Connect</button> | |
<button id="disconnect" type="button">Disconnect</button> | |
<div id="terminal"></div> | |
<form id="send-form"> | |
<input id="input" type="text"> | |
<button type="submit">Send</button> | |
</form> | |
<script src="main.js"></script> | |
<script src="companion.js" data-service-worker="sw.js"></script> | |
</body> | |
</html> |
This file contains hidden or 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
// Получение ссылок на элементы UI | |
let connectButton = document.getElementById('connect'); | |
let disconnectButton = document.getElementById('disconnect'); | |
let terminalContainer = document.getElementById('terminal'); | |
let sendForm = document.getElementById('send-form'); | |
let inputField = document.getElementById('input'); | |
// Подключение к устройству при нажатии на кнопку Connect | |
connectButton.addEventListener('click', function() { | |
connect(); | |
}); | |
// Отключение от устройства при нажатии на кнопку Disconnect | |
disconnectButton.addEventListener('click', function() { | |
disconnect(); | |
}); | |
// Обработка события отправки формы | |
sendForm.addEventListener('submit', function(event) { | |
event.preventDefault(); // Предотвратить отправку формы | |
send(inputField.value); // Отправить содержимое текстового поля | |
inputField.value = ''; // Обнулить текстовое поле | |
inputField.focus(); // Вернуть фокус на текстовое поле | |
}); | |
// Кэш объекта выбранного устройства | |
let deviceCache = null; | |
// Кэш объекта характеристики | |
let characteristicCache = null; | |
// Промежуточный буфер для входящих данных | |
let readBuffer = ''; | |
// Запустить выбор Bluetooth устройства и подключиться к выбранному | |
function connect() { | |
return (deviceCache ? Promise.resolve(deviceCache) : | |
requestBluetoothDevice()). | |
then(device => connectDeviceAndCacheCharacteristic(device)). | |
then(characteristic => startNotifications(characteristic)). | |
catch(error => log(error)); | |
} | |
// Запрос выбора Bluetooth устройства | |
function requestBluetoothDevice() { | |
log('Requesting bluetooth device...'); | |
return navigator.bluetooth.requestDevice({ | |
filters: [{services: [0xFFE0]}], | |
}). | |
then(device => { | |
log('"' + device.name + '" bluetooth device selected'); | |
deviceCache = device; | |
deviceCache.addEventListener('gattserverdisconnected', | |
handleDisconnection); | |
return deviceCache; | |
}); | |
} | |
// Обработчик разъединения | |
function handleDisconnection(event) { | |
let device = event.target; | |
log('"' + device.name + | |
'" bluetooth device disconnected, trying to reconnect...'); | |
connectDeviceAndCacheCharacteristic(device). | |
then(characteristic => startNotifications(characteristic)). | |
catch(error => log(error)); | |
} | |
// Подключение к определенному устройству, получение сервиса и характеристики | |
function connectDeviceAndCacheCharacteristic(device) { | |
if (device.gatt.connected && characteristicCache) { | |
return Promise.resolve(characteristicCache); | |
} | |
log('Connecting to GATT server...'); | |
return device.gatt.connect(). | |
then(server => { | |
log('GATT server connected, getting service...'); | |
return server.getPrimaryService(0xFFE0); | |
}). | |
then(service => { | |
log('Service found, getting characteristic...'); | |
return service.getCharacteristic(0xFFE1); | |
}). | |
then(characteristic => { | |
log('Characteristic found'); | |
characteristicCache = characteristic; | |
return characteristicCache; | |
}); | |
} | |
// Включение получения уведомлений об изменении характеристики | |
function startNotifications(characteristic) { | |
log('Starting notifications...'); | |
return characteristic.startNotifications(). | |
then(() => { | |
log('Notifications started'); | |
characteristic.addEventListener('characteristicvaluechanged', | |
handleCharacteristicValueChanged); | |
}); | |
} | |
// Получение данных | |
function handleCharacteristicValueChanged(event) { | |
let value = new TextDecoder().decode(event.target.value); | |
for (let c of value) { | |
if (c === '\n') { | |
let data = readBuffer.trim(); | |
readBuffer = ''; | |
if (data) { | |
receive(data); | |
} | |
} | |
else { | |
readBuffer += c; | |
} | |
} | |
} | |
// Обработка полученных данных | |
function receive(data) { | |
log(data, 'in'); | |
} | |
// Вывод в терминал | |
function log(data, type = '') { | |
terminalContainer.insertAdjacentHTML('beforeend', | |
'<div' + (type ? ' class="' + type + '"' : '') + '>' + data + '</div>'); | |
} | |
// Отключиться от подключенного устройства | |
function disconnect() { | |
if (deviceCache) { | |
log('Disconnecting from "' + deviceCache.name + '" bluetooth device...'); | |
deviceCache.removeEventListener('gattserverdisconnected', | |
handleDisconnection); | |
if (deviceCache.gatt.connected) { | |
deviceCache.gatt.disconnect(); | |
log('"' + deviceCache.name + '" bluetooth device disconnected'); | |
} | |
else { | |
log('"' + deviceCache.name + | |
'" bluetooth device is already disconnected'); | |
} | |
} | |
if (characteristicCache) { | |
characteristicCache.removeEventListener('characteristicvaluechanged', | |
handleCharacteristicValueChanged); | |
characteristicCache = null; | |
} | |
deviceCache = null; | |
} | |
// Отправить данные подключенному устройству | |
function send(data) { | |
data = String(data); | |
if (!data || !characteristicCache) { | |
return; | |
} | |
data += '\n'; | |
if (data.length > 20) { | |
let chunks = data.match(/(.|[\r\n]){1,20}/g); | |
writeToCharacteristic(characteristicCache, chunks[0]); | |
for (let i = 1; i < chunks.length; i++) { | |
setTimeout(() => { | |
writeToCharacteristic(characteristicCache, chunks[i]); | |
}, i * 100); | |
} | |
} | |
else { | |
writeToCharacteristic(characteristicCache, data); | |
} | |
log(data, 'out'); | |
} | |
// Записать значение в характеристику | |
function writeToCharacteristic(characteristic, data) { | |
characteristic.writeValue(new TextEncoder().encode(data)); | |
} |
This file contains hidden or 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
#terminal div { | |
color: gray; | |
} | |
#terminal div.out { | |
color: red; | |
} | |
#terminal div.in { | |
color: blue; | |
} |
This file contains hidden or 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
importScripts('sw-toolbox.js'); | |
toolbox.precache([ | |
'companion.js', | |
'index.html', | |
'main.js', | |
'styles.css', | |
]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment