走一遭微信小程序蓝牙开发

近期利用周末时间做了小程序蓝牙连接项目之后,总结一下微信小程序蓝牙连接的流程和一些采坑历程。

1. 基本理论

  1. 蓝牙:蓝牙是用于数字设备的短距离无线通信技术标准。从最初1.0版本,到现在已经是5.0版本了。简单的理解,前3个版本(1.0-3.0)包括基础速率Bluetooth Basic Rate(BR)、增强数据速率Enhanced Data Rate(EDR)、高速率High Speed(HS),蓝牙传输数据的速度越来越快了。在4.0版本,引入了低功耗蓝牙Bluetooth Low Energy(BLE)协议,把蓝牙分成低功耗蓝牙(BLE)、传统蓝牙(Classic Bluetooth)、高速蓝牙(Bluetooth high speed)三种模式。5.0版本的新功能主要集中在物联网技术上。

    蓝牙版本 发布时间 最大传输速度 传输距离
    蓝牙5.1 2019 48 Mbit/s 300m
    蓝牙5.0 2016 48 Mbit/s 300m
    蓝牙4.2 2014 24 Mbit/s 50m
    蓝牙4.1 2013 24 Mbit/s 50m
    蓝牙4.0 2010 24 Mbit/s 50m
    蓝牙3.0+HS 2009 24 Mbit/s 10m
    蓝牙2.1+EDR 2007 3 Mbit/s 10m
    蓝牙2.0+EDR 2004 2.1 Mbit/s 10m
    蓝牙1.2 2003 1 Mbit/s 10m
    蓝牙1.1 2002 810 Kbit/s 10m
    蓝牙1.0 1999 723.1 Kbit/s 10m
  2. 低功耗蓝牙(Bluetooth Low Energy):低功耗蓝牙是蓝牙4.0版之后才支持的协议,主要优点有低功耗,低成本等。低功耗蓝牙与经典蓝牙协议不兼容,因此低功耗蓝牙设备不能和经典蓝牙设备连接。

  3. GAPGeneric Access Profile (GAP) 协议定义了设备角色,主要有外围设备(Peripheral)和中心设备(Central),并控制设备连接和广播数据。外围设备通过不停地向外广播数据,让中心设备发现自己,中心设备扫描发现有外围设备存在后,可以与之建立连接,之后就可以使用外围设备提供的服务(Services)

  4. GATTGeneric Attribute Profile (GATT) 协议定义了两个低功耗蓝牙设备使用服务(Services)和特征(Characteristics)的概念来回传输数据的方式。该协议建立在Attribute Protocol(ATT)协议之上。与 GAP 角色有个相似的概念,GATT 分服务端 Server 和客户端 Client

  5. ServicesServices 可以理解为蓝牙设备提供的服务,一个设备可以提供多个服务,每个服务都有一个唯一的UUID,以便可以与其它服务区分开来。一个服务又包含多个 Characteristic 特性值。

  6. CharacteristicsCharacteristics 是 GATT 规范中最小的逻辑数据单元,每个特征总体上包括以下内容:

    1. Declaration:特征声明是特征的重要组成部分,因为它包含特征的UUIDPropertiesProperties重点有以下几种
      1. Read:表示允许客户端读取此特征值
      2. Write:表示允许客户端写此特征值
      3. Notify:每当服务端特征值变化时,服务端将异步通知客户端
      4. Indicate:与Notify类似,区别是它需要客户端确认?(我不确定)
    2. Value: 特征值
    3. Descriptor:每个特征可以有一个或多个特征描述,包含有关特征值的额外信息。
  7. UUIDService Characteristic 都通过一个 16bit128bitUUID进行标识。

以上有些理论知识不太需要知道,写出来主要目的是为了对ServicesCharacteristicsUUID有个更好的了解,也表述的比较简单。

2. 看懂蓝牙接口文档

作为第一次写蓝牙相关的程序的小白,在收到硬件老师傅发来的接口文档的时候,我瞬间懵逼了,这特么是啥:

我们再来看下官方的文档:

// 向蓝牙设备发送一个0x00的16进制数据
let buffer = new ArrayBuffer(1)
let dataView = new DataView(buffer)
dataView.setUint8(0, 0)

wx.writeBLECharacteristicValue({
  // 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
  deviceId,
  // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
  serviceId,
  // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
  characteristicId,
  // 这里的value是ArrayBuffer类型
  value: buffer,
  success (res) {
    console.log('writeBLECharacteristicValue success', res.errMsg)
  }
})

这这这,能对应上吗?

这里官方文档很贴心的在下面给出了相关问答:

看到这里大概就清楚了:

var query =  [0xDD,0x02,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x55] 
var buf = new ArrayBuffer(query.length);
var dataView = new DataView(buf);
query.forEach(function (item, index) {
  dataView.setUint8(index, item);
});
// 写入蓝牙数据
wx.writeBLECharacteristicValue({
  deviceId: deviceId,
  serviceId: serviceId,
  characteristicId: characteristicId,
  value: buf,
  success: function (res) {
    console.log('发送成功')
    console.log(res)
  },
  fail: err => {
    console.log(err)
  }
})

这里笔者踩了坑,网上大多数写入蓝牙数据的时候,将十六进制数据转成了字符串,但是一般转为字符串后,字节数会扩大,导致原来可以一次性发送的数据,必须分两次发送。所以这里大家最好直接使用十六进制的数据发送。

3. 可能你会用到的第三方蓝牙测试工具

3.1 iOS和Android平台

应用名称:nRF Connect,各大App商店均可免费下载,购买时请认准开发者Nordic SemiconductorASA

3.2 Mac 平台

应用名称:LightBlueMac App Store 可免费下载。

4. 发送数据包大于20字节的写法

小程序不会对写入数据包大小做限制,但系统与蓝牙设备会限制蓝牙4.0单次传输的数据大小,超过最大字节数后会发生写入错误,建议每次写入不超过20字节。

这里可以查看你的字符是否超过了20个字节:字节数查询

et buffer = BLEControl.addPassUser(1,1,0,"123456");
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;
  }
  wx.writeBLECharacteristicValue({
    deviceId: this._deviceId,
    serviceId: this._serviceId,
    characteristicId: this._characteristicId,
    value: tmpBuffer,
  })
}

5. 梳理流程

流程以一个完整的蓝牙连接(开始到结束)进行描述,不考虑业务逻辑。

  1. 调用 wx.openBluetoothAdapter 初始化蓝牙模块,请确保手机蓝牙开启,微信拥有蓝牙权限。其他蓝牙相关 API 必须在 wx.openBluetoothAdapter 调用之后使用。

  2. [可选] 通过 wx.onBluetoothAdapterStateChange 监听手机蓝牙状态的改变。

  3. 调用 wx.startBluetoothDevicesDiscovery 搜寻附近的蓝牙外围设备。设置services参数,则只会搜索 主服务的UUID 符合设定的蓝牙设备,从而提高搜索效率。一般同一类型设备主服务的UUID是一样的,以微信硬件平台的蓝牙智能灯为例,主服务的UUIDFEE7

    // 只搜索主服务 UUID 为 FEE7 的设备
      wx.startBluetoothDevicesDiscovery({
        services: ['FEE7'],
        success (res) {
          console.log(res)
        }
      })
    
  4. 调用 wx.onBluetoothDeviceFound 监听搜索到的新设备。在回调函数里可通过advertisDatadeviceIdname等匹配目标设备,具体用什么匹配要根据应用协议,需要注意的是,Android上获取到的deviceId为设备MAC地址,iOS上则为设备uuid。这里也可以在开启蓝牙搜索之后,延迟几秒使用 wx.getBluetoothDevices 获取搜索到的蓝牙设备然后在进行匹配,但在延迟的几秒钟里不一定能搜索到设备,前者可能更适用一些。还有一点需要注意的是部分安卓手机需要开启定位权限才能搜索到设备

    // devices 设备列表结构
      [
        {
          RSSI: (Number),
          advertisData: (ArrayBuffer),
          advertisServiceUUIDs: (Array),
          deviceId: (String),
          localName: (String),
          name: (String),
        }
      ]
    
      // 匹配到device,取deviceId
      deviceId = device.deviceId
    
  5. 在匹配到低功耗蓝牙设备之后,调用 wx.stopBluetoothDevicesDiscovery 停止搜索。

  6. 调用 wx.createBLEConnection 连接低功耗蓝牙设备,参数deviceId传第4步获取到的deviceId。若小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。

  7. [可选] 通过 wx.onBLEConnectionStateChange 监听蓝牙连接状态变化。

  8. 调用 wx.getBLEDeviceServices 获取低功耗蓝牙设备所有服务,参数deviceId传第4步获取到的deviceId

     // services 结构
      [
        {
          isPrimary: (Boolean),
          uuid: (String)
        }
      ]
    
      // service 根据是否为主服务(isPrimary: true)或者uuid是否包含`FEE7`(第3步提到的)??
      serviceId = service.uuid
    
  9. 调用 wx.getBLEDeviceCharacteristics 获取某个服务的所有特征值。特征值描述了服务是否可读,是否可写,是否支持通知等。参数deviceId传第4步获取到的deviceIdserviceId传从上一步获取的serviceId

    // characteristics 结构
      [
        {
          uuid: (String),
          properties: (Object) {
            read: (Boolean)
            write: (Boolean)
            notify: (Boolean)
            indicate: (Boolean)
          }
        }
      ]
    
  10. 如果设备的特征值支持 notify 或者 indicate,调用 wx.notifyBLECharacteristicValueChange 启用低功耗蓝牙设备特征值变化时的 notify 功能,开启成功后再调用 wx.onBLECharacteristicValueChange 监听低功耗蓝牙设备的特征值变化。

  11. 上一步设置完后,如果设备的特征值支持write,通过 wx.writeBLECharacteristicValue 就可以向低功耗设备写二进制数了,如果数据包超过20个字节,分包发送。写成功会触发onBLECharacteristicValueChange监听事件。

  12. 调用 wx.closeBLEConnection 关闭连接,调用 wx.closeBluetoothAdapter 关闭蓝牙模块。

6. 最后

整体流程下来,无论是使用微信小程序的编辑器,还是查看微信小程序的官方文档,到最后的上传和发布,开发体验都是相当不错的,可见微信官方团队这方面真是用心了。