/** * 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 }; })();