RCSP-OTA 协议库(jl_rcsp_ota)
RCSP-OTA 协议库(jl_rcsp_ota_2.1.1.js / jl_rcsp_ota_2.1.1.d.ts)是杰理(Jieli)微信小程序 OTA 方案中的核心协议层:它实现 RCSP(Remote Control Serial Protocol)数据包编解码、命令/回复路由、发送超时重发、设备状态管理与 RCSP 数据处理器(RCSPDataHandler),并向上层 OTA 流程库(jl_ota)提供 RcspImpl 级别的设备操作能力。
Purpose and Scope
本页面向读者完整解释 jl_rcsp_ota 协议库的职责边界、内部结构与工作机理,包括:
- RCSP 数据包(
RcspPacket)的字节结构、命令(Command)/参数(ParamBase)/回复(ResponseBase)三类基类; - 数据透传代理
IOProxy与微信小程序蓝牙通道之间的适配方式; RCSPDataHandler发送队列、超时重发与回复匹配机制;- RCSP-OTA 专用命令集(
CmdReadFileOffset、CmdRequestUpdate、CmdEnterUpdateMode、CmdReadFileBlock、CmdQueryUpdateResult、CmdRebootDevice等)的协议交互流程; - 库的导出 API(
RcspImpl相关导出、日志接口、ab2hex工具等)与配置入口。
本页不覆盖以下内容(属于相邻目录项):
- OTA 流程库(jl_ota):
OTAImpl/RcspOTAManager的状态机、断线重连、进度回调等高层流程编排。本页仅在说明协议库如何被其消费时引用,详见「OTA 流程库」目录页。 - RCSP 认证库(jl_auth):设备鉴权与密钥协商流程。
- 小程序蓝牙接入层:
bluetoothOTAManager.ts/otaWrapper.ts中的wx.*BLE API 封装与页面 UI。 - Demo 页面:
pageCustomCmd等页面级自定义命令演示。
Overview
背景与定位
在微信小程序 OTA 方案中,库依赖分三层(详见 README.md):
| 库文件 | 职责 |
|---|---|
jl_auth_x.x.x | RCSP 认证库:设备鉴权 |
jl_rcsp_ota_x.x.x | RCSP-OTA 协议库:数据包编解码、命令收发、回复匹配、错误处理 |
jl_ota_x.x.x | OTA 流程库:升级流程编排(读取文件标志、查询可升级、传输固件块、查询结果、重启) |
jl_rcsp_ota 是承上启下的协议基础:上层流程库不直接操作蓝牙字节流,而是通过本库的命令对象与回调机制完成一次有状态的 RCSP 会话。它同时向应用层暴露 RcspImpl、Device、CommandBase、RCSPDataHandler 等类型,使 Demo 工程(如 pageCustomCmd 页面)可以直接构造自定义 RCSP 命令并发送。
关键设计意图
- 协议与传输解耦:协议库只负责 RCSP 包的组包/解包与收发逻辑,真正的 BLE 写特征值操作通过
IOProxy.sendDataToDevice由调用方实现(声明文件注释明确:该方法运行在子线程、允许阻塞、回调完整一包 RCSP 数据、调用方需按 MTU 分包)。这让同一套协议库可以复用于 BLE / SPP / USB 三种通信方式(OTAConfig.COMMUNICATION_WAY_BLE=0、SPP=1、USB=2)。 - 同步请求-异步回复模型:所有命令通过
CommandCallback回调结果,RCSPDataHandler内部用SendDataInfo队列 + 定时器实现超时重发(SEND_AGAIN_LIMIT),并依据sn(序号)将设备回复路由回对应的待处理命令。 - 状态机前置检查:命令发送前统一走
RcspImpl的连接状态校验(设备在线、命令对象有效),无效时直接通过CommandCallback.onError返回错误码,避免向离线设备发送无意义数据。
Architecture
flowchart TD
subgraph sg_App["应用层(Demo / 业务页面)"]
App["app.ts / 页面"]
BLEMgr["bluetoothOTAManager.ts"]
OTAWrapper["otaWrapper.ts"]
end
subgraph sg_OtaLib["OTA 流程库 jl_ota"]
OTAImpl["OTAImpl"]
RcspOTAMgr["RcspOTAManager"]
end
subgraph sg_RcspLib["RCSP-OTA 协议库 jl_rcsp_ota"]
Handler["RCSPDataHandler"]
RcspImpl["RcspImpl(设备操作门面)"]
Cmd["命令体系<br/>Command / ParamBase / ResponseBase"]
Parser["BaseCmdParser / RcspPacket"]
DeviceMgr["DeviceMtuManager / DeviceInfo"]
end
subgraph sg_Ble["微信小程序 BLE 通道"]
IOProxy["IOProxy(调用方实现)"]
WXBle["wx.writeBLECharacteristicValue"]
end
App -->|"setLogger / 自定义命令"| RcspImpl
BLEMgr -->|"RCSPProtocol.*"| RcspImpl
OTAWrapper --> RcspImpl
OTAImpl -->|"sendRCSPCommand"| RcspImpl
RcspOTAMgr --> OTAImpl
RcspImpl --> Handler
Handler --> Cmd
Cmd --> Parser
Parser -->|"toData / parseData"| IOProxy
IOProxy -->|"sendDataToDevice"| WXBle
IOProxy -->|"transmitDeviceData / transmitDeviceStatus"| Handler
架构说明:
RcspImpl是协议库对外暴露的设备操作门面:sendRCSPCommand、isDeviceConnected、getUsingDevice、getDeviceInfo、getDeviceInfoManager、addOnRcspCallback/removeOnRcspCallback等操作都汇聚到RCSPDataHandler完成实际收发。RCSPDataHandler是协议核心引擎:维护发送队列(_sendInfoArray)、接收队列(_receiveInfoArray)、超时定时器表(_sendTimeOutIDMap)与解析器(_rcspParser),将IOProxy传入的原始字节流解析为命令/回复对象,再分发给监听者。- 命令体系(
CommandBase/Command/ParamBase/ResponseBase)将每个 RCSP 操作建模为可序列化对象:toData()组包、parseData()解包,上层流程库直接实例化具体命令类(如CmdReadFileOffset)并通过sendRCSPCommand发送。 IOProxy是协议库与小程序 BLE 之间的唯一桥梁:协议库不知道wx.*的存在,bluetoothOTAManager.ts等文件实现IOProxy并把设备上报的字节流/连接状态喂给协议库。
核心类型与数据包结构
RCSP 数据包(RcspPacket)
RcspPacket 是协议库最底层的数据载体,声明见 jl_rcsp_ota_2.1.1.d.ts:
/** Command-RCSP基础数据类型 */
declare class RcspPacket {
static RCSP_HEAD: number[];
static RCSP_END: number;
private _isCommand;
private _isNeedResponse;
private _reserve;
private _opCode;
private _payload?;
private _sn;
isCommand(): boolean;
setCommand(command: boolean): this;
isNeedResponse(): boolean;
setNeedResponse(needResponse: boolean): RcspPacket;
getReserve(): number;
setReserve(reserve: number): RcspPacket;
getOpCode(): number;
setOpCode(opCode: number): RcspPacket;
getSn(): number;
getPayload(): Uint8Array | undefined;
setPayload(payload: Uint8Array): void;
toData(): Uint8Array | null;
parseData(data: Uint8Array): number;
}
一个 RCSP 包的核心字段:帧头 RCSP_HEAD(固定头)、帧尾 RCSP_END、isCommand(命令包 / 回复包)、isNeedResponse(是否需要设备回复)、opCode(操作码,决定命令类型)、sn(序号,用于命令与回复配对)、payload(载荷)。toData() 负责组包为 Uint8Array,parseData() 负责从字节流解析并返回消费长度——这是 RCSPDataHandler 实现粘包/半包处理的基础。
命令三基类
/** Command-基础命令 */
declare class Command<P extends ParamBase, R extends ResponseBase> extends RcspPacket {
private _param;
private _response;
constructor(opCode: number, param: P, response: R | null);
getParam(): P;
getResponse(): R | null;
getSn(): number;
setParam(param: P): void;
setResponse(response: R | null): void;
setSn(sn: number): void;
setStatus(status: number): void;
getStatus(): number;
toData(): Uint8Array | null;
}
/** Command-参数基类 */
declare class ParamBase {
private _sn;
private _basePayload?;
setSn(sn: number): void;
getSn(): number;
getData(): Uint8Array | undefined;
setData(data: Uint8Array): void;
toData(): Uint8Array;
parseData(data: Uint8Array): number;
}
/** Command-回复数据基类 */
declare class ResponseBase {
static STATUS_UNKNOWN: number;
static STATUS_SUCCESS: number;
static STATUS_FAILED: number;
static STATUS_UNKNOWN_CMD: number;
static STATUS_BUSY: number;
static STATUS_NONE_RESOURCE: number;
static STATUS_CRC_ERROR: number;
static STATUS_ALL_DATA_CRC_ERROR: number;
static STATUS_INVALID_PARAM: number;
static STATUS_RESPONSE_DATA_OVER_LIMIT: number;
private _status;
private _sn;
private _payload?;
getStatus(): number;
getSn(): number;
getPayload(): Uint8Array | undefined;
setStatus(status: number): void;
setSn(sn: number): void;
setPayload(payload: Uint8Array): void;
toData(): Uint8Array;
parseData(data: Uint8Array): number;
}
/** Command-回复结果码 */
declare class ResponseResult extends ResponseBase {
static RESULT_OK: number;
static RESULT_FAIL: number;
result: number;
parseData(data: Uint8Array): number;
toData(): Uint8Array;
}
设计意图:
Command<P,R>将「请求参数」与「预期回复」封装为泛型对,getResponse()在收到设备回复后被填充——上层(如jl_ota中的RcspOTAManager)正是通过command.getResponse()?.result/getStatus()判断设备侧执行结果。ResponseBase.STATUS_*是传输层状态码(成功、失败、未知命令、忙、无资源、CRC 错误、参数非法、回复超限等),ResponseResult在其上追加result(RESULT_OK/RESULT_FAIL)作为业务结果码。sn在ParamBase/ResponseBase/RcspPacket三处都存在且相互同步:发送时由RCSPDataHandler分配,回复时按sn匹配。
回调与数据信息
/** Command-命令回调 */
interface CommandCallback<T extends CommandBase> {
onCmdResponse(device: Device, command: T): void;
onError(device: Device, code: number, message: string): void;
}
/** 数据处理-发送数据信息 */
declare class SendDataInfo extends BaseDataInfo {
command: CommandBase;
timeoutMs: number;
callback: CommandCallback<CommandBase> | null;
reSendCount: number;
sendTimestamp: number;
constructor(device: Device, command: CommandBase, timeoutMs: number, callback: CommandCallback<CommandBase> | null);
}
/** 数据处理-接收数据信息 */
declare class ReceiveDataInfo extends BaseDataInfo {
data: Uint8Array;
constructor(device: Device, data: Uint8Array);
}
SendDataInfo 记录了每次发送的完整上下文(命令、超时、回调、已重发次数、发送时间戳),是 RCSPDataHandler 实现「超时重发 + 回复去重」的关键数据结构。
设备与连接状态
declare enum Connection {
CONNECTION_DISCONNECT = 0,
CONNECTION_CONNECTING = 1,
CONNECTION_CONNECTED = 2
}
/** 设备 */
declare class Device {
deviceId: string;
name?: string;
constructor(deviceId: string, name?: string);
equals(o: Device | null): boolean;
}
Device 以 deviceId 标识一台设备,equals() 用于 OTA 流程中「回连后确认还是同一台设备」(jl_ota 中 RcspOTAManager 用它过滤非目标设备的 onRcspInit 事件)。
(文档续写见后续 AppendDoc 调用)
RCSPDataHandler:协议收发引擎
RCSPDataHandler 是协议库的内部引擎,声明见 jl_rcsp_ota_2.1.1.d.ts(声明文件在该行之后继续展开其余成员):
/** 数据处理-RCSP数据处理器 */
declare class RCSPDataHandler {
private SEND_AGAIN_LIMIT;
protected ioProxy: IOProxy;
listener: OnRcspDataListener;
deviceMtuManager: DeviceMtuManager;
private _dataInfoCache;
private _sendInfoArray;
private _receiveInfoArray;
private _rcspParser;
private _isCanHandler;
private _sendTimeOutIDMap;
// ...(sendRCSPCommand / onReceiveDeviceData / 超时重发等成员)
}
内部职责
| 成员 | 作用 |
|---|---|
ioProxy | 数据透传代理(调用方实现),sendDataToDevice 完成真实字节发送 |
listener | OnRcspDataListener:onRcspCommand / onRcspResponse / onError 分发回调 |
deviceMtuManager | MTU 管理(配合 RcspConstant.DEFAULT_PROTOCOL_MTU 调整设备接收能力) |
_sendInfoArray | 待处理发送队列(SendDataInfo),等待设备回复 |
_receiveInfoArray | 接收缓冲,处理粘包/半包 |
_sendTimeOutIDMap | 每个在途命令的超时定时器句柄 |
SEND_AGAIN_LIMIT | 单条命令的最大重发次数,超限后回调超时错误 |
工作机理
- 发送:上层(
RcspImpl.sendRCSPCommand)把CommandBase、超时时间、CommandCallback封装成SendDataInfo入队;分配sn后调用ioProxy.sendDataToDevice(device, command.toData());同时启动超时定时器(默认超时见RcspConstant.DEFAULT_SEND_CMD_TIMEOUT,OTA 流程中常用OTAImpl.WAITING_CMD_TIMEOUT = 20s)。 - 接收:
ioProxy.transmitDeviceData喂入原始字节 →_receiveInfoArray缓冲 →_rcspParser(BaseCmdParser体系)按RCSP_HEAD/RCSP_END切包 →parseData得到CommandBase或回复包。 - 路由:解析结果若是命令包(
isCommand()为真),分发给listener.onRcspCommand(OTA 流程中CmdReadFileBlock/CmdNotifyUpdateFileSize/CmdNotifyADVInfo正是通过该回调被RcspOTAManager拦截处理);若是回复包,按sn在_sendInfoArray中匹配SendDataInfo,取消定时器并回调onCmdResponse。 - 超时重发:定时器到期后,若
reSendCount < SEND_AGAIN_LIMIT则重发并重置定时器;否则以ErrorCode.ERROR_RESPONSE_TIMEOUT回调onError。
这套「队列 + 定时器 + sn 匹配」的设计保证了一次 RCSP 会话中多个命令可以交错发送而不互相串扰,同时为上层提供了统一的重试与超时语义。
RCSP-OTA 命令集
协议库为 OTA 场景定义了整套命令类,它们在 jl_ota 流程库中被按序实例化与发送(证据见 jl_ota_2.1.1.js 中 RcspOTAManager 的实现):
| 命令类 | 方向 | 作用 | 关键参数/回复 |
|---|---|---|---|
CmdGetTargetInfo | 设备→主机 | 上报设备信息(是否双备份、是否需 BootLoader、是否强制升级、bleAddr、receiveMtu) | FLAG_MANDATORY_UPGRADE |
CmdReadFileOffset | 主机→设备 | 读取升级文件偏移(readUpgradeFileFlag) | 回复 FileOffset{offset, len} |
CmdRequestUpdate | 主机→设备 | 查询设备是否可升级(inquiryDeviceCanOTA) | ParamRequestUpdate,回复 result |
CmdEnterUpdateMode | 主机→设备 | 进入升级模式(enterUpdateMode) | 回复 result |
CmdReadFileBlock | 设备→主机 | 设备请求固件数据块(gainFileBlock) | ParamReadFileBlock{offset, len} |
CmdNotifyUpdateFileSize | 设备→主机 | 设备通知文件总大小/已写大小(进度) | totalSize / currentSize |
CmdNotifyADVInfo | 设备→主机 | 设备上报回连广播信息 | 触发 CmdControlADVStream 关闭广播 |
CmdControlADVStream | 主机→设备 | 控制广播流(CTRL_OP_CLOSE 关闭) | CTRL_OP_CLOSE |
CmdChangeCommunicationWay | 主机→设备 | 切换通信方式(BLE/SPP/USB)与重启方式 | ParamCommunicationWay |
CmdQueryUpdateResult | 主机→设备 | 查询升级结果(queryUpdateResult) | 回复 result(见结果码映射) |
CmdRebootDevice | 主机→设备 | 重启设备(rebootDevice) | ParamRebootDevice.OP_REBOOT |
CmdExitUpdateMode | 主机→设备 | 退出升级模式(用于取消升级) | 回复 result |
其中 CmdNotifyUpdateFileSize 的回复会由 RcspOTAManager 主动回填 ResponseBase.STATUS_SUCCESS 并原样返回设备(setCommand(false) 后 sendRCSPCommand),这是对设备通知类命令的 ACK 机制。
升级结果码映射(CmdQueryUpdateResult)
jl_ota 中 queryUpdateResult 的 result 会被映射为 OTAError 错误码(证据见 jl_ota_2.1.1.js):
| 设备 result | 含义 | 映射错误码 |
|---|---|---|
0 | 升级成功 | 触发 rebootDevice → 成功后 onStopOTA |
128 | 需要重新升级(设备仍在升级态) | 重走 readyToReconnectDevice |
1 | 数据校验错误 | ERROR_OTA_DATA_CHECK_ERROR (-102) |
2 | 升级失败 | ERROR_OTA_FAIL (-103) |
3 | 加密密钥不匹配 | ERROR_OTA_ENCRYPTED_KEY_NOT_MATCH (-104) |
4 | 升级文件损坏 | ERROR_OTA_UPGRADE_FILE_ERROR (-105) |
5 | 升级类型错误 | ERROR_OTA_UPGRADE_TYPE_ERROR (-106) |
6 | 长度错误 | ERROR_OTA_LENGTH_OVER (-107) |
7 | Flash 读写错误 | ERROR_OTA_FLASH_IO_EXCEPTION (-108) |
8 | 设备端命令超时 | ERROR_OTA_CMD_TIMEOUT (-109) |
9 | 相同升级文件 | ERROR_OTA_SAME_FILE (-114) |
可升级性检查映射(CmdRequestUpdate)
inquiryDeviceCanOTA 的 result 映射(jl_ota 内常量 T):0=可升级;1=ERROR_OTA_LOW_POWER (-97)(低电量);2=ERROR_OTA_UPDATE_FILE (-98)(升级文件信息错误);3=ERROR_OTA_FIRMWARE_VERSION_NO_CHANGE (-99)(固件版本未变化);4=ERROR_OTA_TWS_NOT_CONNECT (-100)(TWS 未连接);5=ERROR_OTA_HEADSET_NOT_IN_CHARGING_BIN (-101)(耳机未在充电仓内)。
库导出与常量
协议库顶层导出(exports,见 jl_rcsp_ota_2.1.1.js 及 jl_ota_2.1.1.js 的消费方式):
- 核心门面:
RcspImpl(协议操作实现,RcspOTAManager构造时接收)、Device、Connection(CONNECTION_DISCONNECT=0、CONNECTING=1、CONNECTED=2)。 - 命令体系:
CommandBase、Command、ParamBase、ResponseBase、ResponseResult、BaseCmdParser、RcspPacket,以及全部Cmd*/Param*命令类(CmdGetTargetInfo、CmdReadFileOffset、CmdRequestUpdate、CmdEnterUpdateMode、CmdExitUpdateMode、CmdQueryUpdateResult、CmdRebootDevice、CmdChangeCommunicationWay、CmdControlADVStream、CmdReadFileBlock、CmdNotifyUpdateFileSize、CmdNotifyADVInfo等)。 - 处理器与监听:
RCSPDataHandler、IOProxy、OnRcspDataListener、OnRcspCommandListener、OnRcspResponseListener、DeviceMtuManager、SendDataInfo、ReceiveDataInfo。 - 常量与错误:
RcspConstant(DEFAULT_SEND_CMD_TIMEOUT、DEFAULT_PROTOCOL_MTU)、ErrorCode(getErrorDesc1/getErrorDesc2)。 - 工具:
ab2hex(ArrayBuffer 转十六进制字符串)、OTAError(getErrorDesc)。 - 日志:
setLogger(logger)/setLogGrade(grade)与logv/logd/logi/logw/loge五级日志函数(jl_ota库与app.ts都调用setLogger/setLogGrade注入统一日志)。
核心流程:一次 RCSP-OTA 升级的协议交互
下面的时序图展示 jl_rcsp_ota 在完整 OTA 升级中的协议交互(主机侧流程编排由 jl_ota 的 OTAImpl 完成,协议命令收发全部经由本库;证据来自 jl_ota_2.1.1.js 中 RcspOTAManager 对命令的按序调用):
sequenceDiagram
participant App as 小程序应用
participant Rcsp as jl_rcsp_ota (RcspImpl/RCSPDataHandler)
participant OTA as jl_ota (OTAImpl)
participant Dev as 蓝牙设备
App->>Rcsp: setLogger / setLogGrade
App->>OTA: startOTA(OTAConfig, listener)
OTA->>Rcsp: 注册 OnRcspDataListener(addOnRcspCallback)
Dev-->>Rcsp: CmdGetTargetInfo(onRcspInit 透传)
Rcsp-->>OTA: onRcspInit(device, info)
OTA->>Rcsp: sendRCSPCommand(CmdReadFileOffset)
Rcsp->>Dev: RCSP 包(读文件偏移)
Dev-->>Rcsp: FileOffset{offset, len} 回复
Rcsp-->>OTA: onCmdResponse(sn 匹配)
OTA->>Rcsp: sendRCSPCommand(CmdRequestUpdate)
Dev-->>Rcsp: result(0=可升级 / 错误码)
OTA->>Rcsp: sendRCSPCommand(CmdEnterUpdateMode)
Dev-->>Rcsp: result(0=成功)
Dev-->>Rcsp: CmdReadFileBlock{offset, len}(设备主动请求固件块)
Rcsp-->>OTA: onRcspCommand → gainFileBlock
OTA->>Rcsp: sendRCSPCommand(receiveFileBlock: 带 block 数据的回复包)
Rcsp->>Dev: 固件数据块(按 sn 回填 ResponseBase)
Dev-->>Rcsp: CmdNotifyUpdateFileSize{totalSize, currentSize}
Rcsp-->>OTA: notifyUpgradeSize → onProgress
OTA->>Rcsp: sendRCSPCommand(CmdQueryUpdateResult)
Dev-->>Rcsp: result(0=成功)
OTA->>Rcsp: sendRCSPCommand(CmdRebootDevice)
Rcsp->>Dev: 重启命令
OTA-->>App: onStopOTA / onError
关键协议交互点
- 设备主动上行命令:RCSP 协议下,设备可以主动下发命令(如
CmdGetTargetInfo、CmdReadFileBlock、CmdNotifyUpdateFileSize)。RCSPDataHandler解析到命令包后经listener.onRcspCommand通知OTAImpl,后者根据命令类型做出响应:CmdReadFileBlock:从本地固件文件切片(FileOffset{offset, len})并构造回复包(setCommand(false)、回填sn、携带block数据、setStatus(STATUS_SUCCESS))发回设备;CmdNotifyUpdateFileSize:更新进度(100 * currentSize / totalSize,封顶 99.9)并回 ACK;CmdNotifyADVInfo:主机回复CmdControlADVStream(CTRL_OP_CLOSE)关闭广播流,防止回连阶段干扰。
- sn 配对:
RCSPDataHandler为每个下行命令分配sn,设备回复携带相同sn,从而在乱序/多命令场景下正确路由回调。 - 进度推进:
OTAImpl通过W(type, percent)映射升级类型(UPGRADE_TYPE_CHECK_FILE=0/UPGRADE_TYPE_FIRMWARE=1)后回调onProgress(type, percent),最终展示到小程序 UI。
使用示例
1. 注入日志(应用入口)
app.ts 在应用启动时统一注入 RCSP 协议库与 OTA 流程库的日志器与日志级别:
import { setLogger as setOTALogger, setLogGrade as setOTALoggerGrade } from "./lib/jl_lib/jl_ota_2.1.1";
import { setLogger as setRCSPLogger, setLogGrade as setRCSPLoggerGrade } from "./lib/jl_lib/jl_rcsp_ota_2.1.1";
import { setLogger as setAppLogger, setLogGrade as setAppLoggerGrade } from "./lib/log";
来源:app.ts
设计意图:协议库与流程库的日志走同一套五级日志接口(setLogger + setLogGrade),应用层可集中控制日志输出等级,便于 OTA 排障。
2. 在蓝牙管理器中引用协议库
bluetoothOTAManager.ts 通过 import * as RCSPProtocol 获得协议库全部导出,并以此构造 IOProxy 与设备连接管理:
import { OTAWrapper, OTAWrapperOption, OTAWrapperListenner, BluetoothDevice } from "./otaWrapper"
import * as RCSPProtocol from "./jl_lib/jl_rcsp_ota_2.1.1";
otaWrapper.ts 同样引入协议库(见 otaWrapper.ts),说明协议库同时被「蓝牙适配层」与「OTA 包装层」两层消费:适配层实现 IOProxy 完成字节收发,包装层把 RcspImpl 桥接给 jl_ota 的 RcspOTAManager。
3. 页面级自定义命令
Demo 的「自定义命令」页面直接使用协议库构造 RCSP 命令:
import * as RCSPProtocol from "../../../lib/jl_lib/jl_rcsp_ota_2.1.1"
import { formatTime, string2buffer, byteArrayToHex } from "../../../utils/util";
import { loge } from "../../../lib/log";
这展示了协议库的扩展性:业务页面可基于 CommandBase / ParamBase / CommandCallback 构造任意自定义 RCSP 命令,经由 RcspImpl.sendRCSPCommand 与设备交互,无需改动协议库本身。
配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
RcspConstant.DEFAULT_SEND_CMD_TIMEOUT | number | 由库定义 | 协议层默认命令发送超时(毫秒) |
RcspConstant.DEFAULT_PROTOCOL_MTU | number | 由库定义 | 协议层默认 MTU;jl_ota 中 changeReceiveMtu 会将设备 receiveMtu 提升到该值再开始固件传输 |
setLogGrade(grade) | number | 1(库内默认 n=1) | 日志输出级别阈值:logv(1) / logd(2) / logi(3) / logw(4) / loge(5),级别数值大于 n 的日志不输出 |
setLogger(logger) | object | null | 注入日志实现(logv/logd/logi/logw/loge 方法),未注入则日志静默丢弃 |
RCSPDataHandler.SEND_AGAIN_LIMIT | number | 库内定义 | 单条命令最大重发次数 |
OTAImpl.WAITING_CMD_TIMEOUT | number | 20000 | OTA 流程单命令等待超时(毫秒),供上层配置参考 |
OTAImpl.WAITING_DEVICE_OFFLINE_TIMEOUT | number | 6000 | 等待设备断线超时(毫秒) |
OTAImpl.RECONNECT_DEVICE_TIMEOUT | number | 80000 | 回连设备超时(毫秒) |
OTAConfig.COMMUNICATION_WAY_BLE / SPP / USB | number | 0(BLE) | 通信方式枚举,CmdChangeCommunicationWay 的取值来源 |
UpgradeType.UPGRADE_TYPE_CHECK_FILE / FIRMWARE / UNKNOWN | enum | -1/0/1 | 升级类型(文件校验 / 固件升级 / 未知),用于 onProgress 类型参数 |
注:
WAITING_*/RECONNECT_*常量属于jl_ota流程库(jl_ota_2.1.1.js),此处列出是因为它们直接决定协议层命令超时窗口的取值。
API 参考(协议库关键接口)
IOProxy(数据透传代理,调用方实现)
interface IOProxy {
transmitDeviceStatus: (device: Device, status: Connection) => void;
transmitDeviceData: (device: Device, data: Uint8Array) => void;
sendDataToDevice: (device: Device, data: Uint8Array) => boolean;
}
参数与语义:
transmitDeviceStatus(device, status):向协议库上报设备连接状态变化,status取值Connection枚举(断开/连接中/已连接)。transmitDeviceData(device, data):把设备侧收到的原始字节喂给协议库(通常来自wx.onBLECharacteristicValueChange)。sendDataToDevice(device, data):发送一包完整 RCSP 数据;返回boolean表示是否发送成功。按声明注释,该方法运行在子线程、允许阻塞、由调用方按实际 MTU 分包。
RcspImpl(协议操作门面,由协议库实现)
RcspImpl 是 jl_ota 流程库依赖的核心抽象(jl_ota_2.1.1.js 开头即 require("./jl_rcsp_ota_2.1.1.js"),RcspOTAManager 构造参数即 RcspImpl)。其主要方法(由 jl_ota_2.1.1.js 中的调用点归纳):
| 方法 | 说明 |
|---|---|
sendRCSPCommand(device, command, timeoutMs, callback) | 发送一条 RCSP 命令,异步经 CommandCallback.onCmdResponse / onError 回调 |
isDeviceConnected() | 当前设备是否在线(离线时命令直接以 ERROR_DEVICE_OFFLINE 失败) |
getUsingDevice() | 获取当前使用中的 Device |
getDeviceInfo(device) | 获取 DeviceInfo(isSupportDoubleBackup、isNeedBootLoader、mandatoryUpgradeFlag、bleAddr、receiveMtu) |
getDeviceInfoManager() | 获取设备信息管理器(updateDeviceInfo 可修改 receiveMtu 等) |
addOnRcspCallback(listener) / removeOnRcspCallback(listener) | 注册/注销 OnRcspDataListener(onRcspInit、onRcspCommand、onRcspDataCmd、onConnectStateChange、onRcspError、onMandatoryUpgrade、onRcspResponse) |
exitUpdateMode(callback) | 退出升级模式(取消双备份升级时使用) |
OnRcspDataListener(协议事件监听)
interface OnRcspDataListener {
onRcspCommand: (device: Device, command: CommandBase) => void;
onRcspResponse: (device: Device, command: CommandBase) => void;
onError: (device: Device | null, code: number, message: string) => void;
}
onRcspCommand 收到设备上行命令(如 CmdReadFileBlock),onRcspResponse 收到下行命令的回复。jl_ota 的 RcspOTAManager 同时实现了这些回调,并在 onRcspCommand 中完成对 CmdReadFileBlock(回传固件块)、CmdNotifyUpdateFileSize(ACK)、CmdNotifyADVInfo(关闭广播)的处理。
ErrorCode 与 OTAError(错误模型)
declare class ErrorCode {
static ERROR_UNKNOWN: number;
static ERROR_NONE: number;
static ERROR_INVALID_PARAM: number;
static ERROR_DATA_FORMAT: number;
static ERROR_NOT_FOUND_RESOURCE: number;
static ERROR_UNKNOWN_DEVICE: number;
static ERROR_DEVICE_OFFLINE: number;
static ERROR_IO_EXCEPTION: number;
static ERROR_REPEAT_STATUS: number;
static ERROR_RESPONSE_TIMEOUT: number;
static ERROR_REPLY_BAD_STATUS: number;
static ERROR_REPLY_BAD_RESULT: number;
static ERROR_NONE_PARSER: number;
static getErrorDesc1(errorCode: number): string;
static getErrorDesc2(errorCode: number, explain: string): string;
}
核心错误码取值(协议层): ERROR_UNKNOWN=-1、ERROR_INVALID_PARAM=-2、ERROR_DATA_FORMAT=-3、ERROR_UNKNOWN_DEVICE=-32、ERROR_DEVICE_OFFLINE=-33、ERROR_IO_EXCEPTION=-35、ERROR_REPEAT_STATUS=-36、ERROR_RESPONSE_TIMEOUT=-64、ERROR_REPLY_BAD_STATUS=-65、ERROR_REPLY_BAD_RESULT=-66、ERROR_NONE_PARSER=-67。
OTA 域错误码(OTAError,-97 ~ -114): ERROR_OTA_LOW_POWER=-97、ERROR_OTA_UPDATE_FILE=-98、ERROR_OTA_FIRMWARE_VERSION_NO_CHANGE=-99、ERROR_OTA_TWS_NOT_CONNECT=-100、ERROR_OTA_HEADSET_NOT_IN_CHARGING_BIN=-101、ERROR_OTA_DATA_CHECK_ERROR=-102、ERROR_OTA_FAIL=-103、ERROR_OTA_ENCRYPTED_KEY_NOT_MATCH=-104、ERROR_OTA_UPGRADE_FILE_ERROR=-105、ERROR_OTA_UPGRADE_TYPE_ERROR=-106、ERROR_OTA_LENGTH_OVER=-107、ERROR_OTA_FLASH_IO_EXCEPTION=-108、ERROR_OTA_CMD_TIMEOUT=-109、ERROR_OTA_IN_PROGRESS=-110、ERROR_OTA_COMMAND_TIMEOUT=-111、ERROR_OTA_RECONNECT_DEVICE_TIMEOUT=-112、ERROR_OTA_USE_CANCEL=-113、ERROR_OTA_SAME_FILE=-114。
错误回调统一为 (device, code, message) 三元组,getErrorDesc2(code, explain) 拼接出带上下文的描述(jl_ota 中所有 onError 都通过它格式化错误信息)。
失败模式、边界情况与并发
失败模式
- 设备离线:
RcspImpl命令入口先校验isDeviceConnected(),离线直接ERROR_DEVICE_OFFLINE,不产生无线流量。 - 回复状态异常:设备回复
status != STATUS_SUCCESS时,CommandCallback.onError收到ERROR_REPLY_BAD_STATUS;result != RESULT_OK时收到ERROR_REPLY_BAD_RESULT(jl_ota的IHandleResult回调封装实现此判定)。 - 超时:命令等待超时(协议层
DEFAULT_SEND_CMD_TIMEOUT,OTA 流程中WAITING_CMD_TIMEOUT=20s)且重发次数用尽 →ERROR_RESPONSE_TIMEOUT;OTA 各阶段另有独立超时(等待断线 6s、回连 80s、命令 20s)。 - 解析失败:无匹配解析器时返回
ERROR_NONE_PARSER,数据格式错误返回ERROR_DATA_FORMAT,保证异常字节流不会崩溃协议栈。
边界情况
- 固件文件为空/越界:
OTAImpl.startOTA校验updateFileData为空时直接ERROR_INVALID_PARAM;readUpgradeFileFlag读取超过文件长度时返回ERROR_INVALID_PARAM("Read Data over Limit")。 - 重复升级:
isOTA()为真时再次startOTA返回ERROR_OTA_IN_PROGRESS。 - 单备份固件不可中断:设备不支持双备份(
isSupportDoubleBackup=false)时cancelOTA()直接返回false并提示 "device is single flash ota, so ota progress cannot be interrupted"——这是由 Flash 单备份物理特性决定的硬限制。 - 设备主动下发重复命令:
RcspOTAManager对CmdReadFileBlock按sn+ 时间窗(minSameCmdE5Time=50ms)去重,防止设备重传导致重复切块。
并发与一致性
- 命令交错:
RCSPDataHandler以sn区分在途命令,支持多条命令并发在途;CmdReadFileBlock请求被暂存到vt数组,receiveFileBlock按offset/len精确匹配并出队,防止错位回传。 - 进度一致性:
notifyUpgradeSize维护totalSize/currentSize,进度计算100*currentSize/totalSize封顶 99.9(100% 留给onStopOTA),避免 UI 提前显示完成。 - 定时器生命周期:每次命令/阶段切换都会
clearTimeout旧定时器(F()/V()/M()三组清理),防止 OTA 结束后残留回调触发误报。
性能与运维注意
- MTU 协商:
jl_ota在需要 BootLoader 时先changeReceiveMtu()把设备receiveMtu提到RcspConstant.DEFAULT_PROTOCOL_MTU,再进入固件块传输,减少分包次数、提升吞吐。协议层sendDataToDevice的回调数据是完整 RCSP 包,实际 BLE 分包由调用方(bluetoothOTAManager)完成。 - 日志开销:协议库提供
logv/logd/logi/logw/loge五级日志且带等级门控(setLogGrade),生产环境建议调高等级(如n=3,仅输出logi及以上),避免 BLE 高频数据(固件块传输)刷屏拖慢小程序。 - 固件数据缓存:
OTAImpl持有整个updateFileData(Uint8Array),readUpgradeFileFlag按FileOffset切片发送;大固件文件会占用较多小程序内存,建议按官方指导控制单次升级文件大小。
扩展点
- 自定义 RCSP 命令:基于
Command<P,R>/ParamBase/ResponseBase派生新命令类并注册解析器(BaseCmdParser体系),即可扩展协议命令集——Demo 的pageCustomCmd页面正是这一能力的展示。 - 通信方式扩展:
IOProxy.sendDataToDevice完全由调用方实现,同一协议库可对接 BLE(COMMUNICATION_WAY_BLE=0)、SPP(=1)、USB(=2),切换时通过CmdChangeCommunicationWay通知设备。 - 协议事件订阅:
addOnRcspCallback允许注册多个监听者;jl_ota的RcspOTAManager与业务层可同时监听,各自处理关心的命令(OTA 相关 vs 自定义命令)。 - 日志接入:
setLogger可注入任意日志实现,便于对接小程序日志上报或远程排障系统。
Related Links
- README.md(库清单与快速开始)
- RCSP-OTA 协议库声明文件 jl_rcsp_ota_2.1.1.d.ts
- RCSP-OTA 协议库实现 jl_rcsp_ota_2.1.1.js
- OTA 流程库 jl_ota_2.1.1.js(协议库的消费方,含 RcspOTAManager)
- OTA 流程库声明 jl_ota_2.1.1.d.ts
- 应用入口 app.ts(setLogger 注入示例)
- 蓝牙管理器 bluetoothOTAManager.ts(IOProxy 适配示例)
- OTA 包装层 otaWrapper.ts
- 自定义命令页面 pageCustomCmd.ts(自定义命令示例)
相邻主题请参见:OTA 流程库(jl_ota)、RCSP 认证库(jl_auth)、蓝牙连接管理(bluetoothOTAManager) 等目录页。