/** * 蓝牙相关封装,面向 uni-app 的 API。 * * 说明: * - 仅支持蓝牙低功耗(BLE),适用于打印机等外设。 * - 组件内必须先调用 initBluetoothAdapter 之后才可开始搜索/连接。 */ export async function checkBluetoothPermission(): Promise { return new Promise((resolve, reject) => { uni.getSetting({ success: (res) => { const hasLocation = res.authSetting?.['scope.userLocation']; if (hasLocation) { return resolve(); } uni.authorize({ scope: 'scope.userLocation', success: () => resolve(), fail: (err) => { uni.showModal({ title: '权限提示', content: '需要定位权限才能扫描蓝牙设备,请前往设置开启定位权限。', showCancel: false, confirmText: '去设置', success: () => { uni.openSetting({}); }, }); reject(err); }, }); }, fail: reject, }); }); } export async function initBluetoothAdapter(): Promise { type BluetoothMode = 'central' | 'peripheral'; const closeAdapter = () => new Promise((resolve) => { uni.closeBluetoothAdapter({ success: () => resolve(), fail: () => resolve(), }); }); const openAdapter = (mode?: BluetoothMode) => new Promise((resolve, reject) => { const options: any = { success: () => resolve(), fail: (err) => { console.log(err); reject(new Error(err.errMsg || `openBluetoothAdapter 失败`)); }, }; if (mode) { options.mode = mode; } uni.openBluetoothAdapter(options); }); // 先尝试关闭现有 adapter,避免重复初始化导致的异常 await closeAdapter(); // iOS 需要分别以主机/从机模式初始化一次,才能兼容全部场景。 const platform = uni.getSystemInfoSync?.()?.platform || ''; console.log(platform, 'initBluetoothAdapter platform'); if (platform === 'ios') { // iOS 在 central/peripheral 两种模式都初始化一次,且需显式指定 mode。 await closeAdapter(); await openAdapter('central'); await closeAdapter(); await openAdapter('peripheral'); // 最终回到 central,保证后续扫描/连接 BLE 外设稳定可用。 await closeAdapter(); await openAdapter('central'); return; } // Android 等其他平台保持默认初始化流程 await openAdapter(); } export async function getBluetoothAdapterState(): Promise { return new Promise((resolve, reject) => { uni.getBluetoothAdapterState({ success: resolve, fail: reject, }); }); } export async function startBluetoothDevicesDiscovery(onDeviceFound: (device: any) => void): Promise<() => void> { return new Promise((resolve, reject) => { let listener: any | null = null; uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success: () => { listener = (res: any) => { if (!res || !res.devices) return; res.devices.forEach((device: any) => { if (device) onDeviceFound(device); }); }; uni.onBluetoothDeviceFound(listener); resolve(() => { if (listener) { uni.offBluetoothDeviceFound(); listener = null; } }); }, fail: reject, }); }); } export async function stopBluetoothDevicesDiscovery(): Promise { return new Promise((resolve, reject) => { uni.stopBluetoothDevicesDiscovery({ success: () => resolve(), fail: reject, }); }); } export async function connectBLEDevice(deviceId: string): Promise { return new Promise((resolve, reject) => { uni.createBLEConnection({ deviceId, success: resolve, fail: reject, }); }); } export async function closeBLEConnection(deviceId: string): Promise { return new Promise((resolve, reject) => { uni.closeBLEConnection({ deviceId, success: resolve, fail: reject, }); }); } export function onBLEConnectionStateChange(onChange: (res: any) => void): () => void { const listener = (res: any) => { onChange(res); }; uni.onBLEConnectionStateChange(listener); return () => { try { uni.offBLEConnectionStateChange(listener); } catch { try { uni.offBLEConnectionStateChange(); } catch { // ignore } } }; } export async function getBluetoothDevices(): Promise { return new Promise((resolve, reject) => { uni.getBluetoothDevices({ success: (res: any) => resolve(res.devices || []), fail: reject, }); }); } export async function getBLEDeviceServicesAndCharacteristics(deviceId: string): Promise<{ services: any[]; characteristicsMap: Record }> { const services = await new Promise((resolve, reject) => { uni.getBLEDeviceServices({ deviceId, success: (res: any) => resolve(res.services || []), fail: reject, }); }); const characteristicsMap: Record = {}; await Promise.all( services.map(async (service) => { try { const characteristics = await new Promise((resolve, reject) => { uni.getBLEDeviceCharacteristics({ deviceId, serviceId: service.uuid, success: (res: any) => resolve(res.characteristics || []), fail: reject, }); }); characteristicsMap[service.uuid] = characteristics; } catch (err) { // 忽略某些服务获取特征值失败 console.warn('getBLEDeviceCharacteristics fail', service.uuid, err); } }), ); return { services, characteristicsMap }; } // 打印数据 - 重构版 export const writeBLECharacteristicSend = async ( { deviceId, serviceId, characteristicId, uint8Array, oneTimeData = 20 // 单次发送最大字节数 (MTU) }: { deviceId: string; serviceId: string; characteristicId: string; uint8Array: Uint8Array | number[]; oneTimeData?: number; } ): Promise => { console.log(uint8Array, '----'); // 1. 统一数据格式为 Uint8Array const u8 = uint8Array instanceof Uint8Array ? uint8Array : new Uint8Array(uint8Array); const totalLength = u8.length; if (totalLength === 0) { console.warn('writeBLECharacteristicSend: 发送数据为空'); return false; } try { // 2. 循环分包发送 (替代原递归逻辑) for (let offset = 0; offset < totalLength; offset += oneTimeData) { // 计算当前包的实际长度 (处理最后一包不足 oneTimeData 的情况) const chunkLength = Math.min(oneTimeData, totalLength - offset); // 3. 构造 ArrayBuffer (参考原代码逻辑) const buffer = new ArrayBuffer(chunkLength); const dataView = new DataView(buffer); // 填充数据 for (let i = 0; i < chunkLength; i++) { dataView.setUint8(i, u8[offset + i]); } // 4. 发送数据 await new Promise((resolve, reject) => { uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: buffer, // 必须传入 ArrayBuffer success: () => resolve(), fail: (err: any) => { console.error('蓝牙写入失败', err); reject(err); }, }); }); // 5. 写入间隔 (防止部分设备缓冲区溢出,参考之前代码保留 50ms) if (offset + chunkLength < totalLength) { await new Promise((r) => setTimeout(r, 50)); } } return true; } catch (err) { console.error('writeBLECharacteristicSend fail', err); // 这里可以抛出错误让调用者处理,或者统一返回 false return false; } };