步骤:
1.初始化蓝牙,
2.开始搜寻附近的蓝牙外围设备,
3.监听寻找到新设备的事件,
4.搜寻到需要的蓝牙,停止搜寻附近的蓝牙外围设备,
5.连接低功耗蓝牙设备,
6.获取蓝牙设备所有服务 ,
7.获取蓝牙设备某个服务中所有特征值,
8.启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。
注意:必须设备的特征值支持 notify 或者 indicate 才可以成功调用,
9.向低功耗蓝牙设备特征值中写入二进制数据
实现方式一:
uni.openBluetoothAdapter({//首先初始化蓝牙
success(res) {
console.log(JSON.stringify(res))
uni.startBluetoothDevicesDiscovery({//这里是开启蓝牙搜寻
success: (res) => {
console.log('startBluetoothDevicesDiscovery success', res)
uni.onBluetoothDeviceFound((res) => {//这一步是监听返回的蓝牙设备
console.log(JSON.stringify(res))
res.devices.forEach(device => {//这一步就是去筛选找到的蓝牙中,有没有你匹配的名称
console.log(JSON.stringify(device))
if (device.name == 'XiaoanTech') {
this.DeviceID = device.deviceId
let DeviceID = device.deviceId//这里是拿到的uuid
uni.stopBluetoothDevicesDiscovery({//当找到匹配的蓝牙后就关掉蓝牙搜寻,因为蓝牙搜寻很耗性能
success(res) {
console.log(JSON.stringify(res))
}
})
console.log(DeviceID)
uni.createBLEConnection({//连接低功耗蓝牙设备
deviceId:DeviceID,//传入刚刚获取的uuid
success(res) {
console.log(JSON.stringify(res))
//uni.getConnectedBluetoothDevices({
//success(res) {
//console.log(JSON.stringify(res))
//}
//})
setTimeout(function(){//这里为什么要用setTimeout呢,等等下面会解释
uni.getBLEDeviceServices({//获取蓝牙设备所有服务
deviceId:DeviceID,
success(res) {//为什么要用延时,因为不用延时就拿不到所有的服务,在上一步,连接低功耗蓝牙
//设备的时候,需要一个600-1000毫秒的时间后,再去获取设备所有服务,不给延时就会一直返回错误码10004
console.log(JSON.stringify(res))
uni.getBLEDeviceCharacteristics({//获取蓝牙设备某个服务中所有特征值
deviceId:DeviceID,
serviceId:this.ServiceUUID,//这个serviceId可以在上一步获取中拿到,也可以在
//蓝牙文档中(硬件的蓝牙文档)拿到,我这里是通过文档直接赋值上去的,一般有两个,一个是收的uuid,一个是发的uuid,我们这边是发
success(res) {
console.log(JSON.stringify(res))
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:DeviceID,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId:this.ServiceUUID,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId:self.characteristicId,
success(res) {
console.log('notifyBLECharacteristicValueChange success', res.errMsg)
uni.showToast({
title: '开启蓝牙连接',
duration: 2000
});
},
fail(res) {
console.log(JSON.stringify(res))
}
})
},
fail(res){
console.log(JSON.stringify(res))
}
})
},
fail(res){
console.log(JSON.stringify(res))
}
})
},1000)
},
fail(res) {
console.log(res)
}
})
}
})
})
}
})
}, fail(res) {
console.log(res)
if (res.errCode == 10001) {
uni.showToast({
title: '蓝牙未打开',
duration: 2000,
})
} else {
uni.showToast({
title: res.errMsg,
duration: 2000,
})
}
}
})
- 蓝牙连接成功后就是发送指令了,发送二进制数据
SendChange: function () {//开锁
var self = this
// 向蓝牙设备发送一个0x00的16进制数据
let buffer = new ArrayBuffer(8)
let dataView = new DataView(buffer)
dataView.setUint8(0, 0x20)//开锁指令
dataView.setUint8(1, 0x05)//字节
dataView.setUint8(2, 0x0A)//指令
dataView.setUint8(3, 0x0A)//指令
dataView.setUint8(4, 0x05)//指令
dataView.setUint8(5, 0x05)//指令
dataView.setUint8(6, 0x00)//指令
//算法,逢10进1,A到F(或a~f)表示,其中:A~F表示10~15
//20等于32,32+05+10+10+5+5+0 = 67
dataView.setUint8(7, 0x43)
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId:self.DeviceID,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId:self.ServiceUUID,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId:self.characteristicId,
// 这里的value是ArrayBuffer类型
value: buffer,
success(res) {
console.log('writeBLECharacteristicValue success', res.errMsg)
uni.showToast({
title: '开锁',
duration: 2000
});
},
fail(res) {
console.log(JSON.stringify(res))
console.log(JSON.stringify(buffer))
}
})
},
CloseChange: function () {//关锁
var self = this
// 向蓝牙设备发送一个0x00的16进制数据
let buffer = new ArrayBuffer(8)
let dataView = new DataView(buffer)
dataView.setUint8(0, 0x20)
dataView.setUint8(1, 0x05)
dataView.setUint8(2, 0x0A)
dataView.setUint8(3, 0x0A)
dataView.setUint8(4, 0x05)
dataView.setUint8(5, 0x05)
dataView.setUint8(6, 0x01)
dataView.setUint8(7, 0x44)
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId:self.DeviceID,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId:self.ServiceUUID,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId:self.characteristicId,
// 这里的value是ArrayBuffer类型
value: buffer,
success(res) {
console.log('writeBLECharacteristicValue success', res.errMsg)
uni.showToast({
title: '关锁',
duration: 2000
});
},
fail(res) {
console.log(JSON.stringify(res))
console.log(JSON.stringify(buffer))
}
})
},
实现方式二:
export enum BTResultEnum {
// 适配器打开成功
ADAPTER_OPEN_SUCCESS = 0x01,
// 适配器打开失败
ADAPTER_OPEN_FAILED = 0x1001,
// 适配器关闭成功
ADAPTER_CLOSE_SUCCESS = 0x02,
// 适配器关闭失败
ADAPTER_CLOSE_FAILED = 0x1002,
// 搜索设备成功
SEARCH_DEVICE_SUCCESS=0x105,
// 搜索设备失败
SEARCH_DEVICE_FAILED=0x805,
// 连接成功
CONNECT_SUCCESS = 0x03,
// 链接失败
CONNECT_FAILED = 0x1003,
// 写数据成功
WRITE_SUCCESS = 0x04,
//写数据失败
WRITE_FAILED = 0x1004,
SUB_VALUE_CHANGE_SUCCESS=0x05,
SUB_VALUE_CHANGE_FAILED=0x1005,
CHARACTERISTIC_SUCCESS=0X06,
CHARACTERISTIC_FAILED=0x1006,
}
export type BluetoothParam = {
/**
* 设备MAC地址
*/
deviceMac : string;
/**
* 主服务UID
*/
serviceUuid : string;
/**
* 搜索超时时间, 默认10秒钟
*/
searchTimeout:number;
/**
* 通知服务ID
*/
notifyCharacteristicId ?: string;
/**
* 写服务UID
*/
writeCharacteristicId ?: string;
/**
* 读服务UID
*/
readCharacteristicId ?: string;
}
export abstract class BtUpProtocol{
abstract parse(data:Int8Array):void;
}
export abstract class BtDownProtocol{
abstract build():Int8Array;
}
export interface BluetoothStateOptions{
/**
* 接口调用的回调函数
*/
onStateChange: (result: any) => void;
}
export interface BluetoothValueOptions{
onValue: (result:BtUpProtocol) => void;
}
export default AbsCodec{
keyInfo:{[key:number]: BtUpProtocol}={};
secret:string = "";
public constructor() {
this.initProto();
}
/**
* 初始化协议信息
*/
abstract initProto():void;
abstract decode(data:Int8Array):BtUpProtocol;
abstract encode(proto:BtDownProtocol):Int8Array;
abstract getCodeKey(data:Int8Array):number;
}
export function num2Hex(byte:number):string{
let hex= byte.toString(16);
if(hex.length<2){
return '0'+hex;
}
return hex;
}
import { BTResultEnum } from "@/enums/BTResultEnum";
import type { BluetoothParam } from "@/bluetooth/BluetoothParam";
import type {
BtDownProtocol,
BtUpProtocol,
} from "@/bluetooth/protocol/Protocol";
import type {
BluetoothStateOptions,
BluetoothValueOptions,
} from "./bluetoothListener";
import type AbsCodec from "@/bluetooth/Codec";
import { num2Hex } from "@/utils/ByteConverter";
/**
* BluetoothModule 类用于管理蓝牙连接及其相关操作。
* 它封装了微信小程序提供的蓝牙API,用于与蓝牙设备进行通信。
*/
class BluetoothModule {
deviceId: string = ""; // 设备MAC地址
connected: boolean = false; // 连接状态,表示是否已连接
params?: BluetoothParam; // 蓝牙连接的参数
serviceId: string = ""; // 服务UUID,用于标识蓝牙服务
// 特征值UUID(读、写、通知)数据
readCharacteristicId: string = "";
writeCharacteristicId: string = "";
notifyCharacteristicId: string = "";
stateListener?: BluetoothStateOptions; // 状态监听器,用于监听蓝牙连接状态变化
valueListener?: BluetoothValueOptions; // 值监听器,用于监听蓝牙数据变化
codec?: AbsCodec; // 编解码器,用于对蓝牙数据进行编码和解码
constructor() { }
/**
* 设置蓝牙参数。
* @param params 包含蓝牙连接所需的参数(设备MAC地址、服务UUID等)。
*/
setParams(params: BluetoothParam) {
//this.deviceId = params.deviceId;
this.params = params;
this.readCharacteristicId = "";
this.writeCharacteristicId = "";
this.notifyCharacteristicId = "";
}
/**
* 设置编解码器,用于对蓝牙数据进行编码和解码。
* @param codec 编解码器对象。
*/
setCodec(codec: AbsCodec) {
this.codec = codec;
}
/**
* 设置状态监听器,用于监听蓝牙连接状态的变化。
* @param listener 状态监听器对象。
*/
setStateListener(ler: BluetoothStateOptions) {
this.stateListener = ler;
}
/**
* 设置值监听器,用于监听蓝牙数据的变化。
* @param listener 值监听器对象。
*/
setValueListener(ler: BluetoothValueOptions) {
this.valueListener = ler;
}
/**
* 打开蓝牙适配器,初始化蓝牙模块,成功或失败后会调用状态监听器的相应回调方法。
*/
async openAdapter() {
const that = this;
wx.openBluetoothAdapter({
success(res) {
// 通知状态监听器蓝牙适配器打开成功
that.stateListener?.onStateChange(BTResultEnum.ADAPTER_OPEN_SUCCESS);
},
fail(res) {
// 通知状态监听器蓝牙适配器打开失败
that.stateListener?.onStateChange(BTResultEnum.ADAPTER_OPEN_FAILED);
},
});
}
/* ab2hex(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
} */
/**
* 监听发现的设备信息。
* 当找到设备时,会检查其MAC地址是否匹配,如果匹配则停止搜索并尝试连接该设备。
*/
async onFoundDevices() {
let that = this;
wx.onBluetoothDeviceFound(function(res) {
var devices = res.devices;
console.log('new device list has founded')
console.dir(devices)
// 设备数据转成 Unit 8
const avdData = new Uint8Array(devices[0].advertisData);
// 读取MAC地址,并转换为字符串格式
const mac: string = num2Hex(avdData[2]) + ':' + num2Hex(avdData[3]) + ':' + num2Hex(avdData[4])
+ ':' + num2Hex(avdData[5]) + ':' + num2Hex(avdData[6]) + ':' + num2Hex(avdData[7]);
// 如果发现的设备MAC地址与目标设备MAC地址匹配
if (mac.toUpperCase() === that.params?.deviceMac.toUpperCase()) {
that.deviceId = devices[0].deviceId;
// 停止搜索其他设备。
that.stopSearchDevices();
//that.stateListener?.onStateChange(BTResultEnum.CONNECT_FAILED);
// 开始连接到找到的设备
that.connect();
}
//console.log(that.ab2hex(devices[0].advertisData))
})
}
/**
* 停止搜索蓝牙设备
*/
async stopSearchDevices() {
wx.stopBluetoothDevicesDiscovery({
success(res) {
}
})
}
/**
* 开始搜索蓝牙设备。
* @param params 蓝牙参数对象(设备MAC地址、服务UUID等)
*/
async startSearchDevices(params: BluetoothParam) {
let that = this;
// 监听发现的设备信息
this.onFoundDevices();
// 设置蓝牙参数
this.setParams(params);
// 开始搜索蓝牙设备
wx.startBluetoothDevicesDiscovery({
services: [params.serviceUuid],
success(res) {
},
fail(err) {
// 通知状态监听器连接失败
that.stateListener?.onStateChange(BTResultEnum.CONNECT_FAILED);
}
})
}
/**
* 连接到蓝牙设备,连接成功后获取设备的服务UUID和特征值UUID。
*/
async connect() {
//params: BluetoothParam
let that = this;
// 订阅设备连接状态变化
this.subConnectionState();
// this.setParams(params);
// 创建蓝牙连接
//为什么要用延时,因为不用延时就拿不到所有的服务,在createBLEConnection中,连接低功耗蓝牙
//设备的时候,需要一个600-1000毫秒的时间后,再去获取设备所有服务,不给延时就会一直返回错误码10004
wx.createBLEConnection({
deviceId: that.deviceId,
timeout: 6000,
}).then(
(res) => {
// // 连接成功后,获取设备的服务UUID
// that.connected = true;
// that.stateListener?.onStateChange(BTResultEnum.CONNECT_SUCCESS);
wx.getBLEDeviceServices({
deviceId: that.deviceId,
}).then(
(res) => {
let suid = "";
if (that.params) {
suid = that.params.serviceUuid;
}
// 遍历设备的服务UUID,找到匹配的服务UUID
for (var sid in res.services) {
if (res.services[sid].uuid.startsWith(suid)) {
that.serviceId = res.services[sid].uuid;
break;
}
}
// 获取服务的特征值UUID
wx.getBLEDeviceCharacteristics({
deviceId: that.deviceId,
serviceId: that.serviceId,
}).then(
(res1) => {
// 遍历通道特征UUID,分别匹配到读、写、通知的特征值UUID
for (var cid in res1.characteristics) {
var characteristic = res1.characteristics[cid];
// 匹配到写服务ID
if (characteristic.uuid.startsWith("000036F5") == true) {
that.writeCharacteristicId = characteristic.uuid;
}
// 匹配到通知服务ID
if (characteristic.uuid.startsWith("000036F6") == true) {
that.notifyCharacteristicId = characteristic.uuid;
}
}
// 启用通知以接收设备的特征值变化
that.notifyValue();
// 通知状态监听器特征值获取成功
that.stateListener?.onStateChange(BTResultEnum.CHARACTERISTIC_SUCCESS);
},
(err1) => {
// 通知状态监听器特征值获取失败
that.stateListener?.onStateChange(BTResultEnum.CHARACTERISTIC_FAILED);
}
);
},
(err) => {
// 通知状态监听器连接失败
that.stateListener?.onStateChange(BTResultEnum.CONNECT_FAILED);
}
);
},
(err) => {
// 通知状态监听器连接失败
that.stateListener?.onStateChange(BTResultEnum.CONNECT_FAILED);
}
);
}
/**
* 写数据到蓝牙设备。
* @param data 要写入的数据,按照协议进行封装。
*/
async write(data: BtDownProtocol) {
const that = this;
// 编码数据
const da = this.codec?.encode(data);
if (da) {
// 写入数据到蓝牙特征值
wx.writeBLECharacteristicValue({
// deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: that.deviceId,
// serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: that.serviceId,
// characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: that.writeCharacteristicId,
// value是ArrayBuffer类型
value: da.buffer,
}).then(
(res) => {
// 通知状态监听器写入成功
that.stateListener?.onStateChange(BTResultEnum.WRITE_SUCCESS);
},
(err) => {
// 通知状态监听器写入失败
that.stateListener?.onStateChange(BTResultEnum.WRITE_FAILED);
}
);
}
}
/**
* 订阅设备连接状态监听。
* 监听设备连接状态的变化。
*/
async subConnectionState() {
const that = this;
wx.onBLEConnectionStateChange((res) => {
that.connected = res.connected;
if (that.connected) {
// 通知状态监听器连接成功
that.stateListener?.onStateChange(BTResultEnum.CONNECT_SUCCESS);
} else {
// 通知状态监听器连接断开
that.stateListener?.onStateChange(BTResultEnum.CONNECT_FAILED);
}
});
}
/**
* 启用通知以接收设备的特征值变化。
* 监听设备特征值的变化,并处理接收到的数据。
*/
async notifyValue() {
const that = this;
wx.onBLECharacteristicValueChange((res) => {
// 解码接收到的数据
const decodeRes: BtUpProtocol | undefined = that.codec?.decode(
new Int8Array(res.value)
);
if (decodeRes) {
// 通知值监听器数据变化
that.valueListener?.onValue(decodeRes);
}
});
// 启用特征值通知
wx.notifyBLECharacteristicValueChange({
// deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: that.deviceId,
// serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: that.serviceId,
// characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: that.notifyCharacteristicId,
state: true,
type: "notification",
}).then(
(res) => {
console.log("notifyBLECharacteristicValueChange:", res);
},
(err) => {
console.log("notifyBLECharacteristicValueChange:", err);
}
);
}
/**
* 断开与蓝牙设备的连接。
*/
async disConnection() {
let that = this;
wx.closeBLEConnection({
deviceId: that.deviceId,
})
}
/**
* 关闭蓝牙适配器,释放资源。
*/
async closeAdapter() {
wx.closeBluetoothAdapter();
}
}
export default BluetoothModule;
前者:
-
优点:
- 代码简单直观,直接使用了微信小程序的API,没有封装复杂的逻辑。
- 适合简单的蓝牙连接和操作场景。
-
缺点:
- 缺乏结构化和模块化,代码不够灵活和可维护。
- 对于大型项目或复杂的蓝牙操作,不易扩展和复用。
后者:
-
优点:
- 代码模块化、结构清晰,封装了蓝牙连接及其相关操作,易于维护和扩展。
- 使用了面向对象的设计思想,方便管理蓝牙连接的状态和参数。
- 提供了较好的解耦性,使得各个部分的逻辑更加独立。
-
缺点:
- 代码相对复杂,需要理解类和对象的使用,可能对初学者不太友好。
- 需要更多的前期开发工作来实现这些封装。
建议:
- 如果你的项目较为简单,只需要基本的蓝牙连接和操作,且不需要进行太多的维护和扩展,前者可能更合适。
- 如果你的项目较为复杂,需要频繁的蓝牙操作,且需要良好的可维护性和可扩展性,后者更为合适。
总结
- 前者适用于简单、一次性的蓝牙应用。
- 后者适用于复杂、长期维护的蓝牙应用。