杰理 SDK 文档中心
首页
首页
  • 项目概述

    • 项目简介与核心能力
    • 运行环境与SDK版本
  • 快速开始

    • 工程导入与依赖配置
    • 权限配置与示例运行
  • 平台架构

    • SDK分层架构与RCSP协议
    • 蓝牙连接库
    • 健康SDK核心库 JL_Watch
    • 健康服务器与云端服务
  • 健康与运动数据

    • 健康数据同步
    • 运动数据同步
    • 本地数据持久化
  • 设备管理功能

    • 表盘管理
    • 闹钟与健康提醒
    • 消息与联系人同步
    • 天气同步
    • 设备查找
    • 支付宝集成
  • 传输与媒体处理

    • 文件传输与文件管理
    • 音乐传输与播放控制
    • 图像转换库
    • 音频编解码与解密
  • OTA 升级

    • 固件空中升级流程
    • 4G模块与差分升级
  • AI 能力

    • AI表盘与云服务
    • AI语音助手
  • 示例应用

    • HealthAide 健康助手应用
    • WatchTestTool 测试工具
  • 开发者指南

    • 自定义命令扩展
    • 调试技巧与问题排查
    • 版本历史与兼容性

自定义命令扩展

自定义命令扩展(Custom Command)是 JL RCSP SDK 提供的一种"透传"机制,允许 App 与设备(手表/手环等)之间直接交换业务自定义的原始字节数据,用于支持 SDK 未内置的客户拓展功能。

Purpose and Scope

本页面说明如何在 JL Health SDK 体系下开发自定义命令(Custom Command):包括命令的构建、发送、接收、回复、状态判定、MTU 大小限制以及错误处理。页面以仓库中两个官方 Demo —— com.jieli.watchtesttool.CustomCommandDemo 与 com.jieli.healthaide.demos.CustomCommandDemo —— 为唯一事实来源,完整解读其用法与设计意图。

本页面不涉及以下内容(属于其它目录页范畴):

  • SDK 的蓝牙连接、扫描、配对流程(见"连接与配对"相关页面);
  • SDK 内置业务命令(OTA、健康数据、表盘管理等,各功能有独立页面);
  • 协议栈底层封包格式与 RCSP 帧结构细节。

Overview

为什么需要自定义命令

SDK 内置了大量标准功能(OTA 升级、健康数据同步、闹钟、表盘管理等),但客户产品的私有功能无法全部预置。自定义命令提供一条通用通道:

  • 下行(App → 设备):App 把任意字节数组打包成 CustomCmd 命令发送给设备,设备解析后回复执行结果;
  • 上行(设备 → App):设备主动下发自定义命令,App 通过全局回调接收并处理,可选择性回复。

这一机制使客户无需修改 SDK 即可扩展任意功能,且数据载荷(byte[])完全由业务方定义,与固件侧协商好格式即可。

关键概念

概念说明
CustomCmd自定义命令的模型类(com.jieli.jl_rcsp.model.command.custom.CustomCmd),承载参数与响应
CustomParam命令参数,getData() 返回设备下发或 App 上行的自定义字节数据
CustomResponse命令响应,getData() 返回设备回复的自定义字节数据
CommandBuilder.buildCustomCmd(data)构建一条需要设备回复的自定义命令
CommandBuilder.buildCustomCmdWithoutResponse(data)构建一条无需设备回复的自定义命令(单向通知)
Command.CMD_CUSTOM / Command.CMD_EXTRA_CUSTOM命令 ID,用于在回调中过滤自定义命令
StateCode.STATUS_SUCCESS 等命令执行状态码
RcspErrorCode错误码集合,用于构造 BaseError

两个 Demo 的差异

仓库中存在两份 CustomCommandDemo:

  • WatchTestTool 版(code/tool/WatchTestTool_V0.9.0_SDK_V1.14.0/.../watchtesttool/CustomCommandDemo.java)是最简用法,README 明确指定其为参考实现;发送时固定使用 Command.CMD_CUSTOM 过滤,命令均需回复。
  • HealthAide 版(code/app/HealthAide_V1.1.0_SDK_V1.14.0/.../healthaide/demos/CustomCommandDemo.java)是增强版:支持"无回复"构建模式、按命令类型(FLAG_HAVE_PARAMETER_AND_RESPONSE 等)动态判断是否需要回复、细化 STATUS_UNKOWN_CMD 错误分支、并给出 MTU 计算示例;接收时使用 Command.CMD_EXTRA_CUSTOM 过滤。

两者底层都依赖 WatchManager(WatchOpImpl 的子类封装)与 com.jieli.jl_rcsp SDK,掌握其一即可迁移到另一工程。

Architecture

自定义命令扩展在 App 侧的完整链路如下图所示:业务层通过 WatchManager(SDK 门面)与 CommandBuilder(命令工厂)交互,底层由 RCSP 协议栈负责蓝牙传输,设备侧解析后通过回调回流到业务层。

flowchart TD
    subgraph sg_Biz["业务层 (Demo)  com.jieli.*.demos / watchtesttool"]
        Send["CustomCommandDemo.sendCustomCommand()"]
        Recv["CustomCommandDemo.receiveCustomCmd()"]
        MTU["CustomCommandDemo.checkRCSPProtocolMTU()"]
    end

    subgraph sg_App["App 封装层"]
        WM["WatchManager (WatchOpImpl 子类)"]
        SendRcsp["sendRcspCommand()"]
        SendResp["sendCommandResponse()"]
        RegCb["registerOnRcspCallback()"]
        Target["getTargetDevice()"]
    end

    subgraph sg_Sdk["jl_rcsp SDK 层"]
        Builder["CommandBuilder"]
        Build1["buildCustomCmd(data)"]
        Build2["buildCustomCmdWithoutResponse(data)"]
        Model["CustomCmd / CustomParam / CustomResponse"]
        CB["OnRcspCallback.onRcspCommand()"]
        CBErr["RcspCommandCallback.onCommandResponse() / onErrCode()"]
        DSM["DeviceStatusManager.getMaxReceiveMtu()"]
        State["StateCode / RcspErrorCode"]
    end

    subgraph sg_Dev["设备 (固件)"]
        Dev["手环/手表固件"]
    end

    Send -->|"获取实例"| WM
    Send -->|"构建命令"| Builder
    Builder --> Build1
    Builder --> Build2
    Build1 --> Model
    Build2 --> Model
    Send -->|"发送并等待回调"| SendRcsp
    SendRcsp -->|"携带 CustomCmd"| Target
    SendRcsp --> CBErr
    Recv --> RegCb
    RegCb --> CB
    CB -->|"按命令 ID 过滤后强转"| Model
    Recv -->|"回复结果"| SendResp
    SendResp --> Model
    MTU --> DSM
    DSM -->|"protocolMtu - 23"| Send
    SendRcsp -->|"BLE 透传"| Dev
    Dev -->|"设备主动下发"| CB
    CBErr -->|"BaseError"| Send
    State -.->|"状态/错误判定"| CBErr
    State -.-> CB

架构解读:

  1. 业务层是唯一的客户代码入口:三个方法分别覆盖"发送 + 接收 + 尺寸约束"三个扩展维度,客户只需实现 sendCustomCommand(byte[]) 与 receiveCustomCmd() 中的解析逻辑。
  2. App 封装层 WatchManager 屏蔽了设备管理细节:getInstance() 单例获取、getTargetDevice() 得到当前连接设备、sendRcspCommand() 统一发送入口、sendCommandResponse() 回复设备、registerOnRcspCallback() 注册全局上行命令回调。Demo 注释强调"WatchManager 是 WatchOpImpl 的子类,须在 SDK 配置好之后使用",即连接建立后才能发送命令。
  3. SDK 层 CommandBuilder 是命令工厂:buildCustomCmd / buildCustomCmdWithoutResponse 分别生成"要回复"与"不要回复"的命令;CustomCmd 内部按 RCSP 协议打包,CustomParam/CustomResponse 承载业务字节。
  4. 设备侧是自定义命令的另一端:下行命令由固件解析并回复状态;上行命令由固件主动发起,App 侧 onRcspCommand 回调统一收口。

核心机制详解

1. 发送自定义命令(App → 设备)

发送链路的完整步骤(以 HealthAide 增强版为例,见 CustomCommandDemo.java):

  1. 获取管理器:WatchManager.getInstance() 取得全局单例(须先完成 SDK 初始化与设备连接配置)。
  2. 构建命令:CommandBuilder.buildCustomCmd(data) 生成携带自定义数据的命令;若业务不需要设备回复(如单向通知、心跳探活),改用 buildCustomCmdWithoutResponse(data) —— 该模式下 SDK 不会进入等待响应的状态机,onCommandResponse 回调在发送完成后即被触发(响应体为空)。
  3. 发送并等待回调:manager.sendRcspCommand(manager.getTargetDevice(), customCmd, callback)。
  4. 状态判定:回调 onCommandResponse 中首先检查 cmd.getStatus():
    • STATUS_SUCCESS → 继续解析响应;
    • STATUS_UNKOWN_CMD(设备不认识该命令)→ 构造 BaseError(RcspErrorCode.ERR_FUNC_NOT_SUPPORT),并回填 opCode 与 sn 便于定位;
    • 其它非成功状态 → 通过 RcspErrorCode.buildJsonError(...) 构造带状态码详情的错误。
  5. 响应解析:按命令类型判断是否需要响应——FLAG_HAVE_PARAMETER_AND_RESPONSE 或 FLAG_NO_PARAMETER_HAVE_RESPONSE 表示带回复;若类型声明有回复但 cmd.getResponse() 为 null,判定为 ERR_PARSE_DATA 解析错误;否则 response.getData() 即设备回复的业务字节。

WatchTestTool 简版逻辑相同但更精简:只检查 STATUS_SUCCESS 与响应非空,其余状态统一走 ERR_RESPONSE_BAD_RESULT 分支。

2. 接收自定义命令(设备 → App)

接收链路(见 CustomCommandDemo.java):

  1. 注册全局回调:manager.registerOnRcspCallback(new OnRcspCallback(){...})。该回调接收所有上行 RCSP 命令,因此必须按命令 ID 过滤。
  2. ID 过滤:if (command.getId() != Command.CMD_EXTRA_CUSTOM) return;(WatchTestTool 版过滤 Command.CMD_CUSTOM)。注意两个 Demo 使用了不同的命令 ID 常量——这是客户与固件协商的结果,实际项目应统一为同一个 ID,避免过滤失效。
  3. 强转模型:CustomCmd customCmd = (CustomCmd) command;,随后通过 customCmd.getType() 判断该命令是否需要回复。
  4. 参数校验:customCmd.getParam() 为 null 时说明数据非法:
    • 若该命令需要回复 → 置 STATUS_SUCCESS(或按业务置失败)后调用 manager.sendCommandResponse(device, customCmd, null) 回执;
    • 否则直接返回。
  5. 业务处理:param.getData() 取出设备下发的字节,进行业务解析;需要回复时,customCmd.setStatus(StateCode.STATUS_SUCCESS) 并可通过 customCmd.setParam(...) 携带回复数据(Demo 中展示了被注释的 responseData 设置示例,默认以 null 回复空数据),最后 sendCommandResponse 回传。

3. 命令类型(是否需要回复)

CommandBase 定义了四种命令类型标志,Demo 中通过位判断决定行为:

类型标志含义
FLAG_HAVE_PARAMETER_AND_RESPONSE有参数且需要响应(最常见的双向命令)
FLAG_NO_PARAMETER_HAVE_RESPONSE无参数但需要响应
其它标志(无响应类)单向命令,发送后不等待回复

发送方据此决定是否读取 cmd.getResponse();接收方据此决定是否调用 sendCommandResponse()。设计意图:协议层将"是否需要回复"作为命令属性携带,使收发双方无需额外约定即可保持状态机一致,避免出现"发送方等待回复而设备不回复"导致的超时挂起。

4. MTU 与自定义数据大小限制

见 checkRCSPProtocolMTU:

//最大发送MTU
int protocolMtu = DeviceStatusManager.getInstance().getMaxReceiveMtu(device);
//自定义命令的大小 = 最大发送MTU - 协议包长度
int customDataLimit = protocolMtu - 23;
//建议与固件协商好,不建议发最大数据

设计意图:BLE 单包 MTU 有限(常见 23~517 字节),RCSP 协议封装(命令头、参数区、校验等)约占 23 字节,因此自定义数据净荷上限约为 MTU - 23。Demo 明确建议:与固件协商确定实际分包/限长策略,且不要每次都发满上限——留出余量可避免极端情况下的封包失败或丢包重传,提升成功率。超过该长度的大数据应使用 SDK 的"大文件传输/分包"机制,而不是自定义命令。

Core Flow

下行:App 发送自定义命令并接收设备回复

sequenceDiagram
    participant App as 业务层 (Demo)
    participant WM as WatchManager
    participant B as CommandBuilder
    participant SDK as RCSP 协议栈
    participant Dev as 设备固件

    App->>WM: getInstance() / getTargetDevice()
    App->>B: buildCustomCmd(data) 或 buildCustomCmdWithoutResponse(data)
    B-->>App: CustomCmd
    App->>WM: sendRcspCommand(device, customCmd, callback)
    WM->>SDK: 打包并发送 (BLE)
    SDK->>Dev: 自定义命令帧
    alt 需要回复的命令
        Dev-->>SDK: 回复状态 + CustomResponse 数据
        SDK-->>WM: 分发响应
        WM-->>App: onCommandResponse(device, cmd)
        App->>App: 校验 status == STATUS_SUCCESS
        App->>App: 校验 response != null → getData() 解析
    else 无需回复的命令
        Dev-->>SDK: 仅回执状态(或无回执)
        SDK-->>WM: 发送完成事件
        WM-->>App: onCommandResponse(device, cmd) (无响应体)
    end
    Note over App: 失败时走 onErrCode(device, BaseError)

上行:设备主动下发自定义命令

sequenceDiagram
    participant Dev as 设备固件
    participant SDK as RCSP 协议栈
    participant WM as WatchManager
    participant CB as OnRcspCallback
    participant App as 业务层 (Demo)

    Dev->>SDK: 上行自定义命令帧
    SDK->>WM: 解析为 CommandBase
    WM->>CB: onRcspCommand(device, command)
    CB->>CB: getId() != CMD_CUSTOM/CMD_EXTRA_CUSTOM → return
    CB->>CB: 强转 CustomCmd, 判断 type 是否需要回复
    alt param == null
        CB->>WM: sendCommandResponse(device, cmd, null) 回执(若需回复)
    else param != null
        CB->>App: param.getData() 业务解析
        CB->>WM: setStatus(SUCCESS) + sendCommandResponse(device, cmd, null)
    end

Usage Examples

示例 1:发送自定义命令(最简版,WatchTestTool)

该示例展示最直接的用法:构建命令 → 发送 → 回调中校验状态并解析响应。

@Test
public void sendCustomCommand(byte[] data) {
    //WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager manager = WatchManager.getInstance();
    //Send custom command and waiting for the result callback
    manager.sendRcspCommand(manager.getTargetDevice(), CommandBuilder.buildCustomCmd(data), new RcspCommandCallback<CustomCmd>() {
        @Override
        public void onCommandResponse(BluetoothDevice device, CustomCmd cmd) {
            if (cmd.getStatus() != StateCode.STATUS_SUCCESS) {
                onErrCode(device, new BaseError(RcspErrorCode.ERR_RESPONSE_BAD_RESULT, "Device Reply an bad state: " + cmd.getStatus()));
                return;
            }
            CustomResponse response = cmd.getResponse();
            if (null == response) {
                onErrCode(device, new BaseError(RcspErrorCode.ERR_PARSE_DATA, "Response data is error."));
                return;
            }
            byte[] data = response.getData();
            //parse data
        }

        @Override
        public void onErrCode(BluetoothDevice device, BaseError error) {
            //callback error event
        }
    });
}

Source: CustomCommandDemo.java

示例 2:发送自定义命令(增强版,支持无回复模式与错误细分)

HealthAide 版将"是否需要回复"显式建模:可选用 buildCustomCmdWithoutResponse 构建单向命令,并根据命令类型标志决定是否解析响应体;同时把 STATUS_UNKOWN_CMD 单独映射为 ERR_FUNC_NOT_SUPPORT,便于产品层提示"设备不支持该功能"。

@Test
public void sendCustomCommand(byte[] data) {
    //WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager manager = WatchManager.getInstance();
    //build the custom command
    CommandBase customCmd = CommandBuilder.buildCustomCmd(data); //carries custom data
    final boolean isNoResponse = false; //Set whether the command does not require reply
    if (isNoResponse) {
        customCmd = CommandBuilder.buildCustomCmdWithoutResponse(data);//Setting does not require reply
    }
    //Send custom command and waiting for the result callback
    manager.sendRcspCommand(manager.getTargetDevice(), customCmd, new RcspCommandCallback<CustomCmd>() {
        @Override
        public void onCommandResponse(BluetoothDevice device, CustomCmd cmd) {
            final int status = cmd.getStatus();
            if (status != StateCode.STATUS_SUCCESS) {
                if (status == StateCode.STATUS_UNKOWN_CMD) {
                    onErrCode(device, new BaseError(RcspErrorCode.ERR_FUNC_NOT_SUPPORT)
                            .setOpCode(cmd.getId()).setSn(cmd.getOpCodeSn()));
                    return;
                }
                onErrCode(device, RcspErrorCode.buildJsonError(cmd.getId(), cmd.getOpCodeSn(), RcspErrorCode.ERR_RESPONSE_BAD_STATUS,
                        status, StateCode.printResponseStatus(status)));
                return;
            }
            boolean hasResponse = cmd.getType() == CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE
                    || cmd.getType() == CommandBase.FLAG_NO_PARAMETER_HAVE_RESPONSE;
            if (hasResponse) { //有回复
                CustomResponse response = cmd.getResponse();
                if (null == response) {
                    onErrCode(device, new BaseError(RcspErrorCode.ERR_PARSE_DATA, RcspErrorCode.getErrorDesc(RcspErrorCode.ERR_PARSE_DATA)));
                    return;
                }
                byte[] data = response.getData();
                //parse data
            }
        }

        @Override
        public void onErrCode(BluetoothDevice device, BaseError error) {
            //callback error event
        }
    });
}

Source: CustomCommandDemo.java

示例 3:接收设备下发的自定义命令

接收侧必须先 registerOnRcspCallback 注册全局回调,然后在回调内按命令 ID 过滤、强转模型、解析参数,并按需回复:

@Test
public void receiveCustomCmd() {
    //WatchManager是WatchOpImpl的子类,须在1.3配置好sdk
    WatchManager manager = WatchManager.getInstance();
    //add Rcsp event callback
    manager.registerOnRcspCallback(new OnRcspCallback() {
        @Override
        public void onRcspCommand(BluetoothDevice device, CommandBase command) {
            //receive rcsp command
            if (command.getId() != Command.CMD_EXTRA_CUSTOM) return; //filter other command
            CustomCmd customCmd = (CustomCmd) command;
            //Determine whether to reply the command
            boolean hasResponse = customCmd.getType() == CommandBase.FLAG_HAVE_PARAMETER_AND_RESPONSE
                    || customCmd.getType() == CommandBase.FLAG_NO_PARAMETER_HAVE_RESPONSE;
            CustomParam param = customCmd.getParam();
            if (param == null) {
                if (hasResponse) {
                    customCmd.setParam(null);
                    customCmd.setStatus(StateCode.STATUS_SUCCESS);
                    manager.sendCommandResponse(device, customCmd, null);
                }
                return;
            }
            byte[] data = param.getData(); //the custom data from device
            //parse data
            if (hasResponse) { //需要回复
                //doing some thing and reply a success result.
                customCmd.setStatus(StateCode.STATUS_SUCCESS);
                customCmd.setParam(null);
                manager.sendCommandResponse(device, customCmd, null);
            }
        }
    });
}

Source: CustomCommandDemo.java

WatchTestTool 简版的接收逻辑(过滤 Command.CMD_CUSTOM、参数为空时置 STATUS_FAIL 回复)见 CustomCommandDemo.java。

示例 4:计算自定义数据长度上限

@Test
public void checkRCSPProtocolMTU(BluetoothDevice device) {
    //最大发送MTU
    int protocolMtu = DeviceStatusManager.getInstance().getMaxReceiveMtu(device);
    //自定义命令的大小 = 最大发送MTU - 协议包长度
    int customDataLimit = protocolMtu - 23;
    //建议与固件协商好,不建议发最大数据
}

Source: CustomCommandDemo.java

API Reference

以下 API 均来自 com.jieli.jl_rcsp SDK 与 WatchManager 封装,签名以 Demo 中的实际用法为准。

CommandBuilder.buildCustomCmd(byte[] data): CommandBase

构建一条需要设备回复的自定义命令,数据载荷为 data。

参数: data (byte[]) —— 业务自定义数据,长度应满足 data.length <= getMaxReceiveMtu(device) - 23。

返回: 携带自定义数据与响应标志的 CommandBase(实际为 CustomCmd)。

CommandBuilder.buildCustomCmdWithoutResponse(byte[] data): CommandBase

构建一条无需设备回复的自定义命令(单向通知)。

参数: data (byte[]) —— 业务自定义数据。

返回: 无响应标志的 CommandBase。发送后回调立即触发且无响应体,业务不应读取 cmd.getResponse()。

WatchManager.sendRcspCommand(BluetoothDevice device, CommandBase cmd, RcspCommandCallback<T> callback)

向目标设备发送 RCSP 命令(含自定义命令)并注册结果回调。

参数:

  • device (BluetoothDevice) —— 目标设备,通常来自 manager.getTargetDevice();
  • cmd (CommandBase) —— 由 CommandBuilder 构建的命令;
  • callback (RcspCommandCallback<CustomCmd>) —— 结果回调,需实现 onCommandResponse(device, cmd) 与 onErrCode(device, error)。

WatchManager.registerOnRcspCallback(OnRcspCallback callback)

注册全局上行命令回调,设备主动下发的所有 RCSP 命令都会进入 onRcspCommand(device, command)。

注意: 回调是全局的,必须用 command.getId() 过滤出 CMD_CUSTOM / CMD_EXTRA_CUSTOM,再强转为 CustomCmd 处理。

WatchManager.sendCommandResponse(BluetoothDevice device, CustomCmd cmd, Object extra)

回复设备发来的自定义命令。

参数: device 来源设备;cmd 原命令对象(已通过 setStatus() 设置结果、可选 setParam() 设置回复数据);extra 扩展参数,Demo 中固定传 null。

CustomCmd.getStatus() / setStatus(int)、getType()、getResponse()、getParam()、getId()、getOpCodeSn()

  • getStatus() 返回 StateCode 中的执行状态(STATUS_SUCCESS / STATUS_FAIL / STATUS_UNKOWN_CMD 等);
  • getType() 返回 CommandBase 类型标志,用于判断是否需要回复;
  • getResponse() 返回 CustomResponse(可能为 null),getData() 取回复字节;
  • getParam() 返回 CustomParam(可能为 null),getData() 取参数字节;
  • getId() 返回命令 ID,用于回调过滤;getOpCodeSn() 返回命令序号,用于错误定位。

DeviceStatusManager.getInstance().getMaxReceiveMtu(BluetoothDevice device): int

查询当前设备协商后的最大接收 MTU,用于计算自定义数据长度上限。

Configuration Options 与约束

自定义命令本身没有配置文件;其"配置"体现在代码常量与运行时参数上:

配置项位置/形式默认值说明
命令 IDCommand.CMD_CUSTOM / Command.CMD_EXTRA_CUSTOMSDK 常量接收过滤用,必须与固件侧协商一致;两个 Demo 使用了不同 ID,实际项目统一为一个
是否需要回复buildCustomCmd vs buildCustomCmdWithoutResponse需回复单向通知类业务选无回复模式,可减少等待与超时
数据长度上限getMaxReceiveMtu(device) - 23随 MTU超过需与固件协商分包;Demo 建议预留余量
响应数据CustomCmd.setParam(...) 后 sendCommandResponse空(null)需要给设备回数据时设置,Demo 中默认回空
SDK 初始化"1.3 配置好 sdk"(Demo 注释)—必须先完成 SDK 配置与连接,WatchManager.getInstance() 才可用

Failure Modes, Edge Cases & Concurrency

1. 设备回复异常状态

发送后 cmd.getStatus() != STATUS_SUCCESS 即表示设备执行失败。增强版 Demo 细分了两种场景:

  • STATUS_UNKOWN_CMD:设备不认识该命令 → 映射为 ERR_FUNC_NOT_SUPPORT,并回填 opCode 与 sn,业务可据此提示"固件版本过旧/功能未启用";
  • 其它非成功状态:用 RcspErrorCode.buildJsonError(...) 构造带 status 详情的错误,便于日志与联调。

设计意图:错误信息携带命令 ID、序号与状态描述,保证在异步回调场景下可精确对账是哪条命令失败。

2. 响应为空(解析失败)

命令类型声明"需要回复",但 cmd.getResponse() 为 null → ERR_PARSE_DATA。可能原因:设备回复帧不完整、数据被截断、或固件对无响应命令误回了空帧。Demo 在读取 response.getData() 之前一律判空,避免 NPE。

3. 接收侧参数为空

设备下发命令但 customCmd.getParam() 为 null(数据区缺失):

  • WatchTestTool 版:置 STATUS_FAIL 后回执,让设备知道数据非法;
  • HealthAide 版:仅在该命令需要回复时回执(默认回 SUCCESS 空数据),并直接 return 跳过业务处理。

设计意图:接收侧必须"有来必回"——凡是声明需要回复的命令,即使数据非法也要回执,否则设备会一直等待导致链路阻塞或超时。

4. 命令 ID 过滤错误

onRcspCommand 是所有上行命令的汇聚点,忘记过滤或过滤常量不一致会导致:把普通业务命令强转为 CustomCmd 引发 ClassCastException,或漏掉自定义命令导致业务静默丢失。两个 Demo 使用不同 ID 常量(CMD_CUSTOM vs CMD_EXTRA_CUSTOM)也提示:跨工程移植时必须按实际固件协议统一 ID。

5. 数据超长

自定义数据超过 MTU - 23 时,单包无法承载。Demo 建议与固件协商限长,且"不建议发最大数据"——发送满 MTU 的数据在信号波动时更易丢包,重传成本高。超长数据应走 SDK 的大文件/分包通道。

6. 并发与回调线程

  • sendRcspCommand 与 registerOnRcspCallback 均为异步回调模型;RcspCommandCallback.onCommandResponse 与 OnRcspCallback.onRcspCommand 可能来自协议栈收发线程,业务侧修改 UI 或共享状态需自行切线程或加锁。
  • 连续发送多条命令时,onErrCode 中的 sn(getOpCodeSn())可用于关联具体命令,SDK 按序号管理应答,业务不应依赖回调顺序。
  • 接收回调中的 sendCommandResponse 应在当前回调内同步完成,避免跨线程延迟回复导致设备侧超时。

7. 生命周期约束

Demo 注释明确"须在 1.3 配置好 sdk":getInstance() 之前必须完成 SDK 初始化与设备连接;设备断开后发送会立即走 onErrCode。注册的回调建议在页面/服务销毁时反注册,防止内存泄漏与重复回调。

Performance 与 Operational 注意事项

  • 净荷预算:每次发送前可用 getMaxReceiveMtu(device) 动态计算上限(MTU - 23),并在日志中打印实际发送长度,便于定位"发大包失败"类问题。
  • 避免高频单向命令:无回复模式虽省去等待,但大量无回执通知会占用 BLE 带宽,建议业务侧合并/限频。
  • 错误码上报:统一使用 BaseError/RcspErrorCode 构造错误对象并携带 opCode、sn,接入统一日志后能显著降低联调成本。
  • 超时兜底:Demo 未显式设置超时,实际产品中建议在 sendRcspCommand 外层封装超时与重试策略(SDK 底层对需要回复的命令有应答超时机制,业务需在 onErrCode 中兜底提示)。

Extension Points

  1. 自定义命令是"协议级透传"扩展点:客户私有功能只需定义 byte[] 载荷协议,无需改动 SDK;CustomParam/CustomResponse 的 getData()/setData() 即数据出入口。
  2. 接收侧可携带回复数据:HealthAide 版注释展示了 byte[] responseData = new byte[0]; param.setData(responseData); customCmd.setParam(param); 的写法——需要给设备返回业务数据时取消注释并填入即可,这是上行命令"请求-应答"能力的扩展点。
  3. 命令 ID 可协商:CMD_CUSTOM / CMD_EXTRA_CUSTOM 表明 SDK 预留了多个自定义命令槽位;客户可按功能域划分多个 ID(如"传感器配置""显示设置"),在 onRcspCommand 中分别路由,实现结构化扩展。
  4. 封装层可复用:WatchManager(WatchOpImpl 子类)将发送/接收/回复统一封装,Demo 的 sendCustomCommand/receiveCustomCmd 可直接搬入业务 Manager 或 Repository,作为私有协议的基础设施。

Related Links

  • CustomCommandDemo.java(WatchTestTool 版) — README 指定的官方参考实现(最简用法)
  • CustomCommandDemo.java(HealthAide 版) — 增强版(无回复模式、错误细分、MTU 计算)
  • README.md — 功能总览,"自定义命令:支持客户拓展功能",参考 test 包 com.jieli.watchtesttool.CustomCommandDemo
  • 相关 SDK 包(com.jieli.jl_rcsp)的 CommandBuilder、CustomCmd、OnRcspCallback、DeviceStatusManager 等类的源码位于仓库 SDK 目录,建议结合协议文档阅读
Next
调试技巧与问题排查