文章目录
前言
最近开发的小程序新增加了蓝牙通信功能,用于与一款蓝牙跳绳实现数据通信。主要功能点包括:
- 打开蓝牙并扫描设备
- 通过设备deviceId连接对应设备
- 小程序端下发指令,设置蓝牙跳绳模式(计时,计数,自由跳)
- 实时获取蓝牙广播的数据并进行解析
一、关于蓝牙协议
蓝牙(英语:Bluetooth),一种无线通讯技术标准,用来让固定与移动设备,在短距离间交换资料,以形成个人局域网(PAN)。
-
蓝牙协议分为三种模式:
- 高速蓝牙:主要用于数据交换与传输
- 传统蓝牙:以基础信息沟通,设备连接为重点
- 低功耗蓝牙:以不占用过多带宽的设备连接为主。在低功耗模式条件下,Bluetooth 4.0协议以上的蓝牙设备,传输距离可提升到100米以上(BLE)
-
蓝牙服务UUID:
- 通过蓝牙的UUID来标识 蓝牙服务与通讯访问的属性,不同的蓝牙服务和属性使用的是不同的方法,所以在获取到蓝牙服务时需要保持服务一致才能通信
- 蓝牙的read,write,notification特征属性,都有对应的特征服务字段(同样是UUID)。
- 厂商可以自定义蓝牙服务以及特征字段,因此实现蓝牙通信的前提是拿到确定的服务特征值
-
蓝牙广播:
- 蓝牙数据通过广播的形式实现通信,格式如长度+类型+内容,基内容可变,类型固定,长度由内容确定
二、关于微信小程序蓝牙模块API
官方文档地址:设备-蓝牙
主要用到API如下:
- 打开蓝牙适配器:wx.openBluetoothAdapter,后续所有蓝牙模块功能都需要先打开适配器才能进行
- 搜寻蓝牙设备:
2.1 开始搜寻:wx.startBluetoothDevicesDiscovery,此功能比较消耗性能,如果搜索到特定设备可即时停止
2.2 发现设备事件:wx.onBluetoothDeviceFound,在这儿添加实时更新设备列表业务代码
2.3 停止扫描:wx.onBluetoothDeviceFound,停止扫描新的蓝牙设备,当蓝牙扫描到指令设备时,需要即时关闭扫描保证性能
2.4 关闭发现设备事件监听:wx.offBluetoothDeviceFound- 连接蓝牙设备: wx.createBLEConnection,通过传入蓝牙设备deviceId进行设备直连。这里的deviceId可通过上面扫描时wx.onBluetoothDeviceFound响应值获取
- 监听蓝牙设备连接状态:wx.onBLEConnectionStateChange: 包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
- 获取蓝牙服务
5.1 获取蓝牙低功耗设备所有服务: wx.getBLEDeviceServices,通过
5.2 根据特定服务UUID查询所有特征:[wx.getBLEDeviceCharacteristics](wx.getBLEDeviceCharacteristics),- 监听蓝牙数据(实时获取蓝牙跳绳回传的电量,跳绳数量等信息)
6.1 订阅特征变化:wx.notifyBLECharacteristicValueChange,开启订阅后续才能监听到蓝牙数据变化
6.2 监听特征值变化:wx.onBLECharacteristicValueChange,通过监听事件触发数据解析业务- 发送数据(向蓝牙跳绳下发指令)
7.1 下发指令:wx.writeBLECharacteristicValue,通过向蓝牙特定服务的对应特征值写入数据,完成交互。注意:要对应支持“write"属性的特征值- 关闭蓝牙活动
8.1 wx.stopBluetoothDevicesDiscovery(): 停止扫描新设备
8.2 wx.offBluetoothDeviceFound():关闭扫描新设备监听事件
8.3 wx.offBLECharacteristicValueChange():关闭特征值变化监听(数据监听)
8.4 wx.offBLEConnectionStateChange():移除蓝牙低功耗连接状态改变事件的监听函数
8.5 wx.closeBLEConnection: 断开蓝牙连接
8.6 wx.closeBluetoothAdapter():关闭蓝牙适配器
三、蓝牙业务模块封装
因本小程序中的蓝牙跳绳应用场景有:扫描绑定,连接查询详情,实时跳绳数据传输等,所以实现蓝牙基类封装后,通过继承的方式来实现不同场景的使用需求。功能实现包括以下部分:
- 蓝牙模块封装:包括打开适配器,扫描,连接,实时数据传输
- 蓝牙业务中的工具处理函数:
- 扫描到新设备时去重添加
- 微信设备deviceId在安卓和ios下值格式是不同的(需求要展示为统一mac地址)所以需要特征处理
- 蓝牙通信数据处理:16进制数据之前的互相转换
代码如下,篇幅有限进行了部分省略。
3.1 蓝牙基类
/**
* 蓝牙跳绳基类,用于蓝牙相关通信均可继承
*/
import {
inArray, // 展示搜索到的设备列表时去重
uuid2Mac, // 用于统一ios与android设备端展示的deviceId格式为:xx:xx:xx:xx:xx
deviceNameFilter,
utf8to16,
hexToString,
ab2hex,
str2ab,
} from "@/utils/util-BLE.js";
class BLEController {
// 自动关闭定时器
findTimer = null;
// 蓝牙适配器开启状态
static adapterOpend = false;
// 扫描设备状态
startDiscovery = false;
// 蓝牙连接状态
connectStatus = false;
// 蓝牙扫描自动结束时间2min
#timeout = 2 * 60 * 1000;
// 蓝牙通信超时时间5min
#notifyTimeout = 5 * 60 * 1000;
// 蓝牙搜索是否超时
deviceDiscoveryTimeout = false;
// 蓝牙设备ID,注意:ios设置对应deviceId为设备uuid,安卓及开发者工具上连接的蓝牙为mac地址
deviceId;
deviceName;
// 设备mac地址(统一编码处理)
deviceMac;
// 设备列表, [{deviceMac:设备mac地址, deviceId:设备ID,deviceName:设备名称,...}]
deviceList = [];
// 蓝牙服务特征队列
characteristicStack = [];
// 蓝牙消息队列
msgStack = [];
// 蓝牙通信serviceId
serviceId = "";
constructor(context) {
if (context) {
this.deviceId = context.deviceId;
this.deviceMac = context.deviceMac;
this.connectStatus = false;
}
}
/**
* 1.初始化蓝牙模块
*/
openBluetoothAdapter() {
const _this = this;
if (BLEController.adapterOpend) {
console.log("蓝牙适配器已打开,请勿重复操作------》");
return;
}
wx.openBluetoothAdapter({
mode: "central",
success(res) {
BLEController.adapterOpend = true;
console.log("蓝牙适配器打开成功-----------------》");
},
fail(res) {
BLEController.adapterOpend = false;
_this.BLEFail(res);
console.log("蓝牙适配器打开失败-----------------》", res.errMsg);
},
});
}
/**
* 2.扫描蓝牙设备(绑定蓝牙,连接蓝牙通用)
* @param {Array} options.keywords 蓝牙名称筛选关键字
* @param {string} options.deviceId 可选参数,蓝牙设备id,连接用
*/
startBluetoothDevicesDiscovery(options) {
// ---------省略---------------》
if (this.startDiscovery) {
console.log("已开启蓝牙扫描,勿重复开启-----------》");
return;
} else {
this.startDiscovery = true;
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: (res) => {
this.onBluetoothDeviceFound(options);
console.log("开始扫描蓝牙设备-----------------》");
},
fail: (res) => {
this.startDiscovery = false;
},
});
}
}
/**
* 2.1 监听搜索到新设备
* @param {Array} options.keywords 蓝牙名称筛选关键字,来自startBluetoothDevicesDiscovery调用
* @param {string} options.deviceId
*/
onBluetoothDeviceFound(options) {
let {
keywords } = options;
// 超时自动结束
this.findTimer = setTimeout(() => {
clearTimeout(this.findTimer);
if (!