OpusBike/webapp/js/bleService.js

142 lines
3.7 KiB
JavaScript

/**
* BLE Service — Web Bluetooth connection to Bosch Smart System
*/
const BLEService = (() => {
let device = null;
let server = null;
let characteristic = null;
let onDataCallback = null;
let onStatusCallback = null;
/**
* Check if Web Bluetooth is available
*/
function isAvailable() {
return !!(navigator.bluetooth && navigator.bluetooth.requestDevice);
}
/**
* Connect to a Bosch Smart System eBike
*/
async function connect() {
if (!isAvailable()) {
throw new Error('Web Bluetooth is not supported on this device');
}
updateStatus('scanning');
try {
device = await navigator.bluetooth.requestDevice({
filters: [
{ services: [BoschProtocol.SERVICE_UUID] },
{ namePrefix: 'smart system' },
{ namePrefix: 'Bosch' },
{ namePrefix: 'BRC' }
],
optionalServices: [BoschProtocol.SERVICE_UUID]
});
device.addEventListener('gattserverdisconnected', onDisconnected);
updateStatus('connecting');
server = await device.gatt.connect();
updateStatus('discovering');
const service = await server.getPrimaryService(BoschProtocol.SERVICE_UUID);
characteristic = await service.getCharacteristic(BoschProtocol.CHAR_UUID);
// Subscribe to notifications
await characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged', onCharacteristicChanged);
updateStatus('connected');
console.log('BLE connected to:', device.name);
return device.name;
} catch (error) {
updateStatus('disconnected');
if (error.name === 'NotFoundError') {
throw new Error('No bike found. Make sure the Bosch system is powered on.');
}
throw error;
}
}
/**
* Disconnect from the bike
*/
function disconnect() {
if (characteristic) {
characteristic.removeEventListener('characteristicvaluechanged', onCharacteristicChanged);
characteristic = null;
}
if (device && device.gatt.connected) {
device.gatt.disconnect();
}
device = null;
server = null;
updateStatus('disconnected');
}
/**
* Handle incoming BLE notifications
*/
function onCharacteristicChanged(event) {
const dataView = event.target.value;
const parsed = BoschProtocol.parseNotification(dataView);
if (onDataCallback) {
onDataCallback(parsed);
}
}
/**
* Handle unexpected disconnection
*/
function onDisconnected() {
console.warn('BLE device disconnected');
characteristic = null;
server = null;
updateStatus('disconnected');
}
/**
* Update connection status
*/
function updateStatus(status) {
if (onStatusCallback) {
onStatusCallback(status);
}
}
/**
* Set callback for incoming data
*/
function onData(callback) {
onDataCallback = callback;
}
/**
* Set callback for status changes
*/
function onStatus(callback) {
onStatusCallback = callback;
}
/**
* Check if currently connected
*/
function isConnected() {
return device && device.gatt && device.gatt.connected;
}
return {
isAvailable,
connect,
disconnect,
onData,
onStatus,
isConnected
};
})();