# 腾讯小微蓝牙设备协议

## 概要
蓝牙设备（如蓝牙耳机、蓝牙音箱）可以通过蓝牙与“腾讯小微APP”或“QQ音乐App”建立连接，接入小微语音助手的服务。
接入小微语音助手后，设备即可具有语音对话能力，可以通过说话来使用音乐点播、故事播放、新闻收听、天气和百科查询等服务。

一些接入基本情况如下：
- 支持蓝牙协议版本4.2及以上。
- iOS系统支持10.0及以上，连接方式支持IAP，BLE。
- Android系统支持6.0及以上，连接方式支持SPP。
- 录音采样参数：16000采样率，单声道，16bit采样位深。
- 音频编码方式支持Opus，SBC，mSBC。
- 支持OTA升级（可选）。

## 发现与连接

### IAP

仅iOS支持。

protocol string: `com.tencent.xiaowei`

### SPP

仅Android支持。

UUID: `85dbf2f9-73e3-43f5-a129-971b91c72f1e`

### BLE

仅iOS支持。

设备通过Advertising Data和Scan Response来向App传递设备的信息。

优先通过Advertising Data来传递，如数据包大小超过限制，可放到Scan Response中。

广播数据中应包含：

Service UUID：`0709`


MANUFACTURER SPECIFIC DATA：

| Offset | Length | Type       | Description                                   |
| ------ | ------ | ---------- | --------------------------------------------- |
| 0      | 2      | uint16_t   | compnay_id, 由厂商自行在蓝牙组织申请              |
| 2      | 4      | uint32_t   | product_id,由厂商在腾讯小微硬件管理平台申请        |
| 6      | 6      | uint8_t[]  | 蓝牙MAC地址                                    |
| 12     | 8      | uint8_t[]  | 特征值，连接App过程中由App分配，没有时用0填充       |

建立BLE连接后，设备向App发送数据的通道为 `dfd4416e-e40c-47f7-8248-eb8be3dc47f9`。

设备接收来自App的数据的通道为 `9884d812-61fe-4a24-94d3-b2c11a851fac`。

设备需要在与App建立连接后停止广播。


### 连接流程

1. App通过BLE，SPP，IAP等方式查找可用设备。 
2. 发现设备后，App发送103命令给设备来获取product_id，mac地址以及特征值。
如果App已经通过BLE的广播包获取到了相应信息，App会跳过该步骤。
3. App检查特征值，判断是否可以自动连接。
4. 如不能自动连接，App弹窗询问用户是否连接设备。用户确认后继续流程。
5. App发送105命令，给设备分配特征值，设备设备回复配置参数。
6. App发送106命令，给设备发送phone id，设备根据校验方式回复校验信息。
7. 连接完成，App播放已连接的提示。
8. 连接完成后，App会定时发送心跳（112_heartBeat）给设备。如心跳失败，App将断开与耳机的连接。

过程如下图所示

   ![蓝牙_发现](https://res8.wxqcloud.qq.com.cn/wxdoc/76527897-ef53-40ca-8bf3-052d0b033886.png)

## 录音流程

### 长按

1. 用户按下按键时，设备发送101命令，收到回复后开始录音。
2. 设备持续通过255命令发送编码后的音频数据。如多次没有收到App的回复时，设备要停止录音并主动发送102命令尝试告知App停止录音。
3. 用户松开按键时，设备发送108命令，在收到回复后需要继续录音和发送音频数据。
4. 设备收到App发来的102命令时，结束录音。

过程如下图所示

![蓝牙_录音_长按](https://res8.wxqcloud.qq.com.cn/wxdoc/2c763e13-acfb-42f7-af06-48f5bb06dc81.png)

### 短按

1. 用户触发录音，设备发送101命令，收到回复后开始录音。
2. 设备持续通过255命令发送编码后的音频数据。如多次没有收到App的回复时，设备要停止录音并主动发送102命令尝试告知App停止录音。
3. 设备收到App发来的102时，结束录音。

过程如下图所示

![蓝牙_录音_短按](https://res8.wxqcloud.qq.com.cn/wxdoc/b60bacb7-2945-4782-8c16-03c5da5c70f6.png)

### 对话控制

在iOS平台上，如果有第三方的App正在播放音乐，此时小微App需要通过设备来发送AVRCP指令来控制第三方App的音乐播放。

以长按为例，过程如下图所示

![蓝牙_录音_音乐控制](https://res8.wxqcloud.qq.com.cn/wxdoc/6f17f972-9dea-4900-982c-947bb949b2d2.png)



## 采样和编码

### 采样

录音采样参数：16000采样率，单声道，16bit采样位深。

### Opus

iOS和Android均支持。

采用固定码率，压缩后的每一帧大小为40个字节。

支持16倍和8倍的压缩，对应的frame_duration分别为20ms, 10ms。

| 码率（压缩前） | 压缩比例 | 包大小 | frame_duration | 码率（压缩后） |
| -------------- | -------- | ------ | -------------- | -------------- |
| 256kbps        | 16       | 40Byte | 20ms           | 16kbps         |
| 256kbps        | 8        | 40Byte | 10ms           | 32kbps         |

### SBC

iOS和Android均支持。

SBC参数：
```
sbc->frequency = SBC_FREQ_16000;
sbc->blocks = SBC_BLK_16;
sbc->subbands = SBC_SB_8;
sbc->mode = SBC_MODE_MONO;
sbc->allocation = SBC_AM_LOUDNESS;
sbc->bitpool = 12; // 256 bytes input 32 bytes output
sbc->endian = SBC_LE;
```
mSBC参数：
```
​sbc->frequency = SBC_FREQ_16000;
​sbc->blocks = MSBC_BLOCKS;
​sbc->subbands = SBC_SB_8;
​sbc->mode = SBC_MODE_MONO;
​sbc->allocation = SBC_AM_LOUDNESS;
​sbc->bitpool = 26; // 240 bytes input 57 bytes output
​sbc->endian = SBC_LE;
```

## 指令协议

App及设备均可主动发送request指令。

App或设备收到request指令后，回复相应的response指令。

如超过0.5秒没有收到相应的response，耳机会重新发送command，最多重试2次

命令处理成功时，response指令的code为0，其他错误码参考Response Code。

数据序列化时采用小端字节序。


### 包结构

request：

| Offset | Length | Type       | Description                                          |
| ------ | ------ | ---------- | ---------------------------------------------------- |
| 0      | 1      | uint8_t    | command_id                                           |
| 1      | 1      | uint8_t    | command_type，1: request，2: response                 |
| 2      | 1      | uint8_t    | sequence number, 0~255递增循环，response取request值   |
| 3      | 2      | uint16_t   | payload长度，无数据时为0                                |
| 5      | N      | uint8_t[]  | payload，指令相关数据, 无数据时为空                      |

response：
| Offset | Length | Type       | Description                                          |
| ------ | ------ | ---------- | ---------------------------------------------------- |
| 0      | 1      | uint8_t    | command_id                                           |
| 1      | 1      | uint8_t    | command_type，1: request，2: response                 |
| 2      | 1      | uint8_t    | sequence number, 0~255递增循环，response取request值     |
| 3      | 1      | uint8_t    | command result, 0: success                           |
| 4      | 2      | uint16_t   | payload长度，无数据时为0                                |
| 6      | N      | uint8_t[]  | payload，指令相关数据, 无数据时为空            

其中payload数据由各指令自己定义。

### 指令列表

#### 101_wakeUp

用户触发录音，发送该命令给App，并开始录音。

request payload: NULL

response payload: NULL


#### 102_silence

App检测到静音时，发送该命令给设备，设备结束录音。

或设备在录音时多次没有收到App回复时，发送此命令结束录音。

request payload: NULL

respone payload: NULL


#### 103_getInfo

建立连接过程中，由App发送给设备，设备返回product_id以及MAC地址。

request payload：NULL

response payload：

| Offset | Length | Type       | Description                                   |
| ------ | ------ | ---------- | --------------------------------------------- |
| 0      | 4      | uint32_t   | product_id                                    |
| 4      | 6      | uint8_t[]  | Bluetooth mac address                         |
| 10     | 8      | uint8_t[]  | 特征值，连接App过程中由App分配，没有时用0填充       |
| 18     | 1      | uint8_t[]  | connect_state, 最近一次或当前连接的情况:
0 表示未连接其他App或者语音助手。
1 表示已连接QQ音乐App。
2 表示已连接小微App。
3 表示已连接其他语音助手（如有）。 |



#### 105_getConfig

建立连接过程中，由App发送给设备，设备返回参数配置信息。

request payload: 

| Offset | Length | Type       | Description                                                  |
| -------| ------ | ---------- | ------------------------------------------------------------ |
| 0      | 1      | uint8_t    | App期望码率，单位KBps，目前为32                                  |
| 1      | 1      | uint8_t    | 手机操作系统. 1:iOS   2:Android                                |
| 2      | 8      | uint8_t[]  | 特征值，8字节，其中包含了App的信息。
参考代码:`xw_headphone_fvalue_get_host_app` | 
response payload: 

| Offset | Length | Type       | Description                                                  |
| ------ | ------ | ---------- | ------------------------------------------------------------ |
| 0      | 1      | uint8_t    | 连接类型，1:BLE, 2:SPP, 3:IAP                                |
| 1      | 1      | uint8_t    | 编码类型，1:Opus, 2: SBC,   3: mSBC                          |
| 2      | 1      | uint8_t    | 压缩率，16， 8，编码类型为Opus时适用。                       |
| 3      | 1      | uint8_t    | 采样率，1:16000                                              |
| 4      | 1      | uint8_t    | 录音声道，1:单声道                                           |
| 5      | 1      | uint8_t    | 位深，1:16bit                                                |
| 6      | 1      | uint8_t    | 录音方式，1:长按，2:短按                                     |
| 7      | 4      | uint8_t[]  | 固件版本号：1.2.3.4对应的数据应为0x01,0x02,0x03,0x04         |
| 11     | 1      | uint8_t    | 设备型号名称长度                                             |
| 12     | N      | uint8_t[]  | 设备型号名称，UTF8编码                                       |
| 12+N   | 1      | uint8_t    | 设备支持的小微协议版本号，当前为2。
参见附录-App对小微协议版本支持情况。 |
| 13+N   | M      | uint8_t[]  | extra config                                                 |

extra config中包含一到多个配置项。目前支持的配置项有：
- 功能键设备：默认（降噪）/小微助手
- 设备sku信息，用于与厂商在[硬件管理平台](https://xiaowei.weixin.qq.com/hardware)配置的信息关联。
- 设备自定义名称
- 设备序列号
- 更多详细信息参考代码`xw_headphone_extra_config_item`

#### 106_getSig

建立连接过程中，由App发送phoneId给设备，设备返回校验信息。

request payload:

| Offset | Length | Type       | Description               |
| ------ | ------ | ---------- | ------------------------- |
| 0      | N      | uint8_t[]  | phoneId，用于生成校验参数    |

response payload:

| Offset | Length | Type       | Description         |
| ------ | ------ | ---------- | ------------------- |
| 0      | N      | uint8_t[]  | sig，生成的校验参数    |

校验未启用时，sig返回长度不为0的任意值。

#### 107_stopPlay

由设备发送给App，App收到命令后会停止正在播放对话结果，或者暂停正在播放的内容资源（音乐、故事、新闻等）。

request payload: NULL

respone payload: NULL


#### 108_requestStopRceord

长按录音流程中，用户松开按键时，设备需要发送此命令。

request payload: NULL

respone payload: NULL

#### 109_setKeyFunction

设置按键功能，如设备不支持，可不实现。

request payload:

| Offset | Length | Type    | Description                    |
| ------ | ------ | ------- | ------------------------------ |
| 0      | 1      | uint8_t | 1:设备默认功能, 2:小微语音助手     |

response payload: NULL

#### 110_clearFValue

清空设备的特征值。

request payload: 

| Offset | Length | Type    | Description      |
| ------ | ------ | ------- | ---------------- |
| 0      | 1      | uint8_t | 1:清空, 0:不清空  |

response payload: NULL

#### 111_playControl
播放控制及对话结束通知指令，App通过该指令告诉设备，TTS播放结束，以及需要发送的AVRCP指令（pause、resume）。

在Android上，TTS播放完成后会发送该命令告知设备TTS播放结束。

在iOS上，开始录音时，如有第三方App在播放音乐，App会发送该命令告知设备发送AVRCP Pause指令 。对话结束时，发送此命令告知设备TTS播放完成以及是否需要发送AVRCP Resume指令。

request payload:

| Offset | Length | Type	 | Description |
| ------ | ------ | ------- | ----------- |
| 0	   | 1      | uint8_t | value: 
第0个bit（最低位）为1表示是TTS End。
第1个bit为1表示需要设备发送AVRCP Pause事件。
 第2个bit为1表示需要设备发送AVRCP Resume事件。 |

response payload: NULL

#### 112_heartBeat
App和设备均可主动发送该指令，用于探测连接是否正常。

App会定期发送心跳给设备，用于维持App在后台时不被系统挂起，并保持连接可用。

request payload: NULL

response payload: NULL

#### 115_customSkill

**1.6.0及以上版本支持。**

用户query了耳机的自定义技能时，App通过该命令将技能的意图和槽位信息转发给耳机。

request payload:

| Offset | Length | Type	 | Description |
| ------ | ------ | ------- | ----------- |
| 0	   | 1      | uint8_t | skill name length |
| 1 | N | uint8_t [] | skill name |
| N + 1	   | 1      | uint8_t | intent name length |
| N + 2 | M | uint8_t [] | intent name |
| M + N + 2 | 1 | uint8_t | slot_count |
| slot info ||| | 
| slot info ||| |
| ... ||| |

每一个slot info包含了槽位名称和槽位值
|Offset | Length | Type	| Description |
|------ | ------ | ----- | ------- |
| 0	| 1 | uint8_t | slot name length |
| 1	| N | uint8_t [] | slot name |
| N+1 | 1 | uint8_t | slot value length |
| N+2 | M | uint8_t [] | slot value |


response payload: 

NULL（如果处理成功）

出错则参考错误码处理回包。

#### 255_voiceData

设备发送语音数据给App。

request payload:

| Offset | Length | Type       | Description      |
| ------ | ------ | ---------- | ---------------- |
| 0      | N      | uint8_t[]  | 压缩后的语音数据    |

response payload: NULL

### 其他

#### extra_config
extra config用于在105命令中返回更多的配置信息。

extra config的数据由0-N个config_item拼接而成。

每个config_item的第一个byte为config_key。
根据config_key的不同，config_item接下来数据格式如下：


config_key = 200时，config_item长度为2+N，N为对应数据的长度
| Offset | Length | Type | Description |
|------ | ------ | ---- | --------- |
| 0 | 1 | uint8_t | config_key |
| 1 | 1 | uint8_t | length |
| 2 | N	| uint8_t[] | data |
相关代码请参考:

`xw_headphone_extra_config_key `

`xw_headphone_extra_config_item_list_pack`

`xw_headphone_extra_config_item_list_unpack`


## 打开App

厂商自有App中可以跳转到小微App。

当跳转到小微App时，小微App已连接设备，会打开设备的设置页面。

当手机上没有安装小微App时，需要打开下载页面，提示用户下载安装。

### 判断是否安装

iOS： 通过url shceme `xiaoweiapp://`，使用系统接口`-[UIApplication canOpenURL:]`来判断是否安装。

Android：通过url shceme `xiaoweiapp://`，使用系统接口判断是否安装。

示例代码：
```
public boolean checkXwappInstalled() {
​    Intent action = new Intent(Intent.ACTION_VIEW);
​    action.setData(Uri.parse("xiaoweiapp://"));
​    List list = getPackageManager().queryIntentActivities(action, 
        PackageManager.GET_RESOLVED_FILTER);
​    return list != null && list.size() > 0;
}
```

### 下载页面

iOS：https://apps.apple.com/cn/app/tencent-xiaowei/id1454208784?mt=8

Android：https://a.app.qq.com/o/simple.jsp?pkgname=com.tencent.xw

### 跳转链接

iOS & Android：`xiaoweiapp://headphone/settings`


## 附录

### App对小微协议版本支持情况

| 协议版本 | iOS  | Android |
| ------- | ---- | ------- |
| 2       | 1.2.0  | 1.2.0 |
| 3       | 1.2.2  | 1.2.2 |
| 4       | 1.4.0  | 1.4.0 |
| 5       | 1.6.0  | 1.6.0 |

### Response Code

| Code | Description                                                  |
| ---- | ------------------------------------------------------------ |
| 0    | No error                                                     |
| 1    | Invalid command code                                         |
| 2    | Parameter length out of range                                |
| 3    | Parameter length too short                                   |
| 4    | Command handling failed                                      |
| 5    | Waiting response time-out                                    |
| 6    | The data transfer has already been started                   |
| 7    | The data   transfer length doesn’t match the actually received data length |
| 41   | Wake up failed,   invalid sign                               |
| 60   | 设备不支持此功能                                                |

### 代码下载
协议参考代码[下载](https://xwwebimg-75050.gzc.vod.tencent-cloud.com/1608607047178/xiaowei_bluetooth.zip)
