蓝牙连接与设备管理
本文档介绍 PiHome btsmart 应用中蓝牙连接与设备管理能力:包括 JL_BluetoothManager 连接管理器、RCSPController 协议控制器、RCSP 命令/回调体系,以及设备搜索、设备信息查询等核心工作流。所有内容均基于仓库内真实源码(SDK Demo 与测试代码)整理。
Purpose and Scope
本页面覆盖蓝牙连接的端到端机制:从应用层发起操作(搜索设备、查询信息),经过 RCSPController / JL_BluetoothManager 两层 SDK 封装,到 RCSP 命令的构建、发送、响应回调与错误处理,以及连接事件的上报。
本页面不包含的内容(属于同级页面):
- 具体设备功能(闹钟 Alarm、EQ 音效、FM 收音、助听、灯光控制等)的协议细节——它们复用本文档描述的命令通道,但各自的参数/响应模型属于功能页面。
- TWS 双耳同步、OTA 固件升级等专项流程。
- Android 系统蓝牙配对(Bonding)与系统设置层面的内容。
Overview
背景与定位
仓库 code/PiHome_V1.13.0_SDK_V4.2.0/ 下的 btsmart 模块是一款杰理(Jieli)蓝牙音频设备的 Android 控制应用。其蓝牙能力构建在 com.jieli.bluetooth SDK 之上,该 SDK 提供两个核心入口类:
JL_BluetoothManager(com.jieli.bluetooth.impl):蓝牙连接管理核心,负责与设备建立/维护连接、发送 RCSP 命令。通过JL_BluetoothManager.getInstance(context)获取单例。RCSPController(com.jieli.bluetooth.impl.rcsp):面向业务的高层控制器,封装了"搜索设备"、"请求设备信息"等常用动作,并以OnRcspActionCallback/BTRcspEventCallback提供简洁回调。通过RCSPController.getInstance()获取单例。
关键概念
| 概念 | 说明 |
|---|---|
| RCSP 命令 | 应用与设备之间传输的指令,以 CommandBase 为基类,携带参数(Param)与响应(Response) |
| 命令类型标志 | CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE / FLAG_NO_PARAMETER_AND_RESPONSE 等,用于判断命令是否带响应数据 |
| 状态码 | StateCode.STATUS_SUCCESS 表示设备回复成功;其他状态需按错误处理 |
| 错误码 | ErrorCode.SUB_ERR_RESPONSE_BAD_STATUS(设备回复异常状态)、ErrorCode.SUB_ERR_DATA_FORMAT(响应数据格式错误) |
| 正在使用的设备 | RCSPController.getUsingDevice() 返回当前正在使用的设备;JL_BluetoothManager.getConnectedDevice() 返回已连接设备 |
| 设备搜索参数 | SearchDevParam:包含操作类型 op(Constants.RING_OP_OPEN 开启响铃 / 其他关闭)、超时秒数 timeoutSec、播放方 player(0=App 播放,1=设备播放) |
为什么设计为两层 API
从 BtRcspControlDemo.java 可以看出,同一个"获取设备信息"需求提供了两种调用方式:
- 高层方式:
controller.requestDeviceInfo(device, mask, callback),内部自动完成命令构建、发送与解析,业务方只关心成功/失败。 - 底层方式:
manager.sendCommandAsync(device, cmd, timeoutMs, callback),业务方自己用CommandBuilder构建命令、检查cmd.getStatus()、解析响应类型。
这种分层的设计意图是:常规业务走高层 API 减少样板代码;需要精细控制(自定义命令、手动状态校验、超时控制)时走底层 API 获得完整控制权。两种方式共享同一套命令/回调体系,便于在两者之间平滑迁移。
Architecture
下图展示了蓝牙连接与设备管理能力的整体架构:
flowchart TD
subgraph sg_App["应用层 btsmart"]
UI["Demo / UI 页面"]
EventCB["BTRcspEventCallback<br/>事件监听器"]
end
subgraph sg_SDK["蓝牙 SDK com.jieli.bluetooth"]
RCSP["RCSPController<br/>高层动作封装"]
Mgr["JL_BluetoothManager<br/>连接管理核心"]
Builder["CommandBuilder<br/>命令构建工具"]
Cmd["CommandBase<br/>命令模型"]
RcspCB["RcspCommandCallback<br/>命令回调"]
ActionCB["OnRcspActionCallback<br/>动作回调"]
Beans["Bean 模型<br/>DeviceInfo / SearchDevParam"]
Codes["StateCode / ErrorCode<br/>状态与错误码"]
end
subgraph sg_System["系统层"]
BTStack["Android 蓝牙协议栈"]
end
UI -->|"searchDev / requestDeviceInfo"| RCSP
UI -->|"getInstance(context)"| Mgr
RCSP -->|"底层命令通道"| Mgr
Mgr -->|"构建命令"| Builder
Builder -->|"生成"| Cmd
Mgr -->|"sendRcspCommand / sendCommandAsync"| Cmd
Cmd -->|"响应回调"| RcspCB
Mgr -->|"连接管理"| BTStack
RCSP -->|"动作结果"| ActionCB
RCSP -->|"连接事件"| EventCB
Mgr --> Codes
RCSP --> Beans
架构说明:
- 入口层:业务代码(Demo 或 UI)同时持有
RCSPController与JL_BluetoothManager两个单例。前者提供面向场景的封装,后者提供底层连接与命令能力。 - 命令通道:所有交互最终都归结为"构建
CommandBase→ 发送 → 等待设备响应"。CommandBuilder是命令工厂,RcspCommandCallback是响应回调契约。 - 回调双轨:
OnRcspActionCallback(一次性动作结果)与BTRcspEventCallback(持续事件流,如设备主动上报的搜索状态)相互独立,分别对应"请求-应答"与"事件订阅"两种通信模式。 - 状态/错误码:
StateCode与ErrorCode贯穿命令响应解析与异常处理,是错误处理规范化的基础。
核心组件分析
JL_BluetoothManager — 连接管理核心
JL_BluetoothManager 是 SDK 的蓝牙连接枢纽,负责设备连接的建立与维护,并提供命令发送的低层通道。源码中的使用模式(来自 BtRcspControlDemo.java 与 SearchDeviceDemo.java)表明其关键方法如下:
| 方法 | 作用 |
|---|---|
JL_BluetoothManager.getInstance(Context) | 获取全局单例(上下文持有者,应用生命周期内唯一) |
getConnectedDevice() | 返回当前已连接的 BluetoothDevice;发送命令前用于获取目标设备 |
sendRcspCommand(device, CommandBase, RcspCommandCallback) | 同步语义的 RCSP 命令发送,响应经 onCommandResponse 回调 |
sendCommandAsync(device, CommandBase, timeoutMs, RcspCommandCallback) | 异步命令发送,可显式指定超时时间 |
getBluetoothOption() | 返回蓝牙选项对象,getTimeoutMs() 提供默认超时配置 |
设计意图:单例模式保证连接状态(已连接设备、命令会话)在应用全局唯一,避免多实例导致连接混乱;getConnectedDevice() 与 RCSPController.getUsingDevice() 分别从"连接层"与"使用层"视角暴露当前设备,二者在双设备场景(如 TWS 左右耳)下含义不同。
RCSPController — 高层动作封装
RCSPController 把常用操作封装成"参数 → 动作 → 回调"三段式 API,业务方无需关心命令字节结构。其公开能力(依据 Demo 中的调用点)包括:
| 方法 | 作用 |
|---|---|
RCSPController.getInstance() | 获取全局单例 |
getUsingDevice() | 返回当前正在使用的设备 |
searchDev(device, op, timeoutSec, way, player, callback) | 发起设备查找(响铃):way 0=全部、1=左耳、2=右耳;player 0=App 播放、1=设备播放 |
stopSearchDevice(device, callback) | 停止设备查找 |
syncSearchDeviceStatus(device, callback) | 同步查询设备当前的查找状态 |
requestDeviceInfo(device, mask, callback) | 请求设备信息,mask 位掩码控制属性范围(0xffffffff 表示全部) |
getDeviceInfo() | 直接读取 SDK 缓存的设备信息(免一次往返) |
addBTRcspEventCallback(BTRcspEventCallback) | 注册全局蓝牙事件监听器 |
设计意图:searchDev 的 timeoutSec(60 秒)与 player 参数把"查找设备"这一动作的协议细节(响铃开关、超时、播放方)收敛为方法签名;getDeviceInfo() 的缓存读取则是"读写分离"思想的体现——UI 需要反复展示设备信息时不必每次都走蓝牙请求。
命令体系:CommandBase / CommandBuilder / RcspCommandCallback
所有设备交互都基于命令对象,其生命周期在 SearchDeviceDemo.java 中完整呈现:
- 构建:
CommandBuilder.buildSearchDevStatusCmd()等工厂方法生成CommandBase子类(如SearchDevCmd、GetTargetInfoCmd)。 - 发送:
manager.sendRcspCommand(device, cmd, callback)或sendCommandAsync(device, cmd, timeoutMs, callback)。 - 响应:
RcspCommandCallback.onCommandResponse(device, cmd)收到设备回复;onErrCode(device, BaseError)收到错误。 - 校验:先检查
cmd.getStatus() != StateCode.STATUS_SUCCESS;再根据命令类型标志(FLAG_HAVE_PARAMETER_AND_RESPONSE/FLAG_NO_PARAMETER_AND_RESPONSE)判断是否有响应体,并做空值检查后强转具体响应类型。 - 消费:将
CommandBase强转为具体命令(如SearchDevCmd),再取其getResponse()获取业务数据。
为什么命令要有类型标志:RCSP 协议中命令分为"带参数带响应 / 带参数无响应 / 无参数带响应 / 无参数无响应"四类,CommandBase 的类型标志让回调解析逻辑可以在不感知具体命令的情况下,统一判断"这个命令是否应该携带 Response 数据",从而在响应缺失时立即报 SUB_ERR_DATA_FORMAT 而不是空指针崩溃。
事件订阅:BTRcspEventCallback
与"请求-应答"式回调不同,BTRcspEventCallback 是事件订阅机制。设备在查找过程中会主动上报状态,应用通过 controller.addBTRcspEventCallback(...) 注册监听,在 onSearchDevice(device, SearchDevParam) 中接收事件(参见 SearchDeviceDemo.java)。
事件参数 SearchDevParam 通过 getOp() 区分响铃开启/关闭:op == Constants.RING_OP_OPEN 表示设备开始查找(携带 timeoutSec 超时秒数与 player 播放方),否则表示查找已结束。这种设计让 UI 可以被动感知设备侧状态变化(例如用户在设备上手动停止查找),而无需轮询。
核心流程
流程一:设备查找(搜索响铃)
sequenceDiagram
participant App as 应用层 Demo
participant RCSP as RCSPController
participant Mgr as JL_BluetoothManager
participant Dev as 蓝牙设备
participant CB as 回调接口
App->>RCSP: searchDev(device, RING_OP_OPEN, 60, way, RING_PLAYER_APP, callback)
RCSP->>Mgr: 构建 SearchDevCmd 并下发
Mgr->>Dev: 蓝牙通道发送 RCSP 命令
Dev-->>Mgr: 命令响应
Mgr-->>CB: onCommandResponse(device, cmd)
CB-->>App: 校验 STATUS_SUCCESS 后触发 onSuccess(device, Boolean)
Note over App,CB: 查找期间设备主动上报状态
Dev-->>RCSP: onSearchDevice(device, SearchDevParam)
RCSP-->>App: 事件回调(op / timeoutSec / player)
App->>RCSP: stopSearchDevice(device, callback)
RCSP->>Dev: 停止查找命令
Dev-->>RCSP: 响应
RCSP-->>App: onSuccess / onError
步骤详解(依据 SearchDeviceDemo.java):
- 调用
controller.searchDev(...)传入响铃开关Constants.RING_OP_OPEN、超时 60 秒、查找侧way(0=全部,1=左,2=右)与播放方Constants.RING_PLAYER_APP。 - SDK 内部将动作翻译为
SearchDevCmd并通过JL_BluetoothManager下发;动作最终结果经OnRcspActionCallback.onSuccess/onError返回。 - 若需感知设备侧状态,注册
BTRcspEventCallback,其onSearchDevice携带SearchDevParam(op、timeoutSec、player)。 - 查找结束调用
stopSearchDevice显式停止。
流程二:底层 RCSP 命令收发(以查询查找状态为例)
flowchart TD
Start([发起操作]) --> Get["manager.getConnectedDevice()"]
Get --> Build["CommandBuilder.buildSearchDevStatusCmd()"]
Build --> Send["manager.sendRcspCommand(device, cmd, callback)"]
Send --> Wait{"等待设备响应"}
Wait -->|"响应到达"| Resp["onCommandResponse(device, cmd)"]
Wait -->|"超时/错误"| Err["onErrCode(device, BaseError)"]
Resp --> Check{"cmd.getStatus() ==<br/>STATUS_SUCCESS?"}
Check -->|"否"| Bad["构造 BaseError<br/>SUB_ERR_RESPONSE_BAD_STATUS"]
Check -->|"是"| Cast["强转为 SearchDevCmd"]
Cast --> NullCheck{"response == null?"}
NullCheck -->|"是"| DataErr["BaseError<br/>SUB_ERR_DATA_FORMAT"]
NullCheck -->|"否"| Done([处理 SearchDevStatusResponse])
Bad --> Done
DataErr --> Done
Err --> Done
关键点(依据 SearchDeviceDemo.java):
- 底层路径要求业务方主动校验两层:先校验设备状态码,再校验响应体空值。这是 SDK 有意为之——底层 API 面向需要精确错误定位的高级调用方。
BaseError统一承载错误码与描述信息,onErrCode同时收到设备对象,便于按设备维度记录日志。- 命令响应的状态检查使用
StateCode.STATUS_SUCCESS;GetTargetInfoCmd示例(BtRcspControlDemo.java)还展示了用getType()判断命令是否携带响应的通用做法。
流程三:设备信息获取(高层 vs 底层)
flowchart LR
subgraph sg_High["高层 API"]
A1["requestDeviceInfo(device, 0xffffffff, callback)"] --> A2["onSuccess(device, DeviceInfo)"]
A1 --> A3["onError(device, BaseError)"]
end
subgraph sg_Cache["缓存"]
B1["getDeviceInfo() 直接返回缓存"]
end
subgraph sg_Low["底层 API"]
C1["buildGetDeviceInfoCmd(mask)"] --> C2["sendCommandAsync(device, cmd, timeoutMs, cb)"]
C2 --> C3["onCommandResponse + 手动校验状态"]
end
UI["业务代码"] --> sg_High
UI --> sg_Cache
UI --> sg_Low
三种方式对应不同场景:缓存读取用于频繁展示、允许略旧数据;高层请求用于需要实时刷新且不想处理协议细节;底层命令用于需要自定义 mask、自定义超时或深入调试。这一设计保证 SDK 对业务层"易用性"与"可控性"的平衡。
使用示例
以下示例均从仓库内真实测试代码中提取。
示例一:高层 API 发起设备查找
@Test
public void searchDevice(int way) {
//获取RCSPController对象
RCSPController controller = RCSPController.getInstance();
//way : 0 -- all 1 -- left 2 -- right
//执行搜索设备功能并等待结果回调
controller.searchDev(controller.getUsingDevice(), Constants.RING_OP_OPEN, 60, way, Constants.RING_PLAYER_APP, new OnRcspActionCallback<Boolean>() {
@Override
public void onSuccess(BluetoothDevice device, Boolean message) {
//成功回调
}
@Override
public void onError(BluetoothDevice device, BaseError error) {
//失败回调
//error - 错误信息
}
});
}
Source: SearchDeviceDemo.java
说明:高层 API 将"响铃开/关、超时、查找侧、播放方"收敛为方法参数,业务方只需实现 onSuccess / onError 两个回调即可完成一次设备查找动作。
示例二:注册事件监听接收设备侧状态
@Test
public void searchPhone() {
//获取RCSPController对象
RCSPController controller = RCSPController.getInstance();
//注册蓝牙RCSP事件监听器
controller.addBTRcspEventCallback(new BTRcspEventCallback() {
@Override
public void onSearchDevice(BluetoothDevice device, SearchDevParam searchDevParam) {
if (searchDevParam.getOp() == Constants.RING_OP_OPEN) { //open ring
int timeout = searchDevParam.getTimeoutSec();//timeout, unit : second
int player = searchDevParam.getPlayer(); //player (0 --- app play ring 1 --- device play ring)
} else {
//close ring
}
}
});
}
Source: SearchDeviceDemo.java
说明:BTRcspEventCallback.onSearchDevice 属于事件订阅模式,与请求-应答回调相互独立;SearchDevParam 中的 op、timeoutSec、player 三要素完整描述了一次设备查找事件。
示例三:底层 API 构建命令并校验响应
public void syncSearchDeviceStatusV0(Context context) {
//Step0: 获取JL_BluetoothManager对象
final JL_BluetoothManager manager = JL_BluetoothManager.getInstance(context);
//获取已连接和正在使用的设备
final BluetoothDevice device = manager.getConnectedDevice();
//Step1: 构建命令 --- 查询查找设备状态
CommandBase searchDevStatus = CommandBuilder.buildSearchDevStatusCmd();
//Step2: 执行查询查找设备状态功能并等待结果回调
manager.sendRcspCommand(device, searchDevStatus, new RcspCommandCallback() {
@Override
public void onCommandResponse(BluetoothDevice device, CommandBase cmd) {
//Step3: 检查设备状态
if (cmd.getStatus() != StateCode.STATUS_SUCCESS) { //设备状态异常,进行异常处理
onErrCode(device, new BaseError(ErrorCode.SUB_ERR_RESPONSE_BAD_STATUS, "Device reply an bad status: " + cmd.getStatus()));
return;
}
//成功回调
SearchDevCmd searchDevCmd = (SearchDevCmd) cmd;
SearchDevStatusResponse response = (SearchDevStatusResponse) searchDevCmd.getResponse();
//response -- 查询设备状态
}
@Override
public void onErrCode(BluetoothDevice device, BaseError error) {
//失败回调
//error - 错误信息
}
});
}
Source: SearchDeviceDemo.java
说明:底层路径遵循"构建 → 发送 → 校验状态 → 强转类型 → 取响应"五步范式,BaseError(ErrorCode, message) 构造方式统一了错误上报格式。
示例四:获取设备信息(高层 + 缓存)
public void getDeviceInfo() {
//获取RCSPController对象
RCSPController controller = RCSPController.getInstance();
//执行请求设备功能并等待结果回调
controller.requestDeviceInfo(controller.getUsingDevice(), 0xffffffff, new OnRcspActionCallback<DeviceInfo>() {
@Override
public void onSuccess(BluetoothDevice device, DeviceInfo message) {
//成功回调
//message - 设备信息
}
@Override
public void onError(BluetoothDevice device, BaseError error) {
//失败回调
//error - 错误信息
}
});
//第二种方式,获取缓存的设备信息
DeviceInfo deviceInfo = controller.getDeviceInfo();
}
Source: BtRcspControlDemo.java
说明:mask = 0xffffffff 表示请求全部设备属性;缓存接口 getDeviceInfo() 免去一次蓝牙往返,适合列表页/详情页的即时展示。
示例五:底层 API 自定义超时获取设备信息
public void getDeviceInfoV0(Context context, int mask) {
//获取JL_BluetoothManager对象
JL_BluetoothManager manager = JL_BluetoothManager.getInstance(context);
//构造功能命令
//mask = 0xffffffff; -- 获取所有属性
CommandBase getDeviceInfoCmd = CommandBuilder.buildGetDeviceInfoCmd(mask);
//执行获取设备信息功能并等待结果回调
manager.sendCommandAsync(manager.getConnectedDevice(), getDeviceInfoCmd, manager.getBluetoothOption().getTimeoutMs(), new RcspCommandCallback() {
@Override
public void onCommandResponse(BluetoothDevice device, CommandBase cmd) {
//检查设备状态
if (cmd.getStatus() != StateCode.STATUS_SUCCESS) { //设备状态异常,进行异常处理
onErrCode(device, new BaseError(ErrorCode.SUB_ERR_RESPONSE_BAD_STATUS, "Device reply an bad status: " + cmd.getStatus()));
return;
}
//成功回调
//获取对应的命令数据
GetTargetInfoCmd command = (GetTargetInfoCmd) cmd;
//获取回复数据
//注意 - 如果是没有回复数据的命令,回复数据为null
boolean isHasResponse = command.getType() == CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE
|| command.getType() == CommandBase.FLAG_NO_PARAMETER_AND_RESPONSE;
if (isHasResponse) {
TargetInfoResponse response = command.getResponse();
if (null == response) {
onErrCode(device, new BaseError(ErrorCode.SUB_ERR_DATA_FORMAT, "Response data is error."));
return;
}
//处理设备回复数据
}
}
@Override
public void onErrCode(BluetoothDevice device, BaseError error) {
//失败回调
//error - 错误信息
}
});
}
Source: BtRcspControlDemo.java
说明:此示例展示了完整的防御式解析——先用 getType() 判断命令是否应带响应,再对 getResponse() 做空值检查,最后才使用数据。manager.getBluetoothOption().getTimeoutMs() 提供默认超时,也可自行传入自定义超时值。
配置选项
| 配置项 | 类型 | 默认值(依据源码调用) | 说明 |
|---|---|---|---|
BluetoothOption.getTimeoutMs() | long | 由 JL_BluetoothManager.getBluetoothOption() 提供 | 底层命令发送默认超时,可被 sendCommandAsync 显式参数覆盖 |
searchDev 的 timeoutSec | int | 60(Demo 示例值) | 设备查找响铃的持续秒数 |
searchDev 的 way | int | 0=全部 / 1=左 / 2=右 | 指定查找哪一侧耳机(TWS 场景) |
searchDev 的 player | int | Constants.RING_PLAYER_APP(0) | 响铃播放方:0=App 播放,1=设备播放 |
requestDeviceInfo 的 mask | int | 0xffffffff(全部属性) | 位掩码,控制请求哪些设备属性 |
RING_OP_OPEN | int | Constants.RING_OP_OPEN | 响铃开启标志,事件回调中用于区分开关 |
API 参考
JL_BluetoothManager
| 方法签名 | 说明 |
|---|---|
static JL_BluetoothManager getInstance(Context context) | 获取全局单例;首次调用初始化连接管理资源 |
BluetoothDevice getConnectedDevice() | 返回当前已连接的蓝牙设备,未连接时可能为 null |
void sendRcspCommand(BluetoothDevice device, CommandBase cmd, RcspCommandCallback callback) | 发送 RCSP 命令,等待设备响应回调 |
void sendCommandAsync(BluetoothDevice device, CommandBase cmd, long timeoutMs, RcspCommandCallback callback) | 异步发送命令并指定超时时间 |
BluetoothOption getBluetoothOption() | 获取蓝牙选项配置(含默认超时 getTimeoutMs()) |
RCSPController
| 方法签名 | 说明 |
|---|---|
static RCSPController getInstance() | 获取全局单例 |
BluetoothDevice getUsingDevice() | 返回当前正在使用的设备(可能不同于已连接设备) |
void searchDev(BluetoothDevice device, int op, int timeoutSec, int way, int player, OnRcspActionCallback<Boolean> callback) | 查找设备;op 响铃开关、way 0/1/2、player 0=App/1=设备 |
void stopSearchDevice(BluetoothDevice device, OnRcspActionCallback<Boolean> callback) | 停止设备查找 |
void syncSearchDeviceStatus(BluetoothDevice device, OnRcspActionCallback<SearchDevStatusResponse> callback) | 同步查询查找设备状态 |
void requestDeviceInfo(BluetoothDevice device, int mask, OnRcspActionCallback<DeviceInfo> callback) | 请求设备信息(mask 位掩码) |
DeviceInfo getDeviceInfo() | 读取缓存设备信息 |
void addBTRcspEventCallback(BTRcspEventCallback callback) | 注册蓝牙事件监听(含 onSearchDevice) |
回调接口
RcspCommandCallback(底层命令回调)
onCommandResponse(BluetoothDevice device, CommandBase cmd):设备响应到达;调用方需自行校验cmd.getStatus() == StateCode.STATUS_SUCCESS并解析响应体。onErrCode(BluetoothDevice device, BaseError error):命令发送失败、超时或设备状态异常。
OnRcspActionCallback<T>(高层动作回调)
onSuccess(BluetoothDevice device, T message):动作成功,message为结果对象(如Boolean、DeviceInfo、SearchDevStatusResponse)。onError(BluetoothDevice device, BaseError error):动作失败,error携带错误码与描述。
BTRcspEventCallback(事件订阅)
onSearchDevice(BluetoothDevice device, SearchDevParam searchDevParam):设备侧查找状态变化事件;searchDevParam.getOp()区分响铃开关,getTimeoutSec()为超时秒数,getPlayer()为播放方。
数据模型
CommandBase:命令基类。getStatus()返回设备状态码;getType()返回命令类型标志(FLAG_HAVE_PARAMETER_AND_RESPONSE/FLAG_NO_PARAMETER_AND_RESPONSE);子类通过getResponse()暴露响应体。SearchDevCmd/SearchDevParam/SearchDevStatusResponse:查找设备命令三元组(命令 / 参数 / 响应)。GetTargetInfoCmd/TargetInfoResponse:获取设备信息命令与响应。DeviceInfo:设备信息聚合模型,由requestDeviceInfo返回并缓存于RCSPController。BaseError:统一错误模型,由ErrorCode枚举 + 描述字符串构造,如new BaseError(ErrorCode.SUB_ERR_RESPONSE_BAD_STATUS, "...")。
失败模式、边界情况与并发
设备状态码异常
所有命令响应的首要检查点是 cmd.getStatus() != StateCode.STATUS_SUCCESS。设备可能返回"忙碌、不支持、参数错误"等状态,统一转换为 SUB_ERR_RESPONSE_BAD_STATUS 错误(携带实际状态码以便排查)。设计意图:把设备侧的状态错误与传输层错误归一到同一个 onErrCode 通道,简化调用方的错误分支。
响应体为空
即使状态码为成功,命令也可能没有响应数据(FLAG_NO_PARAMETER_AND_RESPONSE 类命令),或响应解析失败返回 null。底层解析模式先用 getType() 判断是否应携带响应,再对 getResponse() 做 null 检查,命中则报 SUB_ERR_DATA_FORMAT。设计意图:防御式解析避免强转空对象导致 NullPointerException,并将数据损坏问题显式化。
设备未连接 / 设备为 null
getConnectedDevice() 与 getUsingDevice() 在未连接状态下可能返回 null;Demo 中所有发送前都先取设备再构建命令。实际业务应在连接事件回调(BTRcspEventCallback 体系)中确认连接就绪后再发起操作,避免向 null 设备发送命令。
超时控制
底层 API 通过 sendCommandAsync(device, cmd, timeoutMs, callback) 显式控制等待时长,默认值来自 getBluetoothOption().getTimeoutMs()。超时后 SDK 触发 onErrCode,调用方不应继续等待 onCommandResponse——两路回调互斥返回。
事件与请求的并发
BTRcspEventCallback(事件流)与 RcspCommandCallback(请求-应答)可能同时触发:例如设备查找期间,searchDev 的 onSuccess 与 onSearchDevice 事件会交错到达。业务代码应确保 UI 状态更新具备幂等性,避免"响铃已停止但事件仍上报"造成状态回退。
双设备(TWS)场景
way 参数(0=全部 / 1=左 / 2=右)表明同一命令可定向到单侧耳机;getUsingDevice() 与 getConnectedDevice() 的差异在双设备连接时尤为关键——"正在使用"的设备才是大部分业务动作的目标。
性能与运维
- 缓存优先:
controller.getDeviceInfo()缓存读取避免高频 UI 刷新触发蓝牙往返;仅在需要实时数据时调用requestDeviceInfo。 - 避免重复注册:
addBTRcspEventCallback为全局事件监听,建议在组件生命周期内注册一次(onCreate/onResume),并在销毁时反注册,防止内存泄漏与重复回调。 - 命令串行化:蓝牙 SPP/LE 通道带宽有限,高频命令(如 EQ 拖动、音量调节)建议节流;
sendRcspCommand与sendCommandAsync的选择需结合业务对超时的敏感性。 - 日志:SDK 提供
JL_Log工具(Demo 中导入),排查连接问题时关注命令构建、发送与回调三处的日志时间戳。
扩展点
- 新命令接入:实现
CommandBase子类 + 对应 Param/Response 模型,在CommandBuilder增加工厂方法,即可复用sendRcspCommand通道,无需改动连接层。 - 新事件订阅:扩展
BTRcspEventCallback增加回调方法,SDK 在对应事件点触发,业务方通过既有addBTRcspEventCallback注册。 - 自定义超时策略:底层 API 的
timeoutMs参数支持按命令差异化配置,可依据设备类型或命令复杂度动态计算。 - 高层动作封装:仿照
searchDev/requestDeviceInfo模式,将新业务动作封装进RCSPController,统一走OnRcspActionCallback回调契约。
测试覆盖
仓库中与蓝牙连接相关的测试集中在 btsmart/src/test/java/com/jieli/btsmart/demo/ 下:
- SearchDeviceDemo.java:覆盖设备查找(高层
searchDev/stopSearchDevice/syncSearchDeviceStatus)与底层命令路径(buildSearchDevStatusCmd+sendRcspCommand)。 - BtRcspControlDemo.java:覆盖设备信息获取(高层
requestDeviceInfo、缓存getDeviceInfo、底层sendCommandAsync),以及系统信息、设备模式切换、重启等 RCSP 控制命令。 - TwsDemo.java:TWS 信息获取,展示
getUsingDevice()在双设备场景下的用法。
这些测试以"Demo 即用法文档"的方式,明确了每个 API 的调用顺序、回调语义与错误处理范式,是接入 SDK 的最佳参考。
Related Links
- SearchDeviceDemo.java(设备查找 Demo)
- BtRcspControlDemo.java(RCSP 控制 Demo)
- TwsDemo.java(TWS 双耳 Demo)
- 设备功能页面:闹钟(Alarm)、EQ 音效、FM 收音、助听(Hearing)、灯光控制等均复用本文档的命令通道,其参数模型见同级功能页面。
- TWS 双耳同步与 OTA 固件升级为独立专项流程,详见对应页面。