Files
akmon/uni_modules/ak-sbsrv/utssdk/web/bluetooth_manager.uts
2026-01-20 08:04:15 +08:00

139 lines
6.4 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 };
};