1.前言
- 蓝牙设备扫码的效率要高于手机摄像头
- App需要进行对蓝牙扫码枪进行适配才能正常使用蓝牙设备枪,并兼容之前的摄像头扫码
- 适配的关键在于:扫码枪进行扫码时,需要对其进行事件监听,并拿到条码的值
2.注意事项
- 蓝牙模块是可选项,默认应该是关闭状态,需要时再手动开启
- 蓝牙模块的运行状态需要存储至全局变量中(vuex),以便所有的页面都能获知
- 蓝牙扫描附近的设备时会增加耗电,应在适当时机予以关闭
- 蓝牙设备连接后,需要保持屏幕常亮,蓝牙断开后,关闭屏幕常亮
3.逻辑梳理
- 初始化蓝牙相关的全局数据(vuex)
- 在设置页面中展示蓝牙连接的状态并监听点击
- 获取开启此功能所依赖的权限,并展示到页面中(仅小程序)
- 获取已保存过的设备列表,用于自动连接已匹配过的设备
- 尝试执行初始化,如果已经执行过,则不再执行
- 初始化成功后,进行事件监听(监听蓝牙适配器状态变化事件,监听低功耗蓝牙连接状态的改变事件)
- 初始化成功后,开启搜寻附近的蓝牙外围设备(一段时间后进行关闭,节省电量)
- 则监听设备发现事件,一旦监听到新设备,则将其保存并渲染到页面(进行去重处理),对比之前已经保存过的设备列表,符合条件自动进行连接
- 将扫码到的附近的蓝牙设备渲染到页面中,监听点击事件进行手动连接
- 设备连接成功后,更新获取已保存过的设备列表,停止搜索设备,并根据deviceId获取设备服务(延时获取)
- 选中目标设备服务,获取其特征码,并选中目标特征码,开启notify 功能
- 监听特征值变化事件(扫码会触发此事件)
- 扫码数据触发时,需要对数据进行转换,特征值的数据格式是arrayBuffer,需要将其转换成字符串
- 处理二维码对应的字符串
4.关于设备服务和特征码
- !!!获取设备服务时,需要延时获取,不然App端获取为空
- 监听扫码事件的关键在于获取正确的服务id和特征码id
- 如果不知道需要的是哪个服务和特征码,可以咨询设备厂家的技术客服
- 服务id和特征码id我是自己盲测出来的
5.数据格式转换 arrayBufferUTF8ToStr
arrayBufferUTF8ToStr(array) {
var out, i, len, c;
var char2, char3;
if (array instanceof ArrayBuffer) {
array = new Uint8Array(array);
}
out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 0xxxxxxx
out += String.fromCharCode(c);
break;
case 12:
case 13:
// 110x xxxx 10xx xxxx
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
},
6.处理扫码结果
- 每个页面处理扫码结果的逻辑是不一样的,为了减少对页面代码的改动,我的处理方式是这样的
- 每个页面新建一个处理扫码结果的方法
- 进行蓝牙扫描时,获取到当前页面,再拿到这个处理方法,有则执行
//转码
var _code = this.arrayBufferUTF8ToStr(res.value)
//去除左右空格
var code = _code.trim()
//拿到当前页面 (页面栈最后一个元素)
var now_page_index = getCurrentPages().length - 1
var now_page = getCurrentPages()[now_page_index]
//执行页面扫码回调
if(typeof now_page.$vm.handleScanResult == 'function'){
//执行回调
console.log('执行当前页面 handleScanResult 方法')
now_page.$vm.handleScanResult(code)
}
7.蓝牙相关数据初始化
const state = {
connectedBluetoothDevices:[],//已经建立连接状态的设备 长度大于0表示设备连接成功
bluetoothAdapterAvailable: false,//蓝牙适配器是否可用
bluetoothAdapterDiscovering:false,//蓝牙适配器是否处于搜索状态
bluetoothOpenNotify:false,//蓝牙扫码监听是否已经开启
bluetoothCloseScanTimer:null,//关闭蓝牙扫描的延时定时器
}
const mutations = {
//更新 已经建立连接状态的设备 的数据
updateConnectedBluetoothDevices(state,val){
state.connectedBluetoothDevices = val
},
//更新 蓝牙适配器可用状态
updateBluetoothAdapterAvailable(state,val){
state.bluetoothAdapterAvailable = val
},
//更新 蓝牙适配器是否处于搜索状态
updateBluetoothAdapterDiscovering(state,val){
state.bluetoothAdapterDiscovering = val
},
//更新 蓝牙扫码监听是否已经开启
updateBluetoothOpenNotify(state,val){
state.bluetoothOpenNotify = val
},
//更新 关闭蓝牙扫描的延时定时器
updateBlueClosetoothScanTimer(state,val){
state.bluetoothCloseScanTimer = val
},
//重置蓝牙数据
resetBluetoothData(state){
state.connectedBluetoothDevices = []
state.bluetoothAdapterAvailable = false
state.bluetoothAdapterDiscovering = false
state.bluetoothOpenNotify = false
}
}
8.设备页面
computed:{
//已经建立连接状态的设备
connectedBluetoothDevices(){
return this.$store.state.connectedBluetoothDevices
},
//蓝牙适配器是否可用
bluetoothAdapterAvailable(){
return this.$store.state.bluetoothAdapterAvailable
},
//蓝牙运行状态
bluetoothAdapterStatus(){
//如果不可用 显示
if(!this.bluetoothAdapterAvailable){
return '未开启'
}else if(this.connectedBluetoothDevices.length > 0){
//如果已经开启 且已经连接了设备 则展示其名称
return this.connectedBluetoothDevices[0].name
}else{
return '未连接设备'
}
},
}
9.蓝牙页面
蓝牙适配器状态
确保下列权限都已开启
蓝牙系统开关:
地理位置的系统开关:
允许微信使用定位:
已保存设备列表
{{save.name}}
{{deviceIsConnected(save.deviceId)? '已连接':'已保存'}}
{{getDeviceServicesStatus}}
空
可用设备列表
{{device | showDeviceName}}
已连接