2026-05-28 11:30:35 +08:00
|
|
|
/* eslint-disable no-useless-assignment, @typescript-eslint/no-unused-vars */
|
|
|
|
|
import { showToast } from "@/uni-helper";
|
2026-03-16 17:58:57 +08:00
|
|
|
|
|
|
|
|
const systemInfo = uni.getSystemInfoSync();
|
|
|
|
|
const isAndroidApp =
|
|
|
|
|
systemInfo.uniPlatform === "app" && systemInfo.osName === "android";
|
|
|
|
|
|
|
|
|
|
// #ifdef APP-PLUS
|
|
|
|
|
const BluetoothAdapter = plus.android.importClass(
|
|
|
|
|
"android.bluetooth.BluetoothAdapter",
|
|
|
|
|
) as Android.BluetoothAdapter;
|
|
|
|
|
const UUID = plus.android.importClass("java.util.UUID") as Android.UUID;
|
|
|
|
|
const Context = plus.android.importClass("android.content.Context");
|
|
|
|
|
const Intent = plus.android.importClass("android.content.Intent");
|
|
|
|
|
const IntentFilter = plus.android.importClass("android.content.IntentFilter");
|
|
|
|
|
const InputStream = plus.android.importClass("java.io.InputStream");
|
|
|
|
|
const OutputStream = plus.android.importClass("java.io.OutputStream");
|
|
|
|
|
const Activity = plus.android.importClass("android.app.Activity");
|
|
|
|
|
const BluetoothSocket = plus.android.importClass(
|
|
|
|
|
"android.bluetooth.BluetoothSocket",
|
|
|
|
|
);
|
|
|
|
|
const BluetoothDevice = plus.android.importClass(
|
|
|
|
|
"android.bluetooth.BluetoothDevice",
|
|
|
|
|
) as Android.BluetoothDevice;
|
|
|
|
|
|
|
|
|
|
const invoke = plus.android.invoke;
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
export interface Device extends UniNamespace.BluetoothDeviceInfo {
|
|
|
|
|
// 通知服务ID
|
|
|
|
|
notifyServiceId?: string;
|
|
|
|
|
// 通知特征值ID
|
|
|
|
|
notifyCharacterId?: string;
|
|
|
|
|
// 写入服务ID
|
|
|
|
|
writeServiceId?: string;
|
|
|
|
|
// 写入特征值ID
|
|
|
|
|
writeCharacterId?: string;
|
|
|
|
|
// 读取服务ID
|
|
|
|
|
readServiceId?: string;
|
|
|
|
|
// 读取特征值ID
|
|
|
|
|
readCharacterId?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class BluetoothError extends Error {
|
|
|
|
|
constructor(massage?: string) {
|
|
|
|
|
super(massage);
|
|
|
|
|
this.name = "BluetoothError";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class BluetoothUtils {
|
|
|
|
|
static async safeInit(cb: () => Promise<unknown>) {
|
|
|
|
|
try {
|
|
|
|
|
console.log("uni.openBluetoothAdapter");
|
|
|
|
|
await uni.openBluetoothAdapter();
|
|
|
|
|
return await cb();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof BluetoothError) {
|
|
|
|
|
showToast(error.message, "error");
|
|
|
|
|
} else {
|
|
|
|
|
showToast("初始化蓝牙失败", "error");
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
console.log("uni.closeBluetoothAdapter");
|
|
|
|
|
await uni.closeBluetoothAdapter();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取所有设备
|
|
|
|
|
* @param cb
|
|
|
|
|
* @param time
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
static async getDevices(cb: (d: Device[]) => void, time: number) {
|
|
|
|
|
return BluetoothUtils.safeInit(() => BluetoothUtils._getDevices(cb, time));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async _getDevices(cb: (d: Device[]) => void, time = 3000) {
|
|
|
|
|
console.log("BluetoothUtils._getDevices()");
|
|
|
|
|
|
|
|
|
|
const foundDevices = <Device[]>[];
|
|
|
|
|
|
|
|
|
|
let btFindReceiver = null;
|
|
|
|
|
let activity = null;
|
|
|
|
|
let btAdapter: Android.BluetoothAdapter = <Android.BluetoothAdapter>{};
|
|
|
|
|
if (isAndroidApp) {
|
|
|
|
|
btAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (isAndroidApp) {
|
|
|
|
|
activity = plus.android.runtimeMainActivity() as Android.Activity;
|
|
|
|
|
console.log({ activity, btAdapter });
|
|
|
|
|
|
|
|
|
|
if (btAdapter.isDiscovering()) {
|
|
|
|
|
btAdapter.cancelDiscovery();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
btFindReceiver = plus.android.implements(
|
|
|
|
|
"io.dcloud.android.content.BroadcastReceiver",
|
|
|
|
|
{
|
|
|
|
|
onReceive: function (
|
|
|
|
|
context: Android.Context,
|
|
|
|
|
intent: Android.Intent,
|
|
|
|
|
) {
|
|
|
|
|
// plus.android.importClass(context);
|
|
|
|
|
// plus.android.importClass(intent);
|
|
|
|
|
const action = intent.getAction();
|
|
|
|
|
|
|
|
|
|
console.log("onReceive");
|
|
|
|
|
if (BluetoothDevice.ACTION_FOUND === action) {
|
|
|
|
|
// 找到设备
|
|
|
|
|
const device =
|
|
|
|
|
intent.getParcelableExtra<Android.BluetoothDevice>(
|
|
|
|
|
BluetoothDevice.EXTRA_DEVICE,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const newDevice = {
|
|
|
|
|
name: device.getName(),
|
|
|
|
|
deviceId: device.getAddress(),
|
|
|
|
|
RSSI: -1,
|
|
|
|
|
advertisData: [],
|
|
|
|
|
advertisServiceUUIDs: [],
|
|
|
|
|
localName: "",
|
|
|
|
|
serviceData: [],
|
|
|
|
|
};
|
|
|
|
|
const repetition = foundDevices.some(
|
|
|
|
|
(v) => v.deviceId === newDevice.deviceId,
|
|
|
|
|
);
|
|
|
|
|
console.log({ newDevice });
|
|
|
|
|
console.log({ repetition });
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!repetition &&
|
|
|
|
|
newDevice.name &&
|
|
|
|
|
newDevice.name !== "未知设备"
|
|
|
|
|
) {
|
|
|
|
|
cb([newDevice]);
|
|
|
|
|
foundDevices.push(newDevice);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED == action) {
|
|
|
|
|
// // 搜索完成
|
|
|
|
|
// }
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
const filter = plus.android.newObject(
|
|
|
|
|
"android.content.IntentFilter",
|
|
|
|
|
) as Android.IntentFilter;
|
|
|
|
|
filter.addAction(BluetoothDevice.ACTION_FOUND);
|
|
|
|
|
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
|
|
|
|
|
activity.registerReceiver(btFindReceiver, filter);
|
|
|
|
|
btAdapter.startDiscovery();
|
|
|
|
|
} else {
|
|
|
|
|
const bluetoothAdapterState = await uni.getBluetoothAdapterState();
|
|
|
|
|
console.log(bluetoothAdapterState);
|
|
|
|
|
|
|
|
|
|
if (!bluetoothAdapterState.available) {
|
|
|
|
|
throw new BluetoothError("蓝牙适配器不可用");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bluetoothAdapterState.discovering) {
|
|
|
|
|
const res = await uni.stopBluetoothDevicesDiscovery();
|
|
|
|
|
console.log(res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sbddRes = await uni.startBluetoothDevicesDiscovery();
|
|
|
|
|
console.log("startBluetoothDevicesDiscovery", sbddRes);
|
|
|
|
|
|
|
|
|
|
// 蓝牙设备监听 plus.bluetooth.onBluetoothDeviceFound
|
|
|
|
|
uni.onBluetoothDeviceFound((result) => {
|
|
|
|
|
// console.log("uni.onBluetoothDeviceFound", result);
|
|
|
|
|
const newDevices = result.devices.filter((v) => {
|
|
|
|
|
// console.log(foundDevices, v);
|
|
|
|
|
return (
|
|
|
|
|
v.name &&
|
|
|
|
|
v.name !== "未知设备" &&
|
|
|
|
|
!foundDevices.some((v2) => v2.deviceId === v.deviceId)
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
if (newDevices.length > 0) {
|
|
|
|
|
cb(newDevices);
|
|
|
|
|
foundDevices.push(...newDevices);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await new Promise((resolve) => {
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
if (isAndroidApp) {
|
|
|
|
|
btAdapter.cancelDiscovery();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log({ foundDevices });
|
|
|
|
|
resolve(foundDevices);
|
|
|
|
|
}, time);
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
if (error instanceof BluetoothError) {
|
|
|
|
|
showToast(error.message, "error");
|
|
|
|
|
} else {
|
|
|
|
|
showToast("获取蓝牙设备失败", "error");
|
|
|
|
|
}
|
|
|
|
|
return Promise.reject(error);
|
|
|
|
|
} finally {
|
|
|
|
|
if (isAndroidApp) {
|
|
|
|
|
if (activity) {
|
|
|
|
|
if (btFindReceiver != null) {
|
|
|
|
|
activity.unregisterReceiver(btFindReceiver);
|
|
|
|
|
}
|
|
|
|
|
activity = null;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await uni.stopBluetoothDevicesDiscovery();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 绑定蓝牙设备
|
|
|
|
|
* @param device
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
static async bindDevice(device: Device, cb: () => Promise<unknown>) {
|
|
|
|
|
return BluetoothUtils.safeInit(() =>
|
|
|
|
|
BluetoothUtils._bindDevice(device, cb),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async _bindDevice(device: Device, cb: () => Promise<unknown>) {
|
|
|
|
|
console.log("_bindDevice", device);
|
|
|
|
|
try {
|
|
|
|
|
let res = null;
|
|
|
|
|
if (isAndroidApp) {
|
|
|
|
|
res = await cb();
|
|
|
|
|
} else {
|
|
|
|
|
// 失败重连5次
|
2026-05-28 11:30:35 +08:00
|
|
|
for (let i = 1; i <= 5; i++) {
|
2026-03-16 17:58:57 +08:00
|
|
|
try {
|
|
|
|
|
await uni.createBLEConnection({
|
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(`蓝牙失败重连${i}次`);
|
|
|
|
|
if (i == 5) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = await new Promise((resolve) => {
|
|
|
|
|
setTimeout(async () => {
|
|
|
|
|
const services = await BluetoothUtils._getServices(device);
|
|
|
|
|
console.log("services", services);
|
|
|
|
|
|
|
|
|
|
await BluetoothUtils._setCharacteristics(device, services);
|
|
|
|
|
resolve(await cb());
|
|
|
|
|
// await cb()
|
|
|
|
|
}, 1000);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
showToast("绑定蓝牙设备失败", "error");
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
if (!isAndroidApp) {
|
|
|
|
|
await uni.closeBLEConnection({
|
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取蓝牙设备所有服务
|
|
|
|
|
* @param device
|
|
|
|
|
* @returns
|
|
|
|
|
*/
|
|
|
|
|
private static async _getServices(device: Device) {
|
|
|
|
|
console.log("获取蓝牙设备所有服务");
|
|
|
|
|
|
|
|
|
|
// const res = await new Promise<UniNamespace.GetBLEDeviceServicesSuccess>(
|
|
|
|
|
// (resolve, reject) => {
|
|
|
|
|
// logger.info("uni.getBLEDeviceServices", device.deviceId);
|
|
|
|
|
// uni.getBLEDeviceServices({
|
|
|
|
|
// deviceId: device.deviceId,
|
|
|
|
|
// success: (res) => {
|
|
|
|
|
// console.log(res);
|
|
|
|
|
// logger.info("res: ", res);
|
|
|
|
|
// resolve(res);
|
|
|
|
|
// },
|
|
|
|
|
// fail: (err) => {
|
|
|
|
|
// console.error(err);
|
|
|
|
|
// logger.info("err: ", err);
|
|
|
|
|
// reject(err);
|
|
|
|
|
// },
|
|
|
|
|
// });
|
|
|
|
|
// }
|
|
|
|
|
// );
|
|
|
|
|
const res = await uni.getBLEDeviceServices({
|
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
|
});
|
|
|
|
|
console.log(res);
|
|
|
|
|
|
|
|
|
|
return res.services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置蓝牙设备特征值
|
|
|
|
|
*/
|
|
|
|
|
private static async _setCharacteristics(
|
|
|
|
|
device: Device,
|
|
|
|
|
services: UniNamespace.GetBLEDeviceServicesSuccessData[],
|
|
|
|
|
) {
|
|
|
|
|
console.log("_setCharacteristics", services);
|
|
|
|
|
|
|
|
|
|
// 获取蓝牙设备某个服务中所有特征值
|
|
|
|
|
// plus.bluetooth.getBLEDeviceCharacteristics
|
|
|
|
|
let notifyServiceId = "";
|
|
|
|
|
let writeServiceId = "";
|
|
|
|
|
let readServiceId = "";
|
|
|
|
|
let notifyCharacterId = "";
|
|
|
|
|
let writeCharacterId = "";
|
|
|
|
|
let readCharacterId = "";
|
|
|
|
|
|
|
|
|
|
for (const service of services) {
|
|
|
|
|
// const { notifyServiceId, writeServiceId, readServiceId } = device;
|
|
|
|
|
const done = [notifyServiceId, writeServiceId, readServiceId].every(
|
|
|
|
|
(v) => v !== "",
|
|
|
|
|
);
|
|
|
|
|
if (done) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
"获取蓝牙设备某个服务中所有特征值/uni.getBLEDeviceCharacteristics",
|
|
|
|
|
);
|
|
|
|
|
const res = await uni.getBLEDeviceCharacteristics({
|
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
|
serviceId: service.uuid,
|
|
|
|
|
});
|
|
|
|
|
console.log(res);
|
|
|
|
|
for (const characteristic of res.characteristics) {
|
|
|
|
|
console.log(characteristic);
|
|
|
|
|
|
|
|
|
|
// const { notifyCharacterId, writeCharacterId, readCharacterId } = device;
|
|
|
|
|
const done = [
|
|
|
|
|
notifyCharacterId,
|
|
|
|
|
writeCharacterId,
|
|
|
|
|
readCharacterId,
|
|
|
|
|
].every((v) => v !== "");
|
|
|
|
|
if (done) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { uuid, properties } = characteristic;
|
|
|
|
|
if (!notifyCharacterId) {
|
|
|
|
|
if (properties.notify) {
|
|
|
|
|
notifyCharacterId = uuid;
|
|
|
|
|
notifyServiceId = service.uuid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!writeCharacterId) {
|
|
|
|
|
if (properties.write) {
|
|
|
|
|
writeCharacterId = uuid;
|
|
|
|
|
writeServiceId = service.uuid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!readCharacterId) {
|
|
|
|
|
if (properties.read) {
|
|
|
|
|
readCharacterId = uuid;
|
|
|
|
|
readServiceId = service.uuid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
device.notifyServiceId = notifyServiceId;
|
|
|
|
|
device.notifyCharacterId = notifyCharacterId;
|
|
|
|
|
device.writeServiceId = writeServiceId;
|
|
|
|
|
device.writeCharacterId = writeCharacterId;
|
|
|
|
|
device.readServiceId = readServiceId;
|
|
|
|
|
device.readCharacterId = readCharacterId;
|
|
|
|
|
console.log(device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async getBLEMTU(device: Device) {
|
|
|
|
|
return BluetoothUtils.bindDevice(device, () =>
|
|
|
|
|
BluetoothUtils._getBLEMTU(device),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static async _getBLEMTU(device: Device) {
|
|
|
|
|
console.log("_getBLEMTU", device);
|
|
|
|
|
// return new Promise<number>(async (resolve, reject) => {
|
2026-05-28 11:30:35 +08:00
|
|
|
return uni.getBLEMTU({
|
|
|
|
|
deviceId: device.deviceId,
|
|
|
|
|
// success: (res) => {
|
|
|
|
|
// resolve(res.mtu);
|
|
|
|
|
// },
|
|
|
|
|
});
|
2026-03-16 17:58:57 +08:00
|
|
|
// });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async sendData(device: Device, data: number[]) {
|
|
|
|
|
if (isAndroidApp) {
|
|
|
|
|
BluetoothUtils.sendDataAndroid(device, data);
|
|
|
|
|
} else {
|
|
|
|
|
const buf = new ArrayBuffer(data.length);
|
|
|
|
|
const dataView = new DataView(buf);
|
|
|
|
|
data.forEach((d, i) => {
|
|
|
|
|
dataView.setUint8(i, d);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
|
|
|
uni.writeBLECharacteristicValue({
|
|
|
|
|
deviceId: device.deviceId ?? "",
|
|
|
|
|
serviceId: device.writeServiceId ?? "",
|
|
|
|
|
characteristicId: device.writeCharacterId ?? "",
|
|
|
|
|
value: buf,
|
|
|
|
|
success: (res) => {
|
|
|
|
|
resolve(res);
|
|
|
|
|
},
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
reject(err);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static async sendDataAndroid(device: Device, data: number[]) {
|
|
|
|
|
const btAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
|
|
|
const deviceObj = invoke(btAdapter, "getRemoteDevice", device.deviceId);
|
|
|
|
|
const PRINTER_UUID = UUID.fromString(
|
|
|
|
|
"00001101-0000-1000-8000-00805F9B34FB",
|
|
|
|
|
);
|
|
|
|
|
const btSocket = invoke(
|
|
|
|
|
deviceObj,
|
|
|
|
|
"createRfcommSocketToServiceRecord",
|
|
|
|
|
PRINTER_UUID,
|
|
|
|
|
);
|
|
|
|
|
if (!invoke(btSocket, "isConnected")) {
|
|
|
|
|
console.log("检测到设备未连接,尝试连接....");
|
|
|
|
|
invoke(btSocket, "connect");
|
|
|
|
|
}
|
|
|
|
|
console.log("设备已连接");
|
|
|
|
|
|
|
|
|
|
const outputStream = invoke(btSocket, "getOutputStream");
|
|
|
|
|
invoke(outputStream, "write", data);
|
|
|
|
|
invoke(outputStream, "flush");
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
invoke(outputStream, "close");
|
|
|
|
|
invoke(btSocket, "close");
|
|
|
|
|
}, 10 * 1000);
|
|
|
|
|
}
|
|
|
|
|
}
|