| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- /**
- * 蓝牙相关封装,面向 uni-app 的 API。
- *
- * 说明:
- * - 仅支持蓝牙低功耗(BLE),适用于打印机等外设。
- * - 组件内必须先调用 initBluetoothAdapter 之后才可开始搜索/连接。
- */
- export async function checkBluetoothPermission(): Promise<void> {
- 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<void> {
- type BluetoothMode = 'central' | 'peripheral';
- const closeAdapter = () =>
- new Promise<void>((resolve) => {
- uni.closeBluetoothAdapter({
- success: () => resolve(),
- fail: () => resolve(),
- });
- });
- const openAdapter = (mode?: BluetoothMode) =>
- new Promise<void>((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<any> {
- 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<void> {
- return new Promise((resolve, reject) => {
- uni.stopBluetoothDevicesDiscovery({
- success: () => resolve(),
- fail: reject,
- });
- });
- }
- export async function connectBLEDevice(deviceId: string): Promise<void> {
- return new Promise((resolve, reject) => {
- uni.createBLEConnection({
- deviceId,
- success: resolve,
- fail: reject,
- });
- });
- }
- export async function closeBLEConnection(deviceId: string): Promise<void> {
- 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<any[]> {
- 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<string, any[]> }> {
- const services = await new Promise<any[]>((resolve, reject) => {
- uni.getBLEDeviceServices({
- deviceId,
- success: (res: any) => resolve(res.services || []),
- fail: reject,
- });
- });
- const characteristicsMap: Record<string, any[]> = {};
- await Promise.all(
- services.map(async (service) => {
- try {
- const characteristics = await new Promise<any[]>((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<boolean> => {
- 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<void>((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;
- }
- };
|