uni-app 蓝牙扫码适配


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.蓝牙相关数据初始化

  • 在vuex中初始化相关数据
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.设备页面



     
           
     


  • 读取vuex中的数据,输入当前的蓝牙运行状态
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.蓝牙页面

  • 展示开关
  • 渲染设备列表
  • 事件初始化