首页 > JavaScript > 小程序蓝牙API踩坑记

小程序蓝牙API踩坑记

最近做中铁的叉车项目包含了蓝牙开锁的功能,上周供应商发给我一块板子,大概就张这样子:

老实说刚看到这东西那是一脸懵逼,连电源线都不给我,怎么玩嘛。后面跟厂家沟通了几次,看了通信部分和硬件通电图,去买了个12V的电源给接上,直到今天调通蓝牙模块发送指令成功,嗯,还是有一些成就感的,接下来把代码贴上,留作纪念。

/*
 * @Description: In User Settings Edit
 * @Author: zhaoyulong
 * @Date: 2019-08-12 09:51:44
 * @LastEditTime: 2019-08-19 15:59:21
 * @LastEditors: Please set LastEditors
 */
import {
  buf2hex,
  getCommand // 获取开锁指令
  // setSnCommand // 获取写入SN号指令
} from '@/utils'
import { mapActions, mapState } from 'vuex'
export default {
  computed: {
    ...mapState({
      bluetoolthInfo: state => state.bluetoolthInfo,
      connected: state => state.bluetoolthInfo.connected,
      deviceId: state => state.bluetoolthInfo.deviceId, // 用于区分设备id - 设备物理地址(9C:A5:25:23:A4:E2)
      serviceId: state => state.bluetoolthInfo.serviceId, // 蓝牙特征值对应服务的 uuid
      characteristicId: state => state.bluetoolthInfo.characteristicId, // 订阅蓝牙特征值uuid(notify)
      writeCharacteristicId: state => state.bluetoolthInfo.writeCharacteristicId // 写入特征值uuid(write)
    })
  },
  data: {
    // connected: false, // 是否已经连接上蓝牙设备
    searching: '', // 设备是否可搜索
    devicesList: [], // 设备列表
    connectDeviceIndex: 0, // 连接设备的次数
    // deviceId: '', // 用于区分设备id - 设备物理地址(9C:A5:25:23:A4:E2)
    // serviceId: '', // 蓝牙特征值对应服务的 uuid
    // characteristicId: '', // 订阅蓝牙特征值uuid(notify)
    // writeCharacteristicId: '', // 写入特征值uuid(write)
    getConnectedTimer: undefined, // 计时器-获取已连接的蓝牙设备
    discoveryDevicesTimer: undefined, // 计时器-搜寻附件设备
    timerOpen: undefined, // 循环获取开锁结果
    openBluetoothSuccess: false, // 蓝牙开锁是否成功
    bluetoothName: ''
  },
  methods: {
    ...mapActions([
      'setBluetoolthInfo' // 设置蓝牙信息
    ]),
    /**
     * @author: zhaoyulong
     * @desc: 修改蓝牙store数据
     */
    changeBluetoolthInfo(key, value) {
      let tempInfo = JSON.parse(JSON.stringify(this.bluetoolthInfo))
      tempInfo[key] = value
      this.setBluetoolthInfo(tempInfo)
    },
    /**
     * @author: zhaoyulong
     * @desc: 开启蓝牙设备入口
     */
    openBlueDevice(scanCode) {
      var that = this
      that.bluetoothName = scanCode
      return new Promise(function(resolve, reject) {
        that.openBluetoothAdapter()
        if (that.timerOpen) {
          clearInterval(that.timerOpen)
        }
        let i = 0
        that.timerOpen = setInterval(function() {
          i++
          if (that.openBluetoothSuccess) {
            clearInterval(that.timerOpen)
            resolve(true)
          }
          if (i > 60) {
            reject(new Error('蓝牙开锁超时,请联系调度中心'))
          }
        }, 1000)
      })
    },
    // 开启蓝牙适配
    openBluetoothAdapter() {
      var that = this
      wx.openBluetoothAdapter({
        success(res) {
          console.log('初始化蓝牙适配器成功', res)
          that.getBluetoothAdapterState()
        },
        fail(res) {
          console.log('fail', res)
          switch (res.errCode) {
            case 10001:
              wx.showToast({
                title: '当前蓝牙不可用,请打开蓝牙!',
                icon: 'none',
                duration: 3000
              })
              break
            default:
              wx.showToast({
                title: '初始化蓝牙模块错误码:' + res.errCode,
                icon: 'none',
                duration: 3000
              })
          }
        }
      })
    },
    // 获取本机蓝牙适配器状态
    getBluetoothAdapterState() {
      var that = this
      wx.getBluetoothAdapterState({
        success: function(res) {
          var available = res.available // 蓝牙适配器是否可用
          var discovering = res.discovering // 是否正在搜索设备
          if (!available) {
            wx.showToast({
              title: '设备无法开启蓝牙连接',
              icon: 'success',
              duration: 2000
            })
          } else {
            if (!discovering) {
              that.step = 3
              // 开启扫描附近的蓝牙设备
              that.startBluetoothDevicesDiscovery()
              // 开启获取已连接的蓝牙设备
              that.getConnectedBluetoothDevices()
            }
          }
        },
        fail: function(res) {
          console.log('fail', res)
        }
      })
    },
    // 开始搜寻附近的蓝牙外围设备。
    // 此操作比较耗费系统资源,请在搜索并连接到设备后调用 wx.stopBluetoothDevicesDiscovery 方法停止搜索
    startBluetoothDevicesDiscovery() {
      var that = this
      // wx.showLoading({
      //   title: '蓝牙搜索'
      // })
      console.log('开始搜寻附近的蓝牙外围设备')
      wx.startBluetoothDevicesDiscovery({
        services: [],
        allowDuplicatesKey: false,
        success: function(res) {
          console.log('搜寻结果', res)
          if (!res.isDiscovering) {
            that.getBluetoothAdapterState()
          } else {
            that.onBluetoothDeviceFound()
          }
        },
        fail: function(err) {
          console.log(err)
        }
      })
    },
    // 根据uuid获取处于已连接状态的设备
    getConnectedBluetoothDevices() {
      var that = this
      console.log('开始根据 uuid 获取处于已连接状态的设备:', that.serviceId)
      if (that.serviceId) {
        wx.getConnectedBluetoothDevices({
          services: [that.serviceId],
          success: function(res) {
            console.log('获取处于连接状态的设备', res)
            var devices = res['devices']
            var flag = false
            var index = 0
            var conDevList = []
            devices.forEach(function(value, i, array) {
              // name: NS-00000001 (在蓝牙连接中呈现的名字)
              if (value['name'] === that.bluetoothName) {
                // 如果存在包含bluetoothName字段的设备
                flag = true
                index += 1
                conDevList.push(value['deviceId'])
                that.changeBluetoolthInfo('deviceId', value['deviceId'])
                // that.deviceId = value['deviceId']
                console.log('设备数量', index)
                // return
              }
            })
            // 循环连接 - 这里的逻辑还没屡清楚
            if (flag) {
              that.connectDeviceIndex = 0
              that.loopConnect(conDevList)
            } else {
              if (!that.getConnectedTimer) {
                that.getConnectedTimer = setTimeout(function() {
                  // 开启获取已连接的蓝牙设备
                  that.getConnectedBluetoothDevices()
                }, 5000)
              }
            }
          },
          fail: function(err) {
            console.log('获取处于连接状态的设备失败', err)
            if (!that.getConnectedTimer) {
              that.getConnectedTimer = setTimeout(function() {
                // 开启获取已连接的蓝牙设备
                that.getConnectedBluetoothDevices()
              }, 5000)
            }
          }
        })
      }
    },
    // 开启发现附近蓝牙设备事件监听
    onBluetoothDeviceFound: function() {
      var that = this
      console.log('开启发现附近蓝牙设备事件监听-会把所有设备找出来')
      wx.onBluetoothDeviceFound(function(res) {
        // 每找出来一个设备这个逻辑会执行一次
        console.log('附件的设备列表', res)
        if (res.devices[0]) {
          var name = res.devices[0]['name']
          if (name !== '') {
            // name: NS-00000001 (在蓝牙连接中呈现的名字)
            if (name === that.bluetoothName) {
              var deviceId = res.devices[0]['deviceId']
              that.changeBluetoolthInfo('deviceId', deviceId)
              // that.deviceId = deviceId
              // console.log(that.deviceId)
              that.startConnectDevices()
            }
          }
        }
      })
    },
    // 连接低功耗蓝牙设备
    // 若小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作
    startConnectDevices: function(ltype, array) {
      var that = this
      clearTimeout(that.getConnectedTimer)
      that.getConnectedTimer = null
      clearTimeout(that.discoveryDevicesTimer)
      // 停止循环获取已连接设备
      that.stopBluetoothDevicesDiscovery()
      // that.isConnectting = true
      if (!this.connected) {
        console.log('开始连接低功耗蓝牙设备')
        wx.createBLEConnection({
          deviceId: that.deviceId,
          success: function(res) {
            console.log('连接成功', res)
            if (res.errCode === 0) {
              // that.connected = true
              that.changeBluetoolthInfo('connected', true)
              setTimeout(function() {
                that.getService(that.deviceId)
              }, 5000)
            }
          },
          fail: function(err) {
            console.log('连接失败:', err)
            if (ltype === 'loop') {
              that.connectDeviceIndex += 1
              that.loopConnect(array)
            } else {
              // 开启扫描附近的蓝牙设备
              that.startBluetoothDevicesDiscovery()
              // 开启获取已连接的蓝牙设备
              that.getConnectedBluetoothDevices()
            }
          },
          complete: function() {
            console.log('连接结束')
            // that.isConnectting = false
          }
        })
      } else {
        that.getService(that.deviceId)
      }
    },
    // 连接成功后根据deiviceId获取设备的所有服务
    getService: function(deviceId) {
      var that = this
      // 监听低功耗蓝牙连接状态的改变事件。包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
      wx.onBLEConnectionStateChange(function(res) {
        console.log('监听蓝牙连接', res)
      })
      // 获取蓝牙设备所有服务(service)。
      wx.getBLEDeviceServices({
        deviceId: deviceId,
        success: function(res) {
          console.log('获取蓝牙设备service值', res)
          that.getCharacter(deviceId, res.services)
        }
      })
    },
    // 读取服务的特征值
    getCharacter: function(deviceId, services) {
      var that = this
      if (services && services.length > 0) {
        // 获取蓝牙设备某个服务中所有特征值(characteristic)。第0个是稳定的服务。这里服务有多个,循环会报错。
        console.log('读取服务', services[0])
        wx.getBLEDeviceCharacteristics({
          deviceId: deviceId,
          serviceId: services[0].uuid,
          success: function(res) {
            console.log('设备特征值列表', res)
            var tempServiceId = services[0].uuid
            var tempCharacteristicId = ''
            var tempWriteCharacteristicId = ''
            if (
              res.errCode === 0 &&
              res.characteristics &&
              res.characteristics.length > 0
            ) {
              for (var i = 0; i < res.characteristics.length; i++) {
                var item = res.characteristics[i]
                if (item.properties.notify) {
                  // that.characteristicId = item.uuid
                  tempCharacteristicId = item.uuid
                } else if (item.properties.write) {
                  // that.writeCharacteristicId = item.uuid
                  tempWriteCharacteristicId = item.uuid
                }
              }
            }
            if (tempCharacteristicId && tempWriteCharacteristicId) {
              // 只有服务中的特征值可以notify和write的时候记录服务id和特征值id
              that.changeBluetoolthInfo('serviceId', tempServiceId)
              that.changeBluetoolthInfo('characteristicId', tempCharacteristicId)
              that.changeBluetoolthInfo('writeCharacteristicId', tempWriteCharacteristicId)
              that.step = 4
              // 执行开锁指令 - 已经调通
              that.writeBLECharacteristicValue(getCommand(that.bluetoothName))
              that.openNotifyService()
            } else {
              console.log('第0个服务特征值不能同时notify和write')
            }
          },
          fail: function(err) {
            console.log('获取服务特征值错误', err)
          }
        })
      }
    },
    // 订阅特征值
    openNotifyService() {
      console.log('开始订阅特征值')
      var that = this
      wx.notifyBLECharacteristicValueChange({
        state: true, // 启用 notify 功能
        // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
        deviceId: that.deviceId,
        // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
        serviceId: that.serviceId,
        // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
        characteristicId: that.characteristicId,
        success(res) {
          console.log('订阅特征值成功', res)
          that.onBLECharacteristicValueChange()
        }
      })
    },
    onBLECharacteristicValueChange() {
      console.log('监听低功耗蓝牙设备的特征值变化事件')
      wx.onBLECharacteristicValueChange(function(res) {
        console.log(`特征值${res.characteristicId}被改变,现在是${res.value}`)
        console.log(buf2hex(res.value))
      })
    },
    writeBLECharacteristicValue(value) {
      console.log('本次发送的指令:', value)
      var that = this
      if (that.connected) {
        //连接蓝牙状态
        var hex = value
        var typedArray = new Uint8Array(
          hex.match(/[\da-f]{2}/gi).map(function(h) {
            return parseInt(h, 16)
          })
        )
        var buffer = typedArray.buffer
        let pos = 0
        let bytes = buffer.byteLength
        while (bytes > 0) {
          let tmpBuffer
          if (bytes > 20) {
            tmpBuffer = buffer.slice(pos, pos + 20)
            pos += 20
            bytes -= 20
          } else {
            tmpBuffer = buffer.slice(pos, pos + bytes)
            pos += bytes
            bytes -= bytes
          }
          console.log(
            '开始写入指令:',
            `deviceId:${that.deviceId}, serviceId:${
              that.serviceId
            }, writeCharacteristicId:${that.writeCharacteristicId}`
          )
          // 向低功耗蓝牙设备特征值中写入二进制数据。注意:必须设备的特征值支持 write 才可以成功调用
          wx.writeBLECharacteristicValue({
            deviceId: that.deviceId,
            serviceId: that.serviceId,
            characteristicId: that.writeCharacteristicId,
            value: tmpBuffer,
            success: function(res) {
              console.log('写入指令结果', res)
              that.openBluetoothSuccess = true
            },
            fail: function(err) {
              console.log('写入指令失败', err)
            }
          })
        }
      } else {
        wx.showModal({
          title: '提示',
          content: '蓝牙已断开',
          showCancel: false,
          success: function(res) {
            that.searching = false
          }
        })
      }
    },
    // 通过递归调用获取已配对蓝牙设备的deviceId,如果获取到了就去连接,
    // devicesId[x]为空说明上传调用getConnectedBluetoothDevices()时获取到的已配对设备全部连接失败了。
    // 则开启重新获取已配对蓝牙设备,并开启扫描附近蓝牙设备
    loopConnect: function(devicesId) {
      var that = this
      // var listLen = devicesId.length
      if (devicesId[this.connectDeviceIndex]) {
        // that.deviceId = devicesId[this.connectDeviceIndex]
        that.changeBluetoolthInfo(
          'deviceId',
          devicesId[this.connectDeviceIndex]
        )
        that.startConnectDevices('loop', devicesId)
      } else {
        console.log('已配对的设备小程序蓝牙连接失败')
        // 开启扫描附近的蓝牙设备
        that.startBluetoothDevicesDiscovery()
        // 开启获取已连接的蓝牙设备
        that.getConnectedBluetoothDevices()
      }
    },
    // 停止搜寻设备
    stopBluetoothDevicesDiscovery() {
      // var that = this
      console.log('停止搜寻附近的蓝牙外围设备')
      wx.stopBluetoothDevicesDiscovery({
        success: function(res) {
          console.log('停止搜寻结果', res)
        },
        fail: function(err) {
          console.log('停止搜寻错误', err)
        }
      })
    }
  }
}

评论区

粤ICP备15040393号-1