蓝牙 (Bluetooth)

微信小程序将各平台的蓝牙能力通过统一的接口封装提供给开发者使用。利用微信小程序的蓝牙接口,开发者可以通过无线方式与其他蓝牙设备交换数据。

1. 蓝牙能力概述

蓝牙技术规范由蓝牙技术联盟 (Bluetooth Special Interest Group, SIG) 制定,开发者可以在其官方网站获取到详细的技术文档。

目前蓝牙最为普遍使用的有两种规格:

  • 蓝牙基础率/增强数据率 (Bluetooth Basic Rate/Enhanced Data Rate, BR/EDR): 也称为经典蓝牙。常用在对数据传输带宽有一定要求的场景上,比如需要传输音频数据的蓝牙音箱、蓝牙耳机等;
  • 蓝牙低功耗 (Bluetooth Low Energy, BLE): 从蓝牙 4.0 起支持的协议,特点就是功耗极低、传输速度更快,常用在对续航要求较高且只需小数据量传输的各种智能电子产品中,比如智能穿戴设备、智能家电、传感器等,应用场景广泛。

2. 微信小程序中的蓝牙能力

在微信小程序中,要使用蓝牙能力(Beacon 除外)必须首先调用 wx.openBluetoothAdapter 初始化蓝牙适配器模块,其生效周期为调用 wx.openBluetoothAdapter 至调用 wx.closeBluetoothAdapter 或微信小程序被销毁为止。只有在微信小程序蓝牙适配器模块生效期间,开发者才能够正常调用蓝牙相关的微信小程序 API,并收到蓝牙模块相关的事件回调(绑定监听不受此限制)。

微信小程序对蓝牙支持情况如下:

  • 经典蓝牙:iOS 因系统限制暂无法提供,安卓目前已在规划中。
  • 蓝牙低功耗 (BLE)
    • 主机模式:基础库 1.1.0(微信客户端 iOS 6.5.6,Android 6.5.7)开始支持。
    • 从机模式:基础库 2.10.3 开始支持。
    • 蓝牙信标 (Beacon):基础库 1.2.0 开始支持。

蓝牙低功耗 (Bluetooth Low Energy, BLE)

主机模式:基础库 1.1.0(微信客户端 iOS 6.5.6,Android 6.5.7)开始支持。

从机模式:基础库 2.10.3 开始支持。

蓝牙低功耗是从蓝牙 4.0 起支持的协议,与经典蓝牙相比,功耗极低、传输速度更快,但传输数据量较小。常用在对续航要求较高且只需小数据量传输的各种智能电子产品中,比如智能穿戴设备、智能家电、传感器等,应用场景广泛。

1. 角色/工作模式

蓝牙低功耗协议给设备定义了若干角色,或称工作模式。微信小程序蓝牙目前支持的有以下几种:

1) 中心设备/主机 (Central)

中心设备可以扫描外围设备,并在发现有外围设备存在后与之建立连接,之后就可以使用外围设备提供的服务(Service)。

一般而言,手机会担任中心设备的角色,利用外围设备提供的数据进行处理或展示等等。微信小程序提供低功耗蓝牙接口是默认设定手机为中心设备的。

2) 外围设备/从机 (Peripheral)

外围设备一直处于广播状态,等待被中心设备搜索和连接,不能主动发起搜索。例如智能手环、传感器等设备。

如果外围设备广播时被设置为不可连接的状态,也被称为广播模式 (Broadcaster),常见的例子是蓝牙信标 (Beacon) 设备。

注意

微信小程序中,蓝牙设备可以同时处于主机和从机模式。在安卓设备上,只需要调用 wx.openBluetoothAdapter 初始化一次蓝牙适配器;而在 iOS 设备上,需要分别使用两种不同的 mode 参数分别初始化中心设备和外围设备的蓝牙适配器。建议统一对于主机和从机模式分别进行一次初始化。wx.closeBluetoothAdapter 会同时关闭两种模式的蓝牙适配器。

2. 通信协议

在两个蓝牙低功耗设备建立连接之后,双方的数据交互是基于 GATT (Generic Attribute Profile) 规范,根据该规范可以定义出一个个配置文件 (Profile),描述该蓝牙设备提供的服务 (Service)。

在整个通信过程中,有几个最主要的概念:

  • 配置文件 (Profile): Profile 是被蓝牙标准预先定义的一些 Service 的集合,并不真实存在于蓝牙设备中。如果蓝牙设备之间要相互兼容,它们只要支持相同的 Profile 即可。一个蓝牙设备可以支持多个 Profile。
  • 服务 (Service): Service 是蓝牙设备对外提供的服务,一个设备可以提供多个服务,比如电量信息服务、系统信息服务等。每个服务由一个 UUID 唯一标识。
  • 特征 (Characteristic): 每个 Service 包含 0 至多个 Characteristic。比如,电量信息服务就会有个 Characteristic 表示电量数据。Characteristic 包含一个值 (value)和 0 至多个描述符 (Descriptor) 组成。在与蓝牙设备通信时,主要就是通过读写 Characteristic 的 value 完成。 每个 Characteristic 由一个 UUID 唯一标识。
  • 描述符 (Descriptor): Descriptor 是描述特征值的已定义属性。例如,Descriptor 可指定人类可读的描述、特征值的取值范围或特定于特征值的度量单位。每个 Descriptor 由一个 UUID 唯一标识。

如下图所示,我们可以简单地理解为:每个蓝牙设备可能提供多个 Service,每个 Service 可能有多个 Characteristic,我们根据蓝牙设备的协议对对应 Characteristic 的值进行读写即可达到与其通信的目的。

GATT

3. UUID (Universally Unique Identifier)

根据蓝牙 4.2 协议规范(Vol 3, Part B, section 2.5.1 UUID),UUID 是一个 128 位的唯一标识符,用来标识 Service 和 Characteristic 等。

为了减少存储和传输 128 位 UUID 值的负担,蓝牙技术联盟预分配了一批 UUID,这一批 UUID 拥有一个共同部分,被称为 Bluetooth Base UUID,即 00000000-0000-1000-8000-00805F9B34FB。因此,预分配的 UUID 也可以使用 16 位或 32 位表示,其中 16 位 UUID 最为常用。使用 16/32 位的 UUID 可以降低存储和传输的负载。开发者自定义的 UUID 应注意不能与预分配的 UUID 冲突。

在微信小程序中,wx.startBluetoothDevicesDiscovery 和 wx.getConnectedBluetoothDevices 的参数支持 16/32/128 位 UUID。在其他接口的参数中,

  • iOS 支持直接使用 16 位 和 128 位的 UUID;
  • Android 8.0.9 版本开始,支持直接使用 16/32/128 位 UUID;
  • Android 8.0.9 以下版本,只支持 128 位的 UUID,需要开发者手动补位到 128 位。补位方式如下
    128位UUID = 16位UUID * 2^96 + Bluetooth Base UUID
    128位UUID = 32位UUID * 2^96 + Bluetooth Base UUID
    
    例如
    0x180F -> 0000180F-0000-1000-8000-00805F9B34FB
    

所有接口的返回值统一为 128 位 UUID。

4. 中心设备的使用流程

4.1 初始化蓝牙模块

在使用蓝牙接口前,必须首先调用 wx.openBluetoothAdapter 初始化蓝牙适配器模块,其他接口必须在初始化后成功方可调用。

当蓝牙开关未开启或手机不支持蓝牙时,会返回错误 (errCode=10001)。此时微信小程序蓝牙模块已经初始化完成,可通过 wx.onBluetoothAdapterStateChange 监听手机蓝牙状态的改变,也可以调用蓝牙模块的所有API。开发者在开发中应该考虑兼容用户在使用微信小程序过程中打开/关闭蓝牙开关的情况,并给出必要的提示,提高可用性。

4.2 扫描并发现蓝牙外围设备

蓝牙模块初始化成功后,一般需要通过 wx.startBluetoothDevicesDiscovery 扫描外围设备。当蓝牙外围设备被扫描到时,会回调 wx.onBluetoothDeviceFound 事件,返回扫描到的设备。扫描设备比较耗费系统资源,请在搜索到需要的设备后及时调用 wx.stopBluetoothDevicesDiscovery 停止搜索。

若之前已连接过某个设备,获取到了 deviceId,可跳过扫描步骤。

// 监听扫描到新设备事件
wx.onBluetoothDeviceFound((res) => {
  res.devices.forEach((device) => {
    // 这里可以做一些过滤
    console.log('Device Found', device)
  })
  // 找到要搜索的设备后,及时停止扫描
  wx.stopBluetoothDevicesDiscovery()
})

// 初始化蓝牙模块
wx.openBluetoothAdapter({
  mode: 'central',
  success: (res) => {
    // 开始搜索附近的蓝牙外围设备
    wx.startBluetoothDevicesDiscovery({
      allowDuplicatesKey: false,
    })
  },
  fail: (res) => {
    if (res.errCode !== 10001) return
    wx.onBluetoothAdapterStateChange((res) => {
      if (!res.available) return
      // 开始搜寻附近的蓝牙外围设备
      wx.startBluetoothDevicesDiscovery({
        allowDuplicatesKey: false,
      })
    })
  }
})

4.3 连接设备

蓝牙低功耗设备间要进行通信,必须首先建立连接。

wx.createBLEConnection({
  deviceId, // 搜索到设备的 deviceId
  success: () => {
    // 连接成功,获取服务
    wx.getBLEDeviceServices({
      deviceId,
    })
  }
})

4.4 获取蓝牙外围设备的服务

wx.getBLEDeviceServices({
  deviceId, // 搜索到设备的 deviceId
  success: (res) => {
    for (let i = 0; i < res.services.length; i++) {
      if (res.services[i].isPrimary) {
        // 可根据具体业务需要,选择一个主服务进行通信
      }
    }
  }
})

4.5 读写服务的特征值

wx.getBLEDeviceCharacteristics({
  deviceId, // 搜索到设备的 deviceId
  serviceId, // 上一步中找到的某个服务
  success: (res) => {
    for (let i = 0; i < res.characteristics.length; i++) {
      let item = res.characteristics[i]
      if (item.properties.write) { // 该特征值可写
        // 本示例是向蓝牙设备发送一个 0x00 的 16 进制数据
        // 实际使用时,应根据具体设备协议发送数据
        let buffer = new ArrayBuffer(1)
        let dataView = new DataView(buffer)
        dataView.setUint8(0, 0)
        wx.writeBLECharacteristicValue({
          deviceId,
          serviceId,
          characteristicId: item.uuid,
          value: buffer,
        })
      }
      if (item.properties.read) { // 该特征值可读
        wx.readBLECharacteristicValue({
          deviceId,
          serviceId,
          characteristicId: item.uuid,
        })
      }
      if (item.properties.notify || item.properties.indicate) {
        // 必须先启用 wx.notifyBLECharacteristicValueChange 才能监听到设备 onBLECharacteristicValueChange 事件
        wx.notifyBLECharacteristicValueChange({
          deviceId,
          serviceId,
          characteristicId: item.uuid,
          state: true,
        })
      }
    }
  }
})
// 操作之前先监听,保证第一时间获取数据
wx.onBLECharacteristicValueChange((result) => {
  // 使用完成后在合适的时机断开连接和关闭蓝牙适配器
  wx.closeBLEConnection({
    deviceId,
  })
  wx.closeBluetoothAdapter({})
})

4.6 断开连接和关闭蓝牙适配器

使用完成后,应该在合适的时机断开连接,并关闭蓝牙适配器。

5. 注意事项

  • 在 iOS 上,对特征值的 readwritenotify 操作,由于系统需要获取特征值实例,传入的 serviceIdcharacteristicId 必须由 wx.getBLEDeviceServices 与 wx.getBLEDeviceCharacteristics 中获取到后才能使用。建议统一在建立连接后先执行 wx.getBLEDeviceServices 与 wx.getBLEDeviceCharacteristics 后再进行与蓝牙设备的数据交互。
  • 考虑到蓝牙功能可以间接进行定位,安卓 6.0 及以上版本,无定位权限或定位开关未打开时,无法进行设备搜索。
  • 在安卓上,部分机型获取设备服务时会多出 0000180000001801 UUID 的服务,这是系统行为,注意不要使用这两个服务。
  • 建立连接和关闭连接必须要成对调用。如果未能及时关闭连接释放资源,安卓上容易导致 state 133 GATT ERROR 的异常。
  • 在与蓝牙设备传输数据时,需要注意 MTU(最大传输单元)。如果数据量超过 MTU 会导致错误,建议根据蓝牙设备协议进行分片传输。安卓设备可以调用 wx.setBLEMTU 进行 MTU 协商。在 MTU 未知的情况下,建议使用 20 字节为单位传输。

蓝牙低功耗网状网络(BLE Mesh)

蓝牙低功耗网状网络(BLE Mesh)是建立在蓝牙低功耗(BLE)协议基础上的一种通信协议,它允许大量 BLE 设备组成一个网状网络,在足够大的物理覆盖范围内实现设备之间的互联与协同控制,从而满足多设备场景下的通信需求。

BLE Mesh 通信协议支持低功耗、可扩展性、灵活性、高可靠性、安全性等优秀特性,在智能家居、智能照明、工业自动化、医疗健康、环境监测等领域具有广泛的应用价值。

1. 基础概念

BLE Mesh 网络

BLE Mesh 网络是一种多对多的网络拓扑结构,网络中的设备节点通过「发布 / 订阅机制」收发消息。

设备配网

未配网 BLE Mesh 设备需要完成配网操作后,才能加入 BLE Mesh 网络并成为设备节点,与网络中的其他节点进行消息通信。帮助 BLE Mesh 设备完成配网操作的设备叫做「启动配置设备」。

设备节点

未配网 BLE Mesh 设备经过配网操作后,就成为了 BLE Mesh 网络中的设备节点。设备节点一般都具有中继、代理、好友、低功耗等特性中的一个或多个。

设备节点由多个元素构成,每个元素包含了多个模型,而每个模型定义了节点的基本功能,比如节点所需要的状态、控制状态的消息以及处理消息所产生的动作等。节点功能的实现是基于模型的,模型可分为 SIG 模型和自定义模型,前者由 SIG 定义,而后者由开发者定义。模型也可基于消息的发送 / 接收方分为客户端模型与服务端模型。

Mesh 地址

BLE Mesh 网络中的设备节点之间想要进行消息通信,就需要为每个节点分配地址用于消息的收发。Mesh 地址主要分为单播地址、组播地址、虚拟地址三种。

单播地址是在设备配网成功后由「启动配置设备」分配的。单播地址可能会出现在消息的来源 / 目标地址字段中。发送到单播地址的消息只能由拥有该单播地址的元素进行处理。

组播地址是 BLE Mesh 网络中的一种多播地址,通常用于将设备节点进行分组。如果发送带有组播地址的消息,所有订阅过该组播地址的设备节点都会收到该消息。

虚拟地址与特定的 UUID 标签相关联,可以用作模型的发布地址或订阅地址。

Mesh 消息

Mesh 消息分为控制消息与接入消息。控制消息是与 BLE Mesh 网络操作有关的消息,例如心跳和好友的请求消息。接入消息允许客户端模型检索或设置服务端模型中的状态值,或被服务端用于报告状态值。

Mesh 消息是 BLE Mesh 网络中数据传输的基本单位,由操作码(opcode)和携带参数(parameters)组成,前者用于标识消息的用途唯一性,后者可以存储有效数据,例如目标地址、设备状态等。

代理设备

如果想要不是 BLE Mesh 设备的其他设备(例如手机)也能成为 BLE Mesh 网络中的一员,可以通过与代理设备节点进行 GATT 连接,借助代理设备实现在 BLE Mesh 网络中收发各种消息。

2.「微信小程序 BLE Mesh 插件」使用流程

在微信小程序中,基于标准 BLE Mesh 通信协议,以微信小程序插件形式提供了 BLE Mesh 的基础能力。开发者可以通过「微信小程序 BLE Mesh 插件」实现 BLE Mesh 设备的本地快速配网、控制管理、网络共享等功能。平台提供了「接口文档」与「示例微信小程序」,方便开发者接入使用。

2.1 接入插件

在使用插件前,首先需要在微信小程序管理后台的「设置 – 第三方服务 – 插件管理」中添加插件,开发者可以登录微信小程序管理后台,通过 appid – wx013447465d3aa024 查找插件并添加。然后在 app.json 文件中声明指定版本的插件。关于微信小程序插件的具体介绍可参考「使用插件」。

目前只有申请了「工具—设备管理」服务类目的微信小程序才能使用「微信小程序 BLE Mesh 插件」。

"plugins": {
  "ble-mesh-plugin": {
    "version": "latest",
    "provider": "wx013447465d3aa024"
  }
}

通过 requirePlugin 方法才能获取「微信小程序 BLE Mesh 插件」的相关接口。例如可以像下面这样调用:

const { getMeshBLEManager } = requirePlugin('ble-mesh-plugin')

2.2 初始化 BLE Mesh 蓝牙配置器

在使用微信小程序 BLE Mesh 相关能力前,需要初始化 BLE Mesh 蓝牙配置器,用于扫描发现周围的 BLE Mesh 设备。

const meshBLEManager = getMeshBLEManager()

meshBLEManager.init().then(({ enabled }) => {
  if (!enabled) {
    wx.showModal({
      title: '错误',
      content: '未启用蓝牙功能, 请打开蓝牙后重试',
      showCancel: false,
    })
  }
})

2.3 扫描发现 BLE Mesh 设备

BLE Mesh 蓝牙配置器初始化后,需要通过 meshBLEManager.startScanMeshDevice 扫描 BLE Mesh 设备,如果在附近扫描到设备就会回调 meshBLEManager.onMeshDeviceFound 事件,返回扫描到的 BLE Mesh 设备实例。由于扫描设备操作比较耗费系统资源,请在搜索到需要的设备后及时调用 meshBLEManager.stopScanMeshDevice 停止扫描。

meshBLEManager.onMeshDeviceFound((res) => {
  // 扫描到的 BLE Mesh 设备
  console.log(res.device)
  // 找到需要的设备后,停止扫描操作
  meshBLEManager.stopScanMeshDevice()
})

// 开始扫描 BLE Mesh 设备
meshBLEManager.startScanMeshDevice({
  allowDuplicatesKey: false,
})

2.4 创建 BLE Mesh 网络

想要实现多个 BLE Mesh 设备之间的互联与协同控制,就需要先创建一个 BLE Mesh 网络。

createMeshNetwork({ name: 'mesh_network' }).then((res) => {
  // 创建 BLE Mesh 网络后会生成唯一的网络标识id,用于后续的网络管理、设备配网、设备消息通信等功能。
  console.log(res.networkId)
})

2.5 共享 BLE Mesh 网络

开发者可以通过 exportMeshNetworksimportMeshNetworks 实现 BLE Mesh 网络数据的导入导出,让多个微信小程序用户可以控制管理同一网络下的 BLE Mesh 设备。此外调用 getMeshNetworks 还可以获取当前存在的所有 BLE Mesh 网络列表。

// 导出 BLE Mesh 网络数据
const { restoreData } = exportMeshNetworks({ data: [{ name: 'mesh_network' }] })
// 导入 BLE Mesh 网络数据
importMeshNetworks({ restoreData })
// 获取当前存在的 BLE Mesh 网络列表
const networks = getMeshNetworks() // [{ name: 'network_name', networkId: 'network_id' }]

注意

在通过 exportMeshNetworks 导出 BLE Mesh 网络数据后,可能需要对数据进行持久化存储。为了保证网络安全,开发者应该将数据加密后再执行存储操作,然后在导入时将数据解密。

在通过 importMeshNetworks 将 BLE Mesh 网络数据导入新用户微信小程序时,由于 iOS 系统限制,同一台 BLE Mesh 设备,不同用户获取到的蓝牙 deviceId 不同,导致无法建立代理设备连接。此时需要设备端在配网成功后持续广播 Node Identity 蓝牙广播包,方便微信小程序插件识别设备节点,更新蓝牙 deviceId 。

2.6 BLE Mesh 网络管理

通过 getMeshNetworkManager 可以获取 BLE Mesh 网络管理配置器,它包括了创建 / 删除 BLE Mesh 群组、获取 BLE Mesh 网络中所有节点的单播地址列表以及组播地址列表等功能。

// 获取 BLE Mesh 网络管理配置器
const meshNetworkManager = getMeshNetworkManager({ networkId: 'network_id' })
// 创建 BLE Mesh 群组
const group = meshNetworkManager.createGroup({ name: 'group_name' })
// 删除 BLE Mesh 群组
meshNetworkManager.removeGroup({ name: 'group_name' })
// 获取组播地址列表
const groups = meshNetworkManager.getGroups()
[{
  name, // BLE Mesh 群组名称
  address, // BLE Mesh 组播地址
  nodes, // BLE Mesh 组播地址下绑定的设备节点信息
}]
// 获取单播地址列表
const unicasts = meshNetworkManager.getUnicasts()
[{
  deviceId, // BLE Mesh 设备id
  name, // BLE Mesh 设备名称
  localName, // BLE Mesh 设备广播数据段中的 LocalName 数据段
  address, // BLE Mesh 设备单播地址
}]

2.7 BLE Mesh 设备配网

想要实现 BLE Mesh 设备配网,首先需要获取 BLE Mesh 设备入网配置器 provisioningManager ,然后通过 provisioningManager.provision 完成配网操作。此外还可以通过 provisioningManager.batchProvision 实现一键配网多个 BLE Mesh 设备。当 BLE Mesh 设备完成配网操作后,才算真正加入了 BLE Mesh 网络。

// 获取 BLE Mesh 设备入网配置器
const provisioningManager = getProvisioningManager({ networkId: 'network_id' })
// 单个 BLE Mesh 设备配网
await provisioningManager.provision({
  unprovisionedDevice, // 未配网的 BLE Mesh 设备实例
  authenticationMethod: AuthenticationMethod.NoOOB, // 配网时的 OOB 安全认证方式
})
// 多个 BLE Mesh 设备批量配网
await provisioningManager.batchProvision({
  unprovisionedDevices, // 未配网的 BLE Mesh 设备实例数组
})

2.8 BLE Mesh 设备消息通信

在完成 BLE Mesh 设备配网后,如果想要和设备进行消息通信,实现控制设备与获取设备信息等功能。首先应该获取 BLE Mesh 代理设备客户端配置器 proxyClientManager ,在开启蓝牙扫描的情况下使用 proxyClientManager.hasProxyServer 确认周围是否存在可用的代理设备,然后通过 proxyClientManager.addAppKeyToNodeproxyClientManager.bindAppKeyToModel 将 BLE Mesh 网络中的 AppKey 绑定到目标设备的 Server 模型上,最后发送标准的 BLE Mesh 消息,用于改变目标设备 Server 模型上的状态或者检索相关信息。

// 获取 BLE Mesh 代理设备客户端配置器
const proxyClientManager = getProxyClientManager({ networkId: 'network_id' })
// 检查周围是否存在可用的代理设备 Server
const hasProxyServer = proxyClientManager.hasProxyServer
if (hasProxyServer) {
  // 添加 AppKey 到目标设备
  await this.proxyClientManager.addAppKeyToNode({
    destination, // 目标设备的单播地址
  })
  // 绑定 AppKey 到目标设备的 GenericOnOffServer 模型上
  await proxyClientManager.bindAppKeyToModel({
    destination, // 目标设备的单播地址
    modelId: ModelIds.GenericOnOffServer, // 标准 GenericOnOffServer 模型的 modelId
  })
  // 获取目标设备的 GenericOnOffServer 模型上的开关状态
  const resGet = await proxyClientManager.getOnOffStatus({
    destination, // 目标设备的单播地址或组播地址
  })
  console.log(resGet.source, resGet.message.isOn)
  // 控制目标设备的 GenericOnOffServer 模型上的开关状态
  const resSet = await proxyClientManager.setOnOffStatus({
    destination, // 目标设备的单播地址或组播地址
    status, // 需要设置的开关状态
  })
  console.log(resSet.source, resSet.message.isOn)
  // 开发者还可以通过自定义 BLE Mesh 消息,发送接收最基础的 opcode 和 parameters ,来实现例如 Vendor Model 的功能。
  proxyClientManager.onMessage(({ source, opcode, parameters }) => {
    // handle mesh message
  })
  proxyClientManager.send({
    destination, // 目标设备的单播地址或组播地址
    opcode, // 操作码
    parameters, // 携带参数
  })
}

3. 注意事项

  1. 开发者可以通过「微信小程序 BLE Mesh 插件」示例微信小程序 ,快速接入微信小程序 BLE Mesh 能力。

  2. 关于微信小程序 BLE Mesh 能力的详细描述,请查阅「微信小程序 BLE Mesh 插件」接口文档

  3. 请尽量使用「微信小程序 BLE Mesh 插件」latest 版本,以保证功能完善、稳定可用。

  4. 目前「微信小程序 BLE Mesh 插件」只提供了 BLE Mesh 的基础能力,后面会根据开发者需要持续迭代更新。

蓝牙信标 (Beacon)

基础库 1.2.0 开始支持。

蓝牙信标 (Beacon) 是建立在蓝牙低功耗 (BLE) 协议基础上的一种广播协议。

Beacon 设备作为蓝牙低功耗协议中的外围设备,持续向周围广播包含设备标识的特定数据包,但不能和中心设备建立连接。微信小程序运行的设备作为中心设备,可以收到 Beacon 设备的广播包,实现数据交互。常用于室内定位、消息推送等场景。

微信小程序中,开发者可以通过 wx.startBeaconDiscovery 开始搜索 Beacon 设备,并通过 wx.onBeaconUpdate 接收设备更新事件。

1. 设备标识

每个 Beacon 设备的广播包中,至少携带了以下信息,共同组成了设备的唯一标识符。

  • UUID (16 字节):128 位的 UUID,用于唯一标识微信小程序所识别的一系列信标设备。
  • major (2 字节):0 – 65535 的无符号整数,可以用来区分相同 UUID 的一组设备。
  • minor (2 字节):0 – 65535 的无符号整数,可以用来区分有相同 UUID 和 major 的设备。

2. 设备状态

当微信小程序接收到 Beacon 设备的信号时,还会提供下列信息

  • rssi: 信号强度,单位为 dBm。
  • proximity: Beacon 标识设备距离的枚举值(仅 iOS)。
  • accuracy: Beacon 设备的距离,单位为米。

3. 注意事项

  • Beacon 相关接口可以直接使用,不需要使用 wx.openBluetoothAdapter 初始化蓝牙适配器模块
  • 由于 Beacon 可以被用来进行定位,因此需要微信有系统的位置权限时才能使用。

NFC

支持平台:Android

支持 HCE(基于主机的卡模拟)模式,即将安卓手机模拟成实体智能卡。 支持 NFC 读写,即手机作为读卡器使用。

  • 适用机型:支持 NFC 功能,且系统版本为 Android 5.0 及以上的手机
  • 适用卡范围:符合 ISO 14443-4 标准的 CPU 卡
  • 支持 Reader / Writer(读取器 / 写入器)模式,即支持 NFC 设备读取或写入被动 NFC 标签和贴纸
  • 适用机型:支持 NFC 功能,且系统版本为 Android 5.0 及以上的手机
  • 适用范围:
    • 支持 NFC-A (ISO 14443-3A) / NFC-B (ISO 14443-3B) / NFC-F (JIS 6319-4) / NFC-V (ISO 15693) / ISO-DEP (ISO 14443-4) 标准的读写
    • (部分 Android 手机)支持 MIFARE Classic / MIFARE Ultralight 标签的读写
    • 支持对 NDEF 格式的 NFC 标签上的 NDEF 数据的读写

无线局域网 (Wi-Fi)

在微信小程序中支持搜索周边的 Wi-Fi 设备,同时可以针对指定设备,传入密码发起连接。

该系列接口为系统原生能力,如需查看「微信连 Wi-Fi」能力及配置跳转微信小程序,请参考文档

1. 连接指定 Wi-Fi 设备

如果知道 Wi-Fi 设备名称和密码,并确认设备在附近,可以直接在微信小程序中连接指定 Wi-Fi。

接口调用时序为:

  1. startWifi: 初始化 Wi-Fi 模块
  2. connectWifi: 连接 Wi-Fi(iOS 需 11 及以上版本支持)
  3. onWifiConnected: 连接上 Wi-Fi 的事件回调

2. 连接周边 Wi-Fi 设备

微信小程序可以通过扫描附近的 Wi-Fi 设备,让用户选择某个设备进行连接。

由于系统限制,不同平台下接口调用时序有所差异:

Android

  1. startWifi: 初始化 Wi-Fi 模块
  2. getWifiList: 请求获取周边 Wi-Fi 列表
  3. onGetWifiList: 获取到 Wi-Fi 列表数据事件
  4. connectWifi: 连接 Wi-Fi
  5. onWifiConnected: 连接上 Wi-Fi 的事件回调

iOS

  1. startWifi: 初始化 Wi-Fi 模块
  2. getWifiList: 请求获取周边 Wi-Fi 列表。本接口会跳转到系统设置中的微信设置页,需引导用户进入「无线局域网」设置页,手动连接设备。(iOS 11.0 及 11.1 版本因系统问题失效)
  3. onGetWifiList: 获取到 Wi-Fi 列表数据事件
  4. setWifiList: 设置 Wi-Fi 列表 中 AP 的相关信息,辅助用户进行连接
  5. onWifiConnected: 连接上 Wi-Fi 的事件回调

3. Wi-Fi 网络下的设备通信

通过 wx.getConnectedWifi 可以获取当前系统连接 Wi-Fi 信息,在确认当前连接是设备 Wi-Fi 后(手机与设备处于同一局域网),便可以使用相关接口与设备进行通信。

  • 使用 wx.startLocalServiceDiscovery 等一系列 mDNS API ,可以获取局域网内提供 mDNS 服务的设备 IP 。然后通过 wx.request / wx.connectSocket 并传入格式为 ${IP}:${PORT}/${PATH} 的 url 参数,就可以进行本地 HTTP / WebSocket 通信。详细文档参考「局域网通信」。

  • 开发者根据具体设备的情况,在知道与设备通信的 ip address 和 port 之后,使用 TCPSocket.connect 或 UDPSocket.connect 就能与设备进行 TCP 或 UDP 通信。详细文档参考「TCP 通信」与「UDP 通信」。

4. 注意事项

  • Android 系统 6.0 以上版本,在没有打开定位开关的时候会导致设备不能正常获取周边的 Wi-Fi 信息。
  • Wi-Fi 相关接口暂不可用 wx.canIUse 接口判断。

硬件设备接入指引

提供硬件设备联网、控制、通讯等能力的微信小程序,在完成设备接入后,才可以使用微信小程序提供的硬件能力(例如「设备消息」、「音视频通话」)等。

接入条件

经过微信认证的非个人主体微信小程序。

面向智能硬件生产企业或开发者。

接入步骤

1. 申请设备类目

登录「微信小程序管理后台」——「设置」——「基本设置/服务类目」,点击「申请更多类目」(一个微信小程序最多可申请5个服务类目)。

添加「工具——设备管理」为微信小程序类目。

2. 开通硬件设备能力

登录「微信小程序管理后台」——「功能」——「硬件设备」,阅读设备使用条件和接入流程等,点击「开通」。

管理员扫码确认后开通成功,进入设备管理页面。

3. 添加设备类型

点击添加设备,按照每个字段对应的说明填写信息,如实填写设备相关信息,否则会导致审核不通过。

每次可注册一种设备类型,例如“空调—空调1号”和“空调—空调2号”需要分别进行注册。

注意:

  • 选择设备类型时,请认真判断注册的设备类型是否已经是已有的设备类型,比如“洗拖一体机”属于“生活电器——扫地机器人”,请不要重复添加平台设备库中已有的设备品类。如果是设备库中缺失的设备类型,可以选择“其他”。

4. 获取设备 model_id

设备注册成功后,可以获得平台分配的 model_id,model_id 是调用微信小程序设备能力相关接口的重要凭证。获取 model_id 后,微信小程序可以按照相关文档指引调用「设备消息」等硬件能力。

微信小程序设备消息

能力介绍

「微信小程序设备消息」是一种长期订阅类型的「微信小程序订阅消息」,且需要完成「设备接入」才能够使用。

用户在使用设备过程中,需要关注某些由设备触发且需要人工介入的事件。例如安防摄像头检测到异常,设备耗材不足,设备发生故障等等。

「微信小程序设备消息」能力指的是,只要用户在微信小程序内订阅通知,开发者就可以将这些事件以订阅消息的形式发送给用户。消息在微信内的产品形态,目前以「服务通知」形式呈现。

开发流程

1. 设备接入

微信小程序想要使用设备消息能力,首先需要接入设备,详见「设备接入」文档。

完成接入后,开发者可获得由平台分配的 model_id 。model_id 对应一种设备类型,也是调用微信小程序设备能力相关接口的重要凭证。

2. 获取模版 ID

登录「微信小程序管理后台」——「功能」——「订阅消息」——「公共模板库」——「长期订阅」,查看可选用的设备消息模板。

选择设备消息模板中需要的关键词,并提交。

注意:设备消息模版的关键词内容由平台生成,为枚举值,开发者不能够自定义内容。

提交后,可在「我的模板」中找到对应模板的模板 ID ,每个模板以 template_id 标记。

3. 获取设备票据

获取 snTicket 用于「发起订阅」步骤。

详见服务端设备票据接口 hardwareDevice.getSnTicket 。

4. 发起订阅

调用 wx.requestSubscribeDeviceMessage 接口会有以下授权弹窗出现,用户同意订阅消息后,才会有设备消息发送至用户的微信会话。

微信小程序内完成设备消息订阅

用户订阅设备消息时,需要手动点击“添加提醒”,设备触发消息后才会出现“响铃+振动”的强提醒状态,开发者可以在前端界面进行引导。

示例代码

wx.requestSubscribeDeviceMessage({
    sn: 'xxxx',
    snTicket: 'xxxxx',
    modelId: 'xxxxx',
    tmplIds: ['xxxxx'],
    success(res) {
        console.log('[wx.requestSubscribeDeviceMessage success]: ', res)
        // { 'QCpBsp1TGJ1ML-UIwAIMkdXpPGzxSfwJqsKsvMVs3io': 'accept' }
    },
    fail(res) {
        console.log('[wx.requestSubscribeDeviceMessage fail]: ', res)
    }
})

5. 发送设备消息

开发者通过微信服务端接口向用户推送设备消息。

详见服务端设备消息发送接口 hardwareDevice.send 。

服务通知 – 设备消息

设备消息具体形式

设备认证

在使用微信小程序提供的部分硬件能力时,需要提前将设备在微信进行注册,以便于微信验证设备的真实可信。

例如:微信小程序音视频通话(for 硬件)

1. 设备要求

微信需要硬件能力来对设备身份进行校验。设备厂商需要保证设备满足一定条件。

1.1 安卓设备

设备需要满足下列条件之一:

  • 设备 EMMC/UFS 存储上的 RPMB(Replay Protected Memory Block) 分区未被使用;
  • 设备支持 TEE,并能按照《设备认证 TEE 规范》开发 TA 并提交验收。

此外,设备厂商需要内置一个 RPMB 分区读写及通信的 RPMBD 服务(由微信提供,参考第 3 节),并保证服务能够开机正常启动。

1.2 Linux 设备

设备需要满足下列条件:

  • 设备 EMMC/UFS 存储上的 RPMB(Replay Protected Memory Block) 分区未被使用;

2. 安全策略

对于同一个 modelId,每一台物理设备应分配唯一且不变的 SN。 如果检测到包括但不限于下列情况,可能会导致设备能力被封禁:

  • 多台设备共用同一个 SN;
  • 同一台设备交替使用多个不同的 SN;
  • 使用虚假设备进行设备注册;
  • 其他伪造或滥用设备的行为。

3. 设备认证(安卓)

3.1 部署 RPMBD 服务

设备认证需要使用 EMMC/UFS 存储上的 RPMB 分区来保证设备的身份,需要设备厂商内置一个 RPMB 分区读写及通信的服务,并保证服务能够开机正常启动。

3.1.1 下载服务

请在 此处 下载对应平台、版本的 rpmbd 二进制文件。

注意:ARM 64 位版本(TEE)需要设备商按照规范开发 TEE 对应的 TA 模块,详细规范与流程参考设备认证 TEE 规范。

3.1.2 运行服务

将下载的 rpmbd 二进制(以下假设文件名为 rpmbd,下载后可以重命名)集成至系统里并以服务的方式运行起来。

注意

  • RPMBD 服务不仅用于注册设备过程,后续使用相关硬件能力时,都需要保证 RPMBD 服务一直运行
  • 每一颗 EMMC/UFS 存储芯片的 RPMB KEY 只能被写一次,不能修改。 如果被写入错误的值 (非注册时的 model_id 和 sn),那么这颗芯片就无法继续使用。
  • 高版本的 android 安全性较强,可能还需要配置 SELinux,且只支持在 system 分区启动。可参考 SELinux 参考配置

运行方式为:

rpmbd /dev/mmcblk1rpmb # /dev/mmcblk1rpmb 为rpmb分区路径, 开发者需要根据自己设备的情况具体填写(高通平台不需要指定)

参考如下 rc 的启动方式:

  • /system/etc/init,放到 system 分区启动(建议)

    service rpmbd /system/bin/rpmbd /dev/mmcblk1rpmb
      class main
      user root
      group root system
    
  • /vendor/etc/init,放到 vendor 分区启动(仅 Android < 8 支持)

    service rpmbd /vendor/bin/rpmbd
      class main
      user root
      group root system
    

3.2 注册设备

在完成 RPMBD 服务部署后,需要使用 WMPF 认证设备。

4. 设备认证(Linux)

  • 使用「微信小程序音视频通话 SDK(直连 Linux 设备)」的设备,请使用wx_device_register注册设备。

5. 常见问题

(1) 注册设备报错 emmc write fail00

检查 rpmbd 服务启动参数里的 rpmb 分区路径是否正确。 若路径正确,确认此路径对应的 rpmb 分区在 Android OS 下能否被访问。


(2) 报错 cert fail

应用缓存被清理,或 Android 认为 APK 有变动导致 keystone 中数字证书失效导致。

需要清理 apk 数据缓存再使用相同的 appid、model_id、SN 调用 registerDevice/registerVoipDevice 刷新密钥。


(3) 接口报错 ticket 1 invalid rpmb_buffer

当前 rpmbd 与 SDK aar 的版本不兼容,应保持二者使用相同版本。例如:rpmbd 服务使用了 1.3 以下版本,而 SDK 使用了 1.3 或以上的版本。


(4) 注册设备返回 -7,或调用接口报错 failed to get native service 或其他获取 rpmbd 服务失败的错误

  • 确认已部署 rpmbd 服务,且服务正常运行。(可以通过 ps 查看)
  • Android >= 8 版本,请确认 rpmbd 是在 system 分区启动
  • 如果启用了 SELinux,需确认 SELinux 的相关规则已正确配置


(5) 注册设备报错 register: null

高版本 android 不允许在主线程里进行网络请求,需要单独开线程里来调用 SDK 接口


(6) 使用物联网卡时,网络请求一直失败

物联网卡请使用 WMPF 注册设备,或设备认证 SDK >= 1.3.1 版本,并确保 servicewechat.com 域名能够正常访问。


(7) 注册设备报错 9800004,device xxx is not confirmed

绝大多数情况是因为注册设备时使用了 1.3 以下版本的 设备认证 SDK,且同时发起了多次 registerVoipDevice 请求,此时有概率设备端使用的密钥与后台不同步,导致设备再也无法成功注册,且该过程不可逆。

建议开发者使用 WMPF 注册设备,或升级到设备认证 SDK 1.3 及以上版本,使用低版本时请务必保证前一次 registerVoipDevice 返回前不要重复调用。


(8) 注册设备报错 9800004,device xxx not registered

绝大多数情况是当前设备之前使用不同的 modelId/sn 进行了注册。如使用 WMPF 注册设备,可以使用 getMiniProgramDeviceInfo 检查下当前设备内的 sn 和 modelId,和传入的是否一致。


(9) 获取票据 getCallerTicket/getDeviceToken 报错 9800004

一般是因为传入的 mode_id 与最初注册设备时不一致。


(10) 报错 ticket 0 digital-sig check fail

多数是因为当前设备已经在这台设备的另一个 App 中注册过,目前设备验证只能用于单个应用。需要再使用相同的 appid、model_id、SN 重新调用 registerDevice/registerVoipDevice 刷新密钥。

例如,同时混用「使用 WMPF 认证设备」和「设备认证 SDK」,可能会导致 WMPF 和 开发者应用互相抢占密钥,导致这个错误。


(11) 注册设备报错 40234 hmac check fail

可能有以下原因

  • 设备已经使用其它的 model_id/sn 注册过,此次注册传入了不同的 model_id;
  • 设备曾经注册过,且注册设备时使用了 1.3 以下版本的设备认证 SDK,且同时发起了多次 registerVoipDevice 请求,此时有概率设备端使用的密钥与后台不同步,导致设备再也无法成功注册,且该过程不可逆。


(12) 获取 deviceToken 时报错 register info invalid

当从「设备认证 SDK」切换到「使用 WMPF 认证设备」后,需要调用 WMPF registerMiniProgramDevice 重新进行设备注册,若未调用或调用未成功,则在需要获取 deviceToken 的场景会报这个错误。


使用 WMPF 认证设备(安卓)

在系统集成 rpmbd 后,如果设备上安装了 微信小程序硬件框架(WMPF),可以直接使用 WMPF 认证设备。

相比使用设备认证 SDK,使用 WMPF 注册设备有以下优势:

  • 接入成本低:不需要额外引入设备认证 SDK,不占用包大小,接入成本更低。
  • 免维护设备凭证:deviceToken 的获取由框架按需进行,开发者只需要进行设备注册,不需要维护 deviceToken,也不需要手动传递给微信小程序,维护成本更低。

注意

  • 注1:使用 WMPF 时,需先保证 rmpbd 服务正常运行。
  • 注2:使用设备认证 SDK 注册的设备,需要重新使用 WMPF 注册,才能免维护设备凭证。

具体使用可以参考示例代码

1. 版本要求

  • WMPF:本能力需安卓 WMPF >= 1.2.0 版本支持(如果是 2023/08/19 之前下载的 wmpf-cli,需要重新下载更新下)。
  • VOIP 通话插件:需插件 >= 2.3.0 支持。

2. 注册设备

使用 registerMiniProgramDevice 进行设备注册。使用 getMiniProgramDeviceInfo 进行注册信息查询。

3. 设备凭证预拉取

当使用 WMPF 注册设备后,框架会按需自行获取设备凭证,无需开发者介入。为了优化设备凭证的获取耗时,开发者可以在可能用到设备凭证前,调用prefetchDeviceToken接口提前进行预拉取,在有效期内(目前 1 小时)框架可以直接从缓存获得。

例如,在发起音视频通话时,框架会获取 deviceToken。建议开发者在用户发起通话的前置页面(例如:联系人页面等)进行设备凭证预拉取。

4. 框架获取设备凭证的场景

目前框架会在下列时机获取设备凭证,建议提前进行预拉取:

  • 微信小程序音视频通话,使用 VOIP 通话插件,调用 initByCaller 发起通话时。

5. 从设备认证 SDK 切换到使用 WMPF 注册设备

如果之前使用设备认证 SDK,想要切换成 WMPF 方式注册,可以注意以下事项:

  1. 同一台设备不要混用设备认证 SDK 和 WMPF 注册设备
  2. APP 中可以删除 voipsdk-1.x-release.aar 和 safeguard-release.aar。
  3. 对于之前注册过的设备,需要重新调用一次 registerMiniProgramDevice 接口以刷新设备密钥。
  4. 开发者不再需要获取和传入 deviceToken/callerTicket,使用插件时不能传 voipToken 参数。
  5. 如果之前开发者做了提前获取 deviceToken/callerTicket 的逻辑,可以替换为提前调用 prefetchDeviceToken