npm package: @ray-js/wechat-bluetooth-sdk
Description: 微信小程序面向开发者封装的业务蓝牙包，目前支持 蓝牙 LE 、 蓝牙 LE + Beacon 、Tuya Beacon 设备的本地连接控制，及蓝牙通道入网
Latest version: 3.0.0

本文介绍了涂鸦 IoT 微信小程序蓝牙 SDK，该 SDK 提供了一些蓝牙功能的封装，方便小程序开发者更加快速地连接涂鸦生态中的智能硬件产品。您可以基于该 SDK 快速地实现小程序蓝牙交互功能的开发，实现对智能蓝牙设备的连接、控制、固件升级等操作。

## 蓝牙 SDK

该 SDK 根据涂鸦蓝牙协议提供高度封装的 API，方便小程序开发者快速实现小程序蓝牙功能。

### 安装 SDK
使用 NPM 包管理器下载 SDK。

```shell
npm install @ray-js/wechat-bluetooth-sdk
```

### 使用示例

若您已安装小程序开发者工具，[点此打开微信官方代码片段](https://developers.weixin.qq.com/s/SEwmJGmv7juj)，即可查看完整示例，若按如下步骤操作，还可生成小程序二维码进行真机体验。

1. 修改 `project.config.json` 文件中的 `appid` 字段为您在涂鸦 IoT 平台授权过的小程序 `AppID`。
2. 使用 `npm install` 命令安装 NPM 包。
3. 在开发者工具中按如下路径构建 NPM 包：**工具** > **构建 npm**。
4. SDK 完整使用示例在代码片段的目录 `pages/home_center/common_panel/index.js` 下。

若您未安装小程序开发者工具，也可参考以下示例代码：

```js
import BleService from '@ray-js/wechat-bluetooth-sdk'

BleService.initParams({ access_token, uid });

//  小程序生命周期钩子
onLoad: async function () {
  // 抽象的蓝牙设备实例
  const instance = BleService.setNewInstance(device_id, home_id)
  // 蓝牙连接
  instance.connectBlue()
  // 监听蓝牙通信
  instance.onReceivePackage((parseReceiveData) => {
    const { type, status, dpState, deviceId } = parseReceiveData
    if (type === 'connect' && status === 'fail') {
      if (deviceId) {
        return { msg: '连接失败 或 连接后又断开' }
      } else {
        return { msg: '未发现当前蓝牙设备' }
      }
    } else if (type === 'connect' && status === 'connected') {
      // 连接成功
      wx.showToast({
        title: '蓝牙连接成功',
        icon: 'success',
        duration: 2000
      })
      // 如果连接成功后需要下发 DP
      instance.sendDp({ dpCode: 'code', dpValue: 123 })
    } else if (!(deviceId in parseReceiveData) && dpState) {
      // 一般为 DP 上报事件，可在此处处理数据 or 走业务逻辑
      this.handleData(dpState)
    }
  })
},

handleData(dpState) {
  // todo....
}
```

## 引入服务

```js
import BleService from '@ray-js/wechat-bluetooth-sdk'
```

## BleService

`BleService` 是一个实例对象，该实例维护了下文所提到的设备实例列表，以及创建设备实例、销毁设备实例等 API。

### 设置接口请求参数（initParams）

```js
BleService.initParams({ access_token, uid });
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | :--- | :--- |
	| access_token | String | 登录凭证 |
	| uid | String | 用户 ID |

- 返回值：无

在蓝牙连接认证流程中，需要通过 `云函数` 接口拉取部分蓝牙连接所需要的数据（具体数据可点此查看[蓝牙通信建立流程图](https://developer.tuya.com/cn/docs/iot/mini-program-integration-documents?id=Ka75d8cadotgh#title-11-%E9%80%9A%E4%BF%A1%E6%97%B6%E5%BA%8F%E5%9B%BE)的示意）。为方便开发者使用，SDK 已内部封装通信流程所涉及的接口请求，但由于 `用户 ID`、`access_token` 为云函数接口鉴权必需参数，因此需要调用方传入该参数以便 SDK 顺利发起请求。`用户 ID`、`access_token` 的获取请 [点此查看](https://developer.tuya.com/cn/docs/iot/user-manger?id=K9wiyx0eez314)。

### 创建蓝牙 LE 设备实例（setNewInstance）

```js
const instance = BleService.setNewInstance(device_id, home_id)
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| device_id | String | 蓝牙设备配网后生成的虚拟 ID |
	| home_id | Number | 蓝牙设备所在家庭 ID |

- 返回值：设备实例

通过调用该方法，将生成一个设备实例。每个蓝牙 LE 设备对应一个实例。该设备的连接状态等数据都将维护在该实例中。同时，设备连接、设备指令下发、OTA 升级等相关 API 也维护在该设备实例中。

涂鸦小程序家庭、设备相关云函数，请参考 [全屋管理](https://developer.tuya.com/cn/docs/iot/house?id=K9wj2qbmwnb64)。

:::info
若当前设备 ID 对应的实例已经被创建过，则会销毁原有实例，并创建新的实例返回。
:::

### 获取已创建的蓝牙 LE 实例（BleService.instance）

```js
const instance = BleService.instance[device_id]
if(!instance) reutrn false
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| device_id | String | 蓝牙设备配网后生成的虚拟 ID |

- 返回值：设备实例

也可通过该方式获取已生成的设备实例。`BleService.instance` 为 `BleService` 维护的设备实例列表。

### 创建 Tuya Beacon 设备实例（creatBeacon）

```js
const instance = BleService.creatBeacon(device_id, home_id)
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| device_id | String | 蓝牙设备配网后生成的虚拟 ID |
	| home_id | Number | 蓝牙设备所在家庭 ID |

- 返回值：设备实例

通过调用该方法，将生成一个 Tuya Beacon 设备实例，该实例具有的能力与蓝牙 LE 设备实例大同小异。但由两种通信协议间的差异所致，两种设备实例的 `API` ，其调用产生的内部逻辑稍有不同。

:::info
若当前设备 ID 对应的实例已经被创建过，则重复调用只会返回旧实例。
:::

### 获取已创建的 Tuya Beacon 实例（BleService.beaconInstance）

```js
const instance = BleService.beaconInstance[device_id]
if(!instance) reutrn false
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| device_id | String | 蓝牙设备配网后生成的虚拟 ID |

- 返回值：设备实例

也可通过该方式获取已生成的设备实例。`BleService.beaconInstance` 为 `BleService` 维护的 Tuya Beacon 设备实例列表。

### 销毁设备实例（destroyInstance）

```js
BleService.destroyInstance(device_id)
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| device_id | String | 蓝牙设备配网后生成的虚拟 ID |

- 返回值：无

## 设备实例

### 蓝牙连接（connectBlue）

```js
const res = await instance.connectBlue()
```

- 返回值：`Promise`

	该方法为异步函数，设备连接结果需要等待 Promise 对象处理完成。

- Promise 对象处理结果

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| success | Boolean | 蓝牙是否成功连接 |
	| errMsg | String | 蓝牙连接失败时的错误信息 |

- 差异化说明

  | 设备通信类型 | 说明 |
	| ---- | ---- |
	| 蓝牙 LE | 调用该 `API` 会发起本地蓝牙连接。蓝牙连接过程中的连接状态变化，会通过下文提到的回调事件进行回调，建议监听该事件回调，以便监控连接过程。 |
	| Tuya Beacon | 由于 Tuya Beacon 并无连接特性，因此调用该 `API` 仅是对设备发出一个 `ping` 请求，若设备在超时时间内（目前默认为 60s 超时，暂不支持修改）回复了心跳，则会回调 'success: true'。  |


### 主动断开设备连接（connectionBreak）

```js
instance.connectionBreak()
```

- 返回值：`Promise`

	该方法为异步函数，执行结果需要等待 Promise 对象处理完成。

- Promise 对象处理结果

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| success | Boolean | 蓝牙是否成功断开连接 |
	| errMsg | String | 蓝牙断开连接失败时的错误信息 |

- 差异化说明

  | 设备通信类型 | 说明 |
	| ---- | ---- |
	| 蓝牙 LE | 调用该 `API` 会断开本地蓝牙连接。后续可通过 `connectBlue` 重新发起连接。 |
	| Tuya Beacon | 由于 Tuya Beacon 并无连接特性，因此调用该 `API` 会固定回调一个设备离线消息，并屏蔽后续所有设备状态上报。若想重新收取设备消息，可调用 `connectBlue` 恢复。  |

### 发送 DP（sendDp）

```js
// 发送标准 DP
const res = instance.sendDp({ dpCode, dpValue })
// 发送自定义 DP（ v1.0.4 以上 ）
const res = instance.sendDp({ dpId, dpType, dpValue })
```
- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| dpValue | any | 要下发的数据 |
	| dpCode | string | dpcode，发送标准 DP 时，传入 dpCode 与 dpValue 即可 |
	| dpId | number | dpid，发送自定义 dp（ `v1.0.4` 以上 ）时，必传此项 |
	| dpType | string | dpValue 所对应的数据类型，发送自定义 dp（ `v1.0.4` 以上 ）时，必传此项 |

- 参数举例
	```js
	// 发送标准 dp，假设 DP 为: dpcode: 'welcome_words', dpType: 'string', dpId: 18
	instance.sendDp({ dpCode: 'welcome_words', dpValue: 'hello world' })
	// 发送自定义 dp，假设 DP 为：dpcode: 'switch_1', dpType: 'boolean', dpid: 101
	instance.sendDp({ dpId: 101, dpType: 'Boolean', dpValue: true })
	```
- 返回值：`Promise`

- Promise 对象处理结果

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| success | Boolean | 数据下发是否成功 |
	| errMsg | String | 蓝牙数据下发失败时的错误信息 |

	:::info
	处理结果为 `true` 仅代表数据单方面下发成功，不代表蓝牙设备正确接收到了数据并校验成功，因此，在遇到数据下发成功但设备无反应时，请先自查所下发数据是否有效。
	:::

### 注册蓝牙回调事件（onReceivePackage）

```js
instance.onReceivePackage(parseReceiveData => {
  const { type, status, dpState, deviceId } = parseReceiveData;

  if (type === 'connect' && status === 'fail') {
    // 连接失败 或 连接后又断开
  } else if (type === 'connect' && status === 'connected') {
    // 连接成功
    wx.showToast({
      title: '蓝牙连接成功',
      icon: 'success',
      duration: 2000
    })
  } else if (!(deviceId in parseReceiveData)) {
    // 一般为 DP 上报事件，可在此处处理数据 or 走业务逻辑
    // 上报的数据会被处理成 dpState 对象，dpState 对象下面有举例
    // todo...
  }
});
```

- 回调参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| type | string | 状态类型： <ul><li> connect：连接状态</li><li> otaStatus：OTA 升级状态 </li><li> send：发送状态  </li></ul> |
	| status | string | 状态值 |
	| deviceId | string | 蓝牙设备 ID，安卓下为 mac 地址 |
	| dpState | Object | 当前蓝牙设备的功能状态集 |
	| online | boolean | Tuya Beacon 设备是否在线 |

- 回调参数 `status` 数值说明

	| type | status 数值 | 说明 |
	| ---- | ---- | ---- |
	| connect | ready | SDK 准备进行蓝牙连接，进入此状态后，请勿再重复调用 connectBlue 进行连接，否则可能会导致连接失败 |
	| -- | connecting | 连接中 |
	| -- | connected | 蓝牙连接成功 |
	| -- | fail | 蓝牙连接失败 |
	| otaStatus | updating | OTA 中 |
	| -- | updated | OTA 成功 |
	| -- | fail | OTA 失败 |
	| -- | ready | 准备开始 OTA，此阶段将先进行设备状态校验（ `v1.1.0` 以上） |
	| -- | check | 向设备发送 OTA 请求，等待设备同意（ `v1.1.0` 以上） |
	| send | sending | 数据发送中 |
	| -- | sended | 数据发送完毕 |

- 回调参数 dpState 举例

	```js
	dpState: {
		[dpcode1]: true,
		[dpcode2]: 100
	}
	```

### 取消监听蓝牙回调事件（offReceivePackage）

```js
instance.offReceivePackage(cb);
```

**参数说明**

| 字段 | 数据类型 | 说明 |
| ---- | ---- | ---- |
| cb | Function | 注册监听时传入的回调函数 |

### 注册蓝牙错误事件（onError）

```js
instance.onError(cb);
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| cb | function | 回调函数 |

- 回调参数举例

	```js
	{
	errCode: 1000,
	errMsg: 'error'
	}
	```

	蓝牙交互时出现的异常情况都将在此处进行回调。

### 取消监听蓝牙回调事件（offError）

```js
instance.offError(cb);
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| cb | Function | 注册监听时传入的回调函数 |

### 获取设备本地状态（getBlueStatus）

```js
instance.getBlueStatus()
```

- 返回值：`Promise`

- Promise 对象处理结果

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| success | Boolean | 设备本地状态查询请求下发是否成功 |
	| errMsg | String | 设备本地状态查询请求下发失败时的错误信息 |

- 差异化说明

  | 设备通信类型 | 说明 |
	| ---- | ---- |
	| 蓝牙 LE | 正常可调用。 |
	| Tuya Beacon | Tuya Beacon 设备不支持使用该 `API`。  |

建议在蓝牙连接成功之后，调用该 API 查询设备本地最新状态，如：设备电量信息等。

设备上报的最新状态也将会通过上文提到的 `onReceivePackage` 进行回调。

:::info
此 API 原理不能简单等同为“调接口拿数据”。

它只是向设备发出一个本地状态上报请求，设备收到这个请求之后，是否要上报本地状态，上报哪些本地状态，由设备决定。但无论上报哪些状态，最终这些数据都将由 SDK 的 `onReceivePackage` 进行回调。
:::

### OTA 升级（sendOtaRequestOrder）

```js
instance.sendOtaRequestOrder(url, version);
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ----| ---- | ---- |
	| url | String | OTA 升级文件 URL 地址 |
	| version | String | OTA 升级文件版本号 |

OTA 升级过程复杂，涉及多个蓝牙指令下发，请务必通过 `onReceivePackage` 监听 OTA 升级过程的状态变化。

- 差异化说明

  | 设备通信类型 | 说明 |
	| ---- | ---- |
	| 蓝牙 LE | 正常可调用。 |
	| Tuya Beacon | Tuya Beacon 设备暂不支持 OTA。  |

### 设备解绑（unboundBlue）

```js
instance.unboundBlue(type);
```

- 参数说明

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| type | String | 共有两种解绑类型，分别对应不同的 type 数值： <ul><li> unbound（默认值）：与设备正常解绑。 </li><li> reset：设备重置，该类型除了解绑外，还会删除设备本地历史数据。 </li></ul> |

- 返回值：`Promise`

- Promise 对象处理结果

	| 字段 | 数据类型 | 说明 |
	| ---- | ---- | ---- |
	| success | Boolean | 蓝牙解绑命令是否下发成功 |
	| errMsg | String | 蓝牙解绑失败时的错误信息 |

- 差异化说明

  | 设备通信类型 | 说明 |
	| ---- | ---- |
	| 蓝牙 LE | 正常可调用。 |
	| Tuya Beacon | Tuya Beacon 设备场景下，该 `API` 无参数。  |

## 其他功能

### Raw 类型数据加解密（encryptRawData、decryptRawData）

```js
// 数据下发前，对 Raw 类型数据加密
BleService.encryptRawData(raw_data)
// 数据上报后，对回调数据进行解密
BleService.decryptRawData(value)
```

**参数说明**

| 字段 | 数据类型 | 说明 |
| ---- | ---- | ---- |
| raw_data | String | Raw 类型数据 |
| value | String | 通过 `onReceivePackage` 回调监听到的 Raw 类型数据 |

:::info
Raw 表示透传类型数据。对某些设备来说，Raw 类型数据可能会包含密码等较为敏感的信息，在数据下发及上报云端时，应对其进行安全加密处理。因此，SDK 要求 Raw 类型数据应加密下发，同时 SDK 自身也会对设备上报的 Raw 类型数据进行加密后返回。
:::

## 错误码

| errCode | 说明 |
| ---- | ---- |
| 1001 | 蓝牙连接过程中，调用微信 API 失败 |
| 1002 | 未授予地理位置权限（安卓特有） |
| 1003 | 未搜索到蓝牙设备 |
| 1004 | 设备信息获取失败 |
| 1005 | 获取蓝牙 services 列表失败 |
| 1006 | 获取 characteristics 列表失败 |
| 1007 | write characteristic uuid 获取失败 |
| 1008 | 当前设备具有 SDK 不支持的蓝牙特性 |
| 1009 | 获取云端实时时间失败 |
| 1010 | 蓝牙连接过程中，下发命令失败 |
| 1011 | 云端随机数获取失败 |
| 1012 | 初始化蓝牙适配器失败 |
| 1013 | 未授予蓝牙权限 |
| 1014 | 蓝牙 LE + Beacon 设备连接时，获取 BeaconKey 失败  |
| 2001 | 蓝牙下发 DP 数据时，下发命令失败 |
| 2003 | 蓝牙 DP 上报云端时，调用接口失败 |
| 2004 | 设备解绑、重置时，下发命令失败 |
| 2005 | 获取设备本地状态信息时，下发命令失败 |
| 3001 | 发送 OTA 升级请求时，下发命令失败 |
| 3002 | 蓝牙拒绝 OTA 升级 |
| 3003 | 不支持 OTA 升级的蓝牙协议版本 |
| 3004 | 不支持的 OTA 升级类型 |
| 3005 | 发送 OTA 升级文件信息时，下发命令失败 |
| 3006 | 发送 OTA 升级文件偏移请求时，下发命令失败 |
| 3007 | 分包发送 OTA 升级数据包时，下发命令失败 |
| 3008 | 发送 OTA 升级停止指令时，下发命令失败 |
| 4001 | 从机服务创建失败，此失败会导致无法本地控制 Tuya Beacon 设备 |
| 5001 | 配网扫描开启失败，原因：当前已有扫描进程 |
| 5002 | 本次配网流程启动失败，原因：未设置homeId |
| 5003 | 本次配网流程启动失败，原因：无设备成功进入配网队列 |
| 5004 | 本次配网流程启动失败，原因：参数类型有误 |
| 5005 | 配网失败：未设置wifi信息，双模设备无法配网 |
| 5006 | 配网失败：配网密钥获取失败 |
| 5007 | 配网失败：设备已被绑定，无法配网 |
| 5008 | 配网失败：蓝牙认证失败 |
| 5009 | 配网失败：设备数据返回有误 |
| 5010 | 配网失败：不支持的协议版本 |
| 5011 | 配网失败：设备云端注册失败 |
| 5012 | 配网失败：设备云端激活失败 |
| 5013 | 配网失败：蓝牙能力上报失败 |
| 5014 | 配网失败：beaconKey获取失败 |
| 5015 | 配网失败：获取加密公钥失败 |
| 5016 | 配网失败：获取配网token失败 |
| 5017 | 配网失败：轮询超时，配网失败 |
| 5018 | 配网失败：多模设备配网失败 |
| 5019 | 配网失败：配网超时 |
| 5020 | 配网失败：多模设备获取配网配置失败 |
| 5021 | 配网失败：Wi-Fi 密码错误，设备无法连接网络 |
| 5022 | 配网失败：beaconKey异常 |
| 5023 | 配网失败：beaconKey发送失败 |

:::info
涉及微信原生 API 的错误码，都将以 `wxErrCode` 与 `wxErrMsg` 这两个字段透传微信侧提供的错误信息，方便错误排查。
:::

## 版本更新

| 版本号 | 更新说明 |
| ---- | ---- |
| 1.2.0 | 支持 ` Tuya Beacon ` 设备进行本地蓝牙控制 |
| 1.1.0 | 支持 ` 蓝牙 LE + Beacon ` 设备通过蓝牙 LE 通道进行本地蓝牙控制 |
| 1.0.4 | 支持设备自定义 DP 下发 |
| 1.0.1 | 支持 蓝牙 LE 3/.* 与 4/.* 协议版本的设备的本地控制能力 |