139 lines
6.4 KiB
Plaintext
139 lines
6.4 KiB
Plaintext
// H5平台 Web Bluetooth 设备扫描实现
|
||
import { DeviceManager } from './device-manager.uts';
|
||
import { ServiceManager } from './service-manager.uts';
|
||
import type { BleDevice, BleOptions, BleConnectOptionsExt, BleDataReceivedCallback, BleConnectionStateChangeCallback } from '../interface.uts'
|
||
|
||
const DEFAULT_OPTIONAL_SERVICES = [
|
||
'00001800-0000-1000-8000-00805f9b34fb', // GAP
|
||
'0000180a-0000-1000-8000-00805f9b34fb', // Device Information
|
||
'0000180f-0000-1000-8000-00805f9b34fb', // Battery
|
||
'00001812-0000-1000-8000-00805f9b34fb', // Human Interface Device
|
||
'0000fe59-0000-1000-8000-00805f9b34fb', // Nordic DFU / vendor specific
|
||
'6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART primary service
|
||
'6e400010-b5a3-f393-e0a9-e50e24dcca9e', // Custom health service (observed on Android)
|
||
'6e400020-b5a3-f393-e0a9-e50e24dcca9e' // Additional vendor service
|
||
];
|
||
|
||
export const BLE_SERVICE_PREFIXES = [
|
||
'6e4000',
|
||
'0000180f',
|
||
'00001812',
|
||
'0000fe59'
|
||
];
|
||
|
||
function normalizeServiceUuid(uuid: string): string {
|
||
if (!uuid) return uuid;
|
||
let u = uuid.trim().toLowerCase();
|
||
if (u.startsWith('0x')) {
|
||
u = u.slice(2);
|
||
}
|
||
if (/^[0-9a-f]{4}$/.test(u)) {
|
||
return `0000${u}-0000-1000-8000-00805f9b34fb`;
|
||
}
|
||
return u;
|
||
}
|
||
|
||
function mergeOptionalServices(userServices?: string[]): string[] {
|
||
const set = new Set<string>();
|
||
DEFAULT_OPTIONAL_SERVICES.forEach((svc) => set.add(normalizeServiceUuid(svc)));
|
||
if (userServices != null) {
|
||
for (let i = 0; i < userServices.length; i++) {
|
||
const normalized = normalizeServiceUuid(userServices[i]);
|
||
if (normalized != null && normalized !== '') {
|
||
set.add(normalized);
|
||
}
|
||
}
|
||
}
|
||
return Array.from(set);
|
||
}
|
||
|
||
// 实例化各个管理器
|
||
const deviceManager = new DeviceManager();
|
||
const serviceManager = new ServiceManager();
|
||
|
||
// 导出简化接口
|
||
export const scanDevices = async (options?: { optionalServices?: string[] }) => {
|
||
const mergedOptions = options != null ? { ...options } : {};
|
||
mergedOptions.optionalServices = mergeOptionalServices(options?.optionalServices ?? []);
|
||
return deviceManager.startScan(mergedOptions);
|
||
};
|
||
export const connectDevice = async (deviceId: string, options?: BleConnectOptionsExt) => deviceManager.connectDevice(deviceId, options);
|
||
export const disconnectDevice = async (deviceId: string) => deviceManager.disconnectDevice(deviceId);
|
||
export const getConnectedDevices = () => deviceManager.getConnectedDevices();
|
||
export const getKnownDevices = () => Object.keys((deviceManager as any).devices || {})
|
||
export const discoverServices = async (deviceId: string) => {
|
||
// 获取 server 实例
|
||
const server = deviceManager.getServer(deviceId)
|
||
if (!server) throw new Error(`设备未连接: ${deviceId}`)
|
||
return serviceManager.discoverServices(deviceId, server);
|
||
};
|
||
export const getCharacteristics = async (deviceId: string, serviceId: string) => serviceManager.getCharacteristics(deviceId, serviceId);
|
||
export const writeCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string, data: string | ArrayBuffer) => serviceManager.writeCharacteristic(deviceId, serviceId, characteristicId, data);
|
||
export const subscribeCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string, callback) => {
|
||
console.log('[bluetooth_manager] subscribeCharacteristic called:', deviceId, serviceId, characteristicId)
|
||
return serviceManager.subscribeCharacteristic(deviceId, serviceId, characteristicId, callback);
|
||
}
|
||
export const unsubscribeCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string) => serviceManager.unsubscribeCharacteristic(deviceId, serviceId, characteristicId);
|
||
export const readCharacteristic = async (deviceId: string, serviceId: string, characteristicId: string) => serviceManager.readCharacteristic(deviceId, serviceId, characteristicId);
|
||
export const sendCommand = async (deviceId: string, serviceId: string, writeCharId: string, notifyCharId: string, command: string, params: any = null, timeout: number = 5000) => dataProcessor.sendAndReceive(deviceId, serviceId, writeCharId, notifyCharId, command, params, timeout);
|
||
// Event adapter helpers: translate DeviceManager callbacks into payload objects
|
||
export const onDeviceFound = (listener) => deviceManager.onDeviceFound((device) => {
|
||
try { listener({ device }); } catch (e) { /* ignore listener errors */ }
|
||
});
|
||
|
||
export const onScanFinished = (listener) => deviceManager.onScanFinished(() => {
|
||
try { listener({}); } catch (e) {}
|
||
});
|
||
|
||
export const onConnectionStateChange = (listener) => deviceManager.onConnectionStateChange((deviceId, state) => {
|
||
try { listener({ device: { deviceId }, state }); } catch (e) {}
|
||
});
|
||
|
||
/**
|
||
* 自动连接并初始化蓝牙设备,获取可用serviceId、writeCharId、notifyCharId
|
||
* @param deviceId 设备ID
|
||
* @returns {Promise<{serviceId: string, writeCharId: string, notifyCharId: string}>}
|
||
*/
|
||
export const autoConnect = async (deviceId: string): Promise<{serviceId: string, writeCharId: string, notifyCharId: string}> => {
|
||
// 1. 连接设备
|
||
await connectDevice(deviceId);
|
||
|
||
// 2. 服务发现
|
||
const services = await discoverServices(deviceId);
|
||
if (!services || services.length === 0) throw new Error('未发现服务');
|
||
|
||
// 3. 获取私有serviceId(优先bae前缀或通过dataProcessor模板)
|
||
let serviceId = '';
|
||
for (const s of services) {
|
||
if (s.uuid && BLE_SERVICE_PREFIXES.some(prefix => s.uuid.startsWith(prefix))) {
|
||
serviceId = s.uuid;
|
||
break;
|
||
}
|
||
}
|
||
if (!serviceId) {
|
||
// 可扩展:通过dataProcessor获取模板serviceId
|
||
serviceId = services[0].uuid;
|
||
}
|
||
|
||
// 4. 获取特征值
|
||
const characteristics = await getCharacteristics(deviceId, serviceId);
|
||
if (!characteristics || characteristics.length === 0) throw new Error('未发现特征值');
|
||
|
||
// 5. 找到write和notify特征
|
||
let writeCharId = '';
|
||
let notifyCharId = '';
|
||
for (const c of characteristics) {
|
||
if (!writeCharId && (c.properties.write || c.properties.writeWithoutResponse)) writeCharId = c.uuid;
|
||
if (!notifyCharId && (c.properties.notify || c.properties.indicate)) notifyCharId = c.uuid;
|
||
}
|
||
if (!writeCharId || !notifyCharId) throw new Error('未找到可用的写/通知特征');
|
||
|
||
// 6. 注册notification
|
||
await subscribeCharacteristic(deviceId, serviceId, notifyCharId, (data) => {
|
||
// 可在此处分发/处理notification
|
||
// console.log('Notification:', data);
|
||
});
|
||
|
||
// 7. 返回结果
|
||
return { serviceId, writeCharId, notifyCharId };
|
||
}; |