r-util-js/packages/uni-app/src/bluetooth-utils/index.ts

462 lines
14 KiB
TypeScript
Raw Normal View History

2026-03-16 17:58:57 +08:00
import { toPromise, showToast } from "@/uni-helper";
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次
for (var i = 1; i <= 5; i++) {
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) => {
return uni.getBLEMTU({
deviceId: device.deviceId,
// success: (res) => {
// resolve(res.mtu);
// },
});
// });
}
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);
}
}