微信小程序蓝牙控制开门

小程序低功耗蓝牙控制开门

整体流程

  1. 初始化蓝牙模块openBluetoothAdapter
  2. 获取本机蓝牙适配器状态getBluetoothAdapterState
  3. 搜索外围蓝牙设备startBluetoothDevicesDiscovery
  4. 监听寻找到新设备onBluetoothDeviceFound
  5. 连接蓝牙createBLEConnection
  6. 获取蓝牙设备的服务getBLEDeviceServices
  7. 获取服务中的特征值getBLEDeviceCharacteristics
  8. 启用特征值变化时的notify功能notifyBLECharacteristicValueChange
  9. 向蓝牙设备写入数据writeBLECharacteristicValue
  10. 关闭蓝牙模块closeBluetoothAdapter

1. 初始化蓝牙模块

  • 初始化蓝牙模块使用的是:wx.openBluetoothAdapter,初始化之前对蓝牙功能做一个判断,看手机微信版本是否支持此功能
  • 初始化之前需要关闭蓝牙模块:wx.closeBluetoothAdapter,否则容易搜索失败
    var _this = this
    if (!wx.openBluetoothAdapter) {
      wx.showModal({
        title: '提示',
        showCancel: false,
        content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试',
      })
    } else {
      wx.closeBluetoothAdapter({
        success: res => {
          wx.openBluetoothAdapter({ // 初始化蓝牙模块
            success: res => {
              console.log('初始化蓝牙成功')
              _this.getBluetoothAdapterState()
            },
            fail: err => {
              console.log(err)
            }
          })
        },
      })
    }

2. 获取本机蓝牙适配器状态

  • 获取本机蓝牙适配器状态使用的是wx.getBluetoothAdapterState,调用成功后,会返回两个参数
    • discovering判断是否正在搜索设备
    • available判断蓝牙适配器是否可用
      getBluetoothAdapterState: function(){
        var _this = this
        wx.getBluetoothAdapterState({
          success: res => {
            if (res.available == false) {
              wx.showToast({
                title: '设备无法开启蓝牙连接',
                icon: 'none',
                duration: 2000
              })
              wx.closeBluetoothAdapter()
            } else if (res.discovering == false) {
              _this.startBluetoothDevicesDiscovery() // 开启搜索外围设备
            } else if (res.available){
              _this.startBluetoothDevicesDiscovery() // 蓝牙适配器正常,去执行搜索外围设备
            }
          }
        })
      }

3. 搜索外围蓝牙设备

  • 搜索外围蓝牙设备使用的是wx.startBluetoothDevicesDiscovery,连接设备后一定要使用wx.stopBluetoothDevicesDiscovery停止搜索
    startBluetoothDevicesDiscovery: function(){
        var _this = this
        wx.startBluetoothDevicesDiscovery({
          allowDuplicatesKey: false,
          success: res => {
            if(!res.isDiscovering){ // 是否在搜索设备
              _this.getBluetoothAdapterState()
            }else{
              _this.onBluetoothDeviceFound() // 搜索成功后,执行监听设备的api
            }
          },
          fail: err => {
            console.log("蓝牙搜寻失败")
            wx.stopBluetoothDevicesDiscovery() // 没有搜索到设备
            wx.closeBluetoothAdapter() // 关闭蓝牙模块
          }
        })
    }

4. 监听寻找到新设备

  • 监听寻找到新设备使用的是wx.onBluetoothDeviceFound,每搜到一个新设备就会触发一次,然后返回一个搜索到的设备列表,包含了设备名称和mac地址,一般都是使用设备名称和mac地址来匹配设备的
    • 安卓和IOS返回的deviceId不一样,安卓返回的是mac地址,IOS返回的是UUID,如果想通过mac地址来匹配设备,可以让mac地址存储在advertisData数据段中,然后解析这个数据段得到mac地址
    • 我使用的是通过设备名称来进行匹配
      onBluetoothDeviceFound: function(){
        var _this = this
        wx.onBluetoothDeviceFound(res => {
          for(let i=0; i<res.devices.length; i++){
            if(res.devices[i].name == "设备名称" || res.devices[i].localName == "设备名称"){
              _this.setData({
                deviceId: res.devices[i].deviceId // 把匹配设备的deviceId存到data中
              })
              wx.stopBluetoothDevicesDiscovery() // 匹配到设备后关闭搜索
              _this.createBLEConnection() // 连接蓝牙
            }
          }
        })
      }

5. 连接蓝牙

  • 连接蓝牙使用的是wx.createBLEConnection,连接蓝牙是通过deviceId连接,deviceId是通过wx.onBluetoothDeviceFound获取的
  • 连接蓝牙容易失败,所以可以定一个变量count用来计算连接的次数,如果超出特定的次数就判断为连接失败,关闭蓝牙模块
    createBLEConnection: function () { // 连接低功耗蓝牙
      var _this = this
      wx.createBLEConnection({
        deviceId: _this.data.deviceId,
        success: res => {
          _this.getBLEDeviceServices()
        },
        fail: err => {
          console.log("连接失败")
          if( count < 6 ){
            count++
            _this.createBLEConnection()
          }else{
            wx.closeBluetoothAdapter() // 连接失败关闭蓝牙模块
          }
        }
      })
    }

6. 获取蓝牙设备的服务

  • 获取蓝牙设备的服务列表使用的是wx.getBLEDeviceServices
    getBLEDeviceServices: function () {
      var _this = this
      wx.getBLEDeviceServices({
        deviceId: _this.data.deviceId,
        success: res => {
          for(let i=0; i<res.services.length; i++){
            // 如果提前得知可以直接判断,如果不知道可以用蓝牙工具看一下服务所需的功能
            if(res.services[i].uuid == _this.data.service){
              _this.setData({
                serviceId: res.services[i].uuid
              })
              _this.getBLEDeviceCharacteristics()
            }   
          }
        },
        fail: err => {
          console.log("获取服务失败")
          wx.closeBluetoothAdapter() // 关闭蓝牙模块
        }
      })
    },

7. 获取服务中的特征值

  • 获取服务中的特征值使用的是wx.getBLEDeviceCharacteristics
    getBLEDeviceCharacteristics: function () { // 获取服务中的特征值
      var _this = this
      wx.getBLEDeviceCharacteristics({
        deviceId: _this.data.deviceId,
        serviceId: _this.data.serviceId,
        success: res => {
          for(let i=0; i<res.characteristics.length; i++){
            let model = res.characteristics[i]
            if ((model.properties.notify || model.properties.indicate) && (model.properties.read && model.properties.write)){
              _this.setData({
                characteristicId: model.uuid
              })
              _this.notifyBLECharacteristicValueChange()
            }
          }
        },
        fail: err => {
          console.log("获取服务中的特征值失败")
          wx.closeBluetoothAdapter() // 关闭蓝牙模块
        }
      })
    },

8. 启用特征值变化时的notify功能

  • 启用特征值变化时的notify功能使用的是wx.notifyBLECharacteristicValueChange
    notifyBLECharacteristicValueChange: function () { // 启用蓝牙特征值变化时的notify功能
        var _this = this
        wx.notifyBLECharacteristicValueChange({
          deviceId: _this.data.deviceId,
          serviceId: _this.data.serviceId,
          characteristicId: _thisa.characteristicId,
          state: true,
          success: res => {
            wx.onBLECharacteristicValueChange(res => {
                console.log(如果要打印需要从arraybuffer格式转为字符串或16进制)
            })
            _this.writeBLECharacteristicValue() // 向蓝牙设备写入数据
          },
          fail: err => {
            console.log("启用BLE蓝牙特征值变化时的notify功能错误")
            wx.closeBluetoothAdapter() // 关闭蓝牙模块
          }
        })

9. 向蓝牙设备写入数据

  • 向蓝牙设备写入数据wx.writeBLECharacteristicValue,这时候就是输入提前设定好的指令
    writeBLECharacteristicValue: function () { // 向蓝牙设备写入数据
      var _this = this
      wx.writeBLECharacteristicValue({
        deviceId: _this.data.deviceId,
        serviceId: _this.data.serviceId,
        characteristicId: _this.data.characteristicId,
        value: buffer, // 这个输入的指令,需要转换成ArrayBuffer
        success: res => {
          console.log("成功")
          wx.closeBluetoothAdapter() // 关闭蓝牙模块
        },
        fail: err => {
          console.log("输入指令失败")
          wx.closeBluetoothAdapter() // 关闭蓝牙模块
        }
      })
    }

转换格式

  • 字符串转为arraybuffer

    string2buffer: function (str) {
        // 首先将字符串转为16进制
        let val = ""
        for (let i = 0; i < str.length; i++) {
          if (val === '') {
            val = str.charCodeAt(i).toString(16)
          } else {
            val += ',' + str.charCodeAt(i).toString(16)
          }
        }
        // 将16进制转化为ArrayBuffer
        return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function (h) {
          return parseInt(h, 16)
        })).buffer
    }
  • arraybuffer转为字符串

    function ab2str(u,f) {
       var b = new Blob([u]);
       var r = new FileReader();
        r.readAsText(b, 'utf-8');
        r.onload = function (){if(f)f.call(null,r.result)}
    }

蓝牙遇见的坑

  • 1.苹果手机有时候输入指令会显示发送成功,但是设备并没有反应
    • 解决方法:把需要输入的指令改成每10毫秒输入一个字节
      
    • 如果改成每10毫秒输入一个字节,安卓手机就会频繁出现10008错误
    • 针对这个问题我用了一个不太好的方法,我判断了一下手机的类型,如果是ios的就分10毫秒输入,如果是安卓的就一次性输入
  • 2.在调用wx.onBluetoothDeviceFound这个api时ios会监听两次,然后就会导致最后设备会有两次指令输入
    • 解决方法:我在搜索蓝牙设备之前做了一个定时器,然后用getBluetoothDevices来查看所有已搜索到的蓝牙,在这个里面做判断来连接蓝牙设备
  • 我上面的步骤没有把这些坑的解决步骤加上,如果碰见此类问题,可以自己在合适的位置修改一下

二维码

我自己做了一个小程序的蓝牙调试器,下面是小程序码,欢迎大家体验
在这里插入图片描述