168 lines
4.7 KiB
JavaScript
168 lines
4.7 KiB
JavaScript
/**
|
|
* BLE Service — Web Bluetooth connection to Bosch Smart System
|
|
*
|
|
* Connects via the correct Bosch eaa2 service UUID,
|
|
* subscribes to notifications on char 0011,
|
|
* and passes decoded data to the app.
|
|
*/
|
|
|
|
const BLEService = (() => {
|
|
let device = null;
|
|
let server = null;
|
|
let characteristic = null;
|
|
let writeCharacteristic = 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 {
|
|
// Request device — use namePrefix filters + optionalServices
|
|
// (some Bosch bikes don't advertise the service UUID directly)
|
|
device = await navigator.bluetooth.requestDevice({
|
|
filters: [
|
|
{ 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);
|
|
|
|
// Get notify characteristic (0011)
|
|
characteristic = await service.getCharacteristic(BoschProtocol.CHAR_UUID);
|
|
|
|
// Try to get write characteristic (0012) for potential init commands
|
|
try {
|
|
writeCharacteristic = await service.getCharacteristic(BoschProtocol.WRITE_UUID);
|
|
console.log('Write characteristic (0012) available');
|
|
} catch (e) {
|
|
console.log('Write characteristic not available:', e.message);
|
|
}
|
|
|
|
// Subscribe to notifications IMMEDIATELY
|
|
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;
|
|
}
|
|
writeCharacteristic = 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;
|
|
|
|
// Log raw bytes for debugging
|
|
const raw = [];
|
|
for (let i = 0; i < dataView.byteLength; i++) {
|
|
raw.push(dataView.getUint8(i).toString(16).toUpperCase().padStart(2, '0'));
|
|
}
|
|
console.log('BLE notification:', raw.join('-'), `(${dataView.byteLength}B)`);
|
|
|
|
const parsed = BoschProtocol.parseNotification(dataView);
|
|
if (onDataCallback) {
|
|
onDataCallback(parsed);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle unexpected disconnection
|
|
*/
|
|
function onDisconnected() {
|
|
console.warn('BLE device disconnected');
|
|
characteristic = null;
|
|
writeCharacteristic = 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
|
|
};
|
|
})();
|