JL_Assist 自定义连接
JL_Assist 是杰理(JieLi)iOS 蓝牙 SDK 提供的「自定义连接」辅助类:当 App 自行管理 CBCentralManager 扫描、连接与 CBPeripheral 服务/特征发现时,将系统 BLE 回调事件转交给 JL_Assist,由它完成 RCSP 协议数据封装、设备认证(配对)握手以及写数据分发,从而把"自定义连接"无缝对接到 JL_ManagerM 命令中心与 JL_OTAManager OTA 能力之上。
Purpose and Scope
本文档面向需要自行控制蓝牙连接流程(自定义扫描、过滤、连接策略)的开发者,完整说明:
JL_Assist的职责边界与设计意图(为什么需要它、它与默认连接模式的区别);- 核心属性(服务号、RCSP 读写特征、认证密钥、MTU 控制、数据日志)的语义与配置方式;
- 从
CBCentralManager回调到JL_Assist方法的完整映射关系与配对/认证握手流程; - 数据写出路径(
JL_AssistDelegate回调)以及如何与JL_OTAManager集成完成 OTA; - 官方 Demo(
JLAssistOTADemo)中的初始化、重连、超时与重试示例; - 配置项、API 参考、失败模式与并发注意事项。
本页不覆盖以下内容(属于同级页面的范畴):JL_OTAManager 的完整 OTA 状态机与命令细节、默认 SDK 托管连接的实现、JLAdvParse 广播解析的全部规则。文中仅在这些主题与本页相关时做"For X, see Y"式指引。
Overview
在杰理 SDK 的常规用法中,JL_ManagerM 会接管整条 BLE 链路(扫描、连接、发现、收发)。但在实际产品中,很多 App 需要自定义连接策略:例如按厂商数据(Manufacturer Data)中的 MAC 地址过滤设备、维护自己的设备列表 UI、控制重连时机与超时。此时 SDK 提供了一个折中方案:
- 连接管理交给 App:App 持有
CBCentralManager/CBPeripheral,自行完成扫描、连接、服务发现、特征订阅; - 协议与认证交给
JL_Assist:App 把 CoreBluetooth 委托回调"喂"给JL_Assist的assist*系列方法,由它负责 RCSP 协议帧封装、设备认证握手(mAuthKey/mAuthEnable)与数据写入调度; - 写数据通过代理回流:
JL_Assist不直接持有CBPeripheral的写入权,而是通过JL_AssistDelegate的assistDidWriteData:回调把待发送数据交回 App,由 App 调用CBPeripheral writeValue:...真正写蓝牙。这使得数据链路完全可插拔、可监控。
同时,JL_Assist 内部持有一个 JL_ManagerM(mCmdManager,命令中心),因此上层业务(如 JL_OTAManager)可以像使用默认连接一样使用命令通道,感知不到连接层是自定义的。一句话概括:JL_Assist 是"自定义连接"与"SDK 命令体系"之间的适配层。
flowchart TD
subgraph sg_App["App 层(自定义连接)"]
App["App 业务 / 设备列表 UI"]
BleManager["BleManager(扫描/连接/重连管理)"]
ScanFilter["扫描过滤(UUID / MAC)"]
end
subgraph sg_CB["CoreBluetooth 系统层"]
CBCentral["CBCentralManager"]
CBPeri["CBPeripheral"]
CBChar["CBCharacteristic"]
end
subgraph sg_Assist["JL_BLEKit 协议层"]
Assist["JL_Assist(RCSP 封装 + 认证握手)"]
Delegate["JL_AssistDelegate"]
CmdMgr["JL_ManagerM(命令中心)"]
end
subgraph sg_OTA["OTA 能力层"]
OTAManager["JL_OTAManager"]
end
App --> BleManager
BleManager --> ScanFilter
BleManager -->|"scan / connect"| CBCentral
CBCentral --> CBPeri
CBPeri --> CBChar
BleManager -->|"BLE 回调事件"| Assist
Assist -->|"assistDidWriteData:"| Delegate
Delegate -->|"bleWrite(data)"| BleManager
BleManager -->|"writeValue"| CBPeri
Assist --> CmdMgr
CmdMgr --> OTAManager
OTAManager -->|"otaDataSend:"| Delegate
上图展示了两条关键路径:
- 上行(读)路径:
BleManager收到的订阅通知数据 → 交给JL_Assist解析(assistUpdateValueForCharacteristic:)→ 进入JL_ManagerM命令中心 →JL_OTAManager得到响应; - 下行(写)路径:
JL_OTAManager产生的 OTA 数据 → 命令中心 →JL_Assist封装为 RCSP 帧 →assistDidWriteData:回调 → App 的bleWrite:→CBPeripheral writeValue。
JL_Assist 处于协议层与系统层的交汇点,是自定义连接模式下数据进出的唯一咽喉,因此它的状态管理(认证、MTU、就绪通知)直接决定整条链路是否可用。
核心机制
职责边界:连接归 App,协议归 SDK
JL_Assist 的头文件定义非常克制,它既不是 CBCentralManager 的封装,也不直接调用 CBPeripheral 的写方法,而是通过一组"回调注入"方法(assist*)与一个写回调代理(JL_AssistDelegate)把协议层与连接层解耦:
@protocol JL_AssistDelegate <NSObject>
-(void)assistDidWriteData:(NSData *_Nonnull)data;
@end
typedef void(^JL_Assist_BK)(BOOL isPaired);
@class JL_ManagerM;
@interface JL_Assist : NSObject
@property(strong,nonatomic)JL_ManagerM *mCmdManager; //命令中心
@property(strong,nonatomic)NSString *mService; //服务号
@property(strong,nonatomic)NSString *mRcsp_W; //特征:RCSP写
@property(strong,nonatomic)NSString *mRcsp_R; //特征:RCSP读
...
@property(assign,nonatomic)id<JL_AssistDelegate> __nullable mDelegate;
...
@end
Source: JL_Assist.h
设计意图:
assistDidWriteData:回调意味着"写蓝牙"永远是 App 的职责。SDK 只负责生成正确的 RCSP 字节流,App 负责把字节真正送进CBPeripheral。这让 App 可以统一在BleManager里做发送队列、日志、节流,也便于在调试时打印裸数据(mLogData)。JL_Assist_BK回调(BOOL isPaired)用于异步告知认证/配对结果,后续所有命令都必须等待YES之后才可发送,这是整个自定义连接流程中最重要的时序约束。
核心属性语义
| 属性 | 类型 | 含义 |
|---|---|---|
mCmdManager | JL_ManagerM * | 命令中心。JL_Assist 在内部完成 RCSP 封装后把命令交给它,上层 JL_OTAManager 通过它收发指令 |
mService | NSString * | GATT 服务号(UUID 字符串),例如杰理设备服务 AE00 |
mRcsp_W | NSString * | RCSP 写特征 UUID(App 向设备下发指令的特征) |
mRcsp_R | NSString * | RCSP 读/通知特征 UUID(设备主动上报的特征,需订阅 notify) |
mAuthKey / mAuthEnable | NSData * / BOOL | 设备认证密钥与开关。启用后连接建立即执行认证握手,认证通过才允许收发业务命令 |
mPairKey / mNeedPaired | NSData * / BOOL | 旧版"握手(配对)秘钥"接口,头文件中标记为 deprecated("Use mAuthKey/mAuthEnable instead"),新代码应迁移到认证接口 |
mLogData | BOOL | 是否打印裸数据(调试用),开启后可观察 RCSP 帧收发 |
mLimitMtu | NSInteger | 在最大 MTU 基础上减少的数据量,默认 40,用于给系统协议栈预留余量 |
mMaxMtu | NSInteger | 最大 MTU。头文件注释明确指出初始化值不一定准确,应使用 [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse] 在实际连接后再获取 |
mRcspPeripheral / mRcspWrite / mRcspRead | CBPeripheral * / CBCharacteristic * | 连接建立后由 assistDiscoverCharacteristicsForService:Peripheral: 填充的当前链路对象 |
mBleName | NSString * | 设备名字,用于日志与重连识别 |
mDelegate | id<JL_AssistDelegate> | 写数据代理,实现后所有发出的数据都走回调发送 |
Source: JL_Assist.h
BLE 回调 → JL_Assist 方法映射
JL_Assist 的设计要求 App 在 CBCentralManager / CBPeripheral 的委托方法中逐一对号入座地调用 assist* 方法。头文件为每个方法都标注了"应在哪个系统回调中执行":
| 系统回调 | 调用 JL_Assist 方法 | 作用 |
|---|---|---|
centralManagerDidUpdateState: | assistUpdateState: | 把蓝牙状态同步给 SDK |
centralManager:didDisconnectPeripheral:error: | assistDisconnectPeripheral: | 通知 SDK 链路断开,清理连接状态 |
peripheral:didDiscoverServices: | assistDiscoverCharacteristicsForService:Peripheral: | 开始发现 RCSP 读写特征 |
peripheral:didUpdateNotificationStateForCharacteristic:error: | assistUpdateCharacteristic:Peripheral:Result: | 通知特征订阅完成后触发认证握手,结果通过 JL_Assist_BK 返回 |
peripheral:didUpdateValueForCharacteristic:error: | assistUpdateValueForCharacteristic: | 把设备上报数据送入 SDK 解析 |
peripheral:didWriteValueForCharacteristic:error: 与 peripheral:didIsReadyForWrite:error: | assistDidReady | 通知 SDK 写缓冲就绪,可以继续发送下一包 |
Source: JL_Assist.h
这里有一个容易被忽略的关键点:assistDidReady 同时挂在写完成与 didIsReadyForWrite 两个系统回调上,这是因为 iOS 对 CBCharacteristicWriteWithoutResponse 写入有系统级背压(buffer 满时系统会暂停发送并回调 didIsReadyForWrite)。SDK 借此实现"逐包确认、按 MTU 分片"的流控,App 不应在未收到 assistDidReady 时连续灌入大量数据。
核心流程
连接与认证时序
sequenceDiagram
participant App as App / BleManager
participant CB as CBCentralManager
participant P as CBPeripheral
participant A as JL_Assist
participant D as JL_AssistDelegate
App->>CB: scanForPeripherals
CB-->>App: didDiscover(按 UUID / MAC 过滤)
App->>CB: connect(peripheral)
CB-->>App: didConnect
App->>P: discoverServices
P-->>App: didDiscoverServices
App->>A: assistDiscoverCharacteristicsForService:Peripheral:
App->>P: discoverCharacteristics
P-->>App: didDiscoverCharacteristics
App->>P: setNotifyValue(YES, mRcsp_R)
P-->>App: didUpdateNotificationState
App->>A: assistUpdateCharacteristic:Peripheral:Result:
A->>A: 认证握手(mAuthEnable 时)
A-->>App: JL_Assist_BK(isPaired)
Note over App,D: 认证通过后进入正常收发
App->>P: writeValue(OTA/命令数据)
P-->>App: didWriteValue / didIsReadyForWrite
App->>A: assistDidReady
A-->>D: assistDidWriteData:(data)
D->>App: bleWrite(data)
流程要点:
- 扫描过滤:Demo 中
startScan只扫描 3 秒后自动stopScan;发现回调里同时支持两种重连目标——reconnectUUID(按peripheral.identifier匹配)与reconnectMac(按kCBAdvDataManufacturerData中的 MAC 与JLAdvParse.otaBleMacAddress匹配)。 - 特征订阅是认证的触发点:
assistUpdateCharacteristic:Peripheral:Result:的Result块是JL_Assist_BK,返回isPaired。只有收到YES后业务代码才应发送 OTA 命令;NO表示认证失败,应断开并提示用户。 - 写路径是"回调闭环":SDK 需要发送的任何数据都以
assistDidWriteData:交还 App,App 写入CBPeripheral后,系统回调再触发assistDidReady,SDK 才知道可以继续下一包。如果 App 忘记实现mDelegate,链路将完全静默——数据不会发出,也不会有报错,这是集成时最常见的坑。
与 JL_OTAManager 的集成
自定义连接模式下,JL_Assist 通过 mCmdManager(JL_ManagerM)向 JL_OTAManager 提供命令通道,因此 OTA 流程对上层完全透明。官方 Demo(JLAssistOTADemo)给出的标准调用顺序为:
- 初始化
JL_OTAManager单例并绑定delegate; - 完成自定义连接 + 特征订阅 + 认证(上文流程)后,把
CBPeripheral的 UUID 与名称写入otaManager.mBLE_UUID/mBLE_NAME,调用noteEntityConnected()通知 OTA 管理器设备已就绪,再调用cmdTargetFeature()查询设备能力; - 用户点击升级时调用
cmdOTAData(data)下发固件数据; - OTA 管理器在需要发送数据时通过
otaDataSend(_ data: Data)回调回到 App,App 再调用BleManager.shared.assistManager.bleWrite(data)走JL_Assist写路径; - 取消升级调用
cmdOTACancelResult,断开连接调用noteEntityDisconnected()。
flowchart LR
subgraph sg_OTAFlow["OTA 集成链路"]
OTA["JL_OTAManager"] -->|"cmdOTAData / cmdTargetFeature"| CMD["JL_ManagerM 命令中心"]
CMD -->|"RCSP 封装"| ASSIST["JL_Assist"]
ASSIST -->|"assistDidWriteData:"| BLE["BleManager.bleWrite"]
BLE -->|"writeValue"| DEV["设备"]
DEV -->|"notify 数据"| BLE --> ASSIST --> CMD --> OTA
end
注意图中 JL_OTAManager 的发送回调目标与 JL_Assist 的写回调目标必须指向同一个 App 写入口(BleManager),否则 OTA 数据会发不出去或乱序。
Usage Examples
示例一:JL_Assist 初始化与参数配置(Swift)
以下代码摘自官方 Demo 中 BleManager 单例的初始化:它一次性配置了配对开关、服务号与读写特征 UUID。注意 mNeedPaired 是旧版属性(头文件中已标记 deprecated),新版应改用 mAuthEnable / mAuthKey:
private override init() {
super.init()
assistManager.mNeedPaired = true
assistManager.mService = SERVICE_UUID
assistManager.mRcsp_W = CHARACTERISTIC_WRITE
assistManager.mRcsp_R = CHARACTERISTIC_NOTIFY
JLLogManager.logLevel(.DEBUG, content: "BleManager init")
}
Source: OTA 升级开发示例(JL_Assist 自定义蓝牙连接).md
示例二:按 UUID / MAC 重连与超时控制(Swift)
自定义连接的价值在于可控的重连策略。Demo 分别提供"按 UUID 重连"与"按 MAC 重连"两条路径,并用 10 秒定时器做超时兜底:
// 通过 UUID 重连设备并启动超时计时
func reConnectWithUUID(uuid: String) {
reconnectUUID = uuid
reconnectMac = nil
JLLogManager.logLevel(.DEBUG, content: "reConnectWithUUID: \(uuid)")
startScan()
startTimeout()
}
// 通过 MAC 地址重连设备并启动超时计时
func reConnectWithMac(mac: String) {
reconnectUUID = nil
reconnectMac = mac
JLLogManager.logLevel(.DEBUG, content: "reConnectWithMac: \(mac)")
startScan()
startTimeout()
}
// 超时控制
@objc private func timeoutHandler() {
timerCount += 1
if timerCount >= maxCount {
timer?.invalidate()
timer = nil
timerCount = 0
JLLogManager.logLevel(.ERROR, content: "连接超时")
}
}
private func startTimeout() {
maxCount = 10
timerCount = 0
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timeoutHandler), userInfo: nil, repeats: true)
timer?.fire()
}
Source: OTA 升级开发示例(JL_Assist 自定义蓝牙连接).md
示例三:扫描回调中的重连目标匹配(Swift)
扫描发现设备时,BleManager 依据预先设置的重连目标做匹配:UUID 直比,MAC 则借助 JLAdvParse.otaBleMacAddress 从广播的 Manufacturer Data 中解析比对。匹配成功后立即 stopScan 并连接:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if peripheral.name != nil {
JLLogManager.logLevel(.DEBUG, content: "发现设备: \(peripheral.name!)")
discoverPeripherals.removeAll(where: { $0.identifier == peripheral.identifier })
discoverPeripherals.append(peripheral)
discoverPeripheralsSubject.accept(discoverPeripherals)
}
if reconnectUUID != nil {
if peripheral.identifier.uuidString == reconnectUUID {
reconnectUUID = nil
stopScan()
connect(peripheral: peripheral)
return
}
}
if reconnectMac != nil {
guard let advData = advertisementData["kCBAdvDataManufacturerData"] as? Data else { return }
guard let mac = reconnectMac else { return }
if JLAdvParse.otaBleMacAddress(mac, isEqualToCBAdvDataManufacturerData: advData) {
stopScan()
reconnectMac = nil
connect(peripheral: peripheral)
return
}
}
}
Source: OTA 升级开发示例(JL_Assist 自定义蓝牙连接).md
示例四:OTA 异常与重试策略(Objective-C)
JL_OTAManager 的 delegate 回调中,JL_OTAResultReconnect* 系列表示需要重连(分别对应按 UUID、按 MAC 重连),JL_OTAResultFailCmdTimeout 表示命令超时,Demo 采用"最多重试 3 次、间隔递增"的策略:
- (void)otaUpgradeResult:(JL_OTAResult)result Progress:(float)progress {
switch (result) {
case JL_OTAResultReconnect:
case JL_OTAResultReconnectUpdateSource: {
NSString *uuid = self.otaManager.mBLE_UUID;
[[BleManager shared] reConnectWithUUID:uuid];
break;
}
case JL_OTAResultReconnectWithMacAddr: {
NSString *mac = self.otaManager.bleAddr;
[[BleManager shared] reConnectWithMac:mac];
break;
}
case JL_OTAResultFailCmdTimeout: {
if (self.retryCount < 3) {
self.retryCount += 1;
NSTimeInterval delay = (NSTimeInterval)self.retryCount;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.otaManager cmdTargetFeature];
});
}
break;
}
default:
break;
}
}
Source: OTA 升级开发示例(JL_Assist 自定义蓝牙连接).md
示例五:Swift 侧 OTA 标准调用流程
final class OTAExampleViewController: UIViewController, JL_OTAManagerDelegate {
private let otaManager = JL_OTAManager.getOTAManager()
override func viewDidLoad() {
super.viewDidLoad()
otaManager.delegate = self
BleManager.shared.startScan()
}
private func onPeripheralReady(_ peripheral: CBPeripheral) {
otaManager.mBLE_UUID = peripheral.identifier.uuidString
otaManager.mBLE_NAME = peripheral.name ?? ""
otaManager.noteEntityConnected()
otaManager.cmdTargetFeature()
}
private func startUpgrade(with data: Data) {
otaManager.cmdOTAData(data)
}
func otaUpgradeResult(_ result: JL_OTAResult, progress: Float) {
print("升级状态:\(result) 进度:\(progress)")
}
func otaDataSend(_ data: Data) {
BleManager.shared.assistManager.bleWrite(data) // 通过 Assist 写入
}
}
Source: OTA 升级开发示例(JL_Assist 自定义蓝牙连接).md
该示例清晰地展示了自定义连接与 OTA 之间的契约:App 只负责三件事——连接设备、把 otaDataSend: 的数据写出去、把 delegate 回调转给 UI,其余协议细节全部由 JL_Assist + JL_ManagerM + JL_OTAManager 消化。
Configuration Options
JL_Assist 的全部配置通过属性完成,无需额外配置文件。下表汇总了每个选项的类型、默认值与行为说明(默认值以头文件注释与 Demo 为准):
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mService | NSString * | 无(必填) | GATT 服务号,必须与设备广播/服务一致,否则无法发现 RCSP 特征 |
mRcsp_W | NSString * | 无(必填) | RCSP 写特征 UUID,App 下发指令用 |
mRcsp_R | NSString * | 无(必填) | RCSP 读/通知特征 UUID,设备上报用,需 setNotifyValue:YES 订阅 |
mAuthEnable | BOOL | NO(需按产品开启) | 是否启用设备认证;开启后连接建立即执行认证握手 |
mAuthKey | NSData * | nil | 设备认证密钥,与 mAuthEnable 配套使用 |
mNeedPaired | BOOL | — | ⚠️ 已废弃(deprecated),请迁移至 mAuthEnable |
mPairKey | NSData * | — | ⚠️ 已废弃(deprecated),请迁移至 mAuthKey |
mLimitMtu | NSInteger | 40 | 在最大 MTU 基础上预留的余量,防止分片越界 |
mMaxMtu | NSInteger | 初始化值不准确 | 实际应取 [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse] |
mLogData | BOOL | NO | 是否打印 RCSP 裸数据,调试用 |
mDelegate | id<JL_AssistDelegate> | nil | 写数据回调代理;不设置则数据无法发出 |
mBleName | NSString * | nil | 设备名,日志与重连识别用 |
API Reference
协议 JL_AssistDelegate
-(void)assistDidWriteData:(NSData *_Nonnull)data;
说明:JL_Assist 需要下发的所有数据(RCSP 帧)都会通过此方法交给 App。App 应在此方法内调用 CBPeripheral writeValue:forCharacteristic:type:CBCharacteristicWriteWithoutResponse 真正写入蓝牙。
参数:
data(NSData *):待发送的完整 RCSP 数据包(已按 MTU 分片)。
注意:这是单向通知回调,无返回值。若 App 需要发送队列或节流,应在此处实现。头文件注释明确说明:实现此代理后,发出的数据都走回调发送,内部不再处理。
Source: JL_Assist.h
JL_Assist_BK 类型
typedef void(^JL_Assist_BK)(BOOL isPaired);
说明:认证/配对结果回调。YES 表示配对成功、可以发送业务命令;NO 表示配对失败,应断开连接。
Source: JL_Assist.h
-(void)assistUpdateState:(CBManagerState)state
说明:在 centralManagerDidUpdateState: 中调用,将系统蓝牙开关状态同步给 SDK,SDK 据此维护内部状态机。
参数:
state(CBManagerState):系统蓝牙状态。
Source: JL_Assist.h
-(void)assistDisconnectPeripheral:(CBPeripheral *)peripheral
说明:在 centralManager:didDisconnectPeripheral:error: 中调用,通知 SDK 链路已断开、清理 RCSP 会话状态。断开后业务层应停止发送,等待重连流程。
参数:
peripheral(CBPeripheral *):断开的设备。
Source: JL_Assist.h
-(void)assistDiscoverCharacteristicsForService:(CBService*)service Peripheral:(CBPeripheral*)peripheral
说明:在 peripheral:didDiscoverServices: 中找到目标服务后调用,SDK 将依据 mService / mRcsp_W / mRcsp_R 在服务内定位 RCSP 特征。调用后 App 应继续执行 discoverCharacteristics:forService:。
参数:
service(CBService *):已发现的服务;peripheral(CBPeripheral *):当前设备。
Source: JL_Assist.h
-(void)assistUpdateCharacteristic:(nonnull CBCharacteristic *)characteristic Peripheral:(CBPeripheral*)peripheral Result:(JL_Assist_BK)result
说明:在 peripheral:didUpdateNotificationStateForCharacteristic:error: 中调用。当 mRcsp_R 特征订阅成功(notification 开启)后调用此方法,SDK 随即执行认证/配对握手;result 回调返回 isPaired,用于告知 App 是否可以开始业务通信。
参数:
characteristic(CBCharacteristic *):发生通知状态变化的特征;peripheral(CBPeripheral *):当前设备;result(JL_Assist_BK):认证结果回调(YES配对成功 /NO配对失败)。
Source: JL_Assist.h
-(void)assistUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
说明:在 peripheral:didUpdateValueForCharacteristic:error: 中调用,把设备 notify 上来的数据送入 SDK 解析。这是上行数据唯一入口,漏调会导致 SDK 收不到任何设备响应(如 cmdTargetFeature 无回包)。
参数:
characteristic(CBCharacteristic *):数据更新的特征。
Source: JL_Assist.h
-(void)assistDidReady
说明:同时在 peripheral:didWriteValueForCharacteristic:error: 与 peripheral:didIsReadyForWrite:error: 中调用,通知 SDK 写缓冲已就绪、可继续发送下一包。这是 RCSP 分片流控的"放行信号",请勿遗漏。
Source: JL_Assist.h
失败模式、边界情况与并发注意
认证失败(isPaired == NO)
assistUpdateCharacteristic:Peripheral:Result: 的 JL_Assist_BK 回调返回 NO 表示配对/认证失败。此时不应发送任何业务命令,否则设备端会丢弃或返回错误。建议处理:断开连接 → 清理状态 → 提示用户设备不匹配或密钥过期。旧版 mNeedPaired/mPairKey 与新版 mAuthEnable/mAuthKey 混用时可能出现认证行为不一致,务必统一迁移到新接口。
代理未实现导致链路静默
JL_Assist 的写数据完全依赖 mDelegate 的 assistDidWriteData:。若 App 忘记设置代理,SDK 内部"不发也不报错",现象表现为:连接成功、订阅成功、认证成功,但设备收不到任何指令。这是自定义连接模式最高频的集成错误,排查时应先确认 mDelegate 已赋值且 assistDidWriteData: 内真正调用了 writeValue。
断线处理与重连
assistDisconnectPeripheral: 必须在 didDisconnectPeripheral 回调中调用,否则 SDK 内部会残留旧链路状态,重连后可能出现串包或命令无响应。Demo 的重连策略是双轨制:
JL_OTAResultReconnect/JL_OTAResultReconnectUpdateSource→reConnectWithUUID:JL_OTAResultReconnectWithMacAddr→reConnectWithMac:(解析 Manufacturer Data 中的 MAC)
并配套 10 秒超时定时器(maxCount = 10,每秒 tick),超时后打印"连接超时"并清理。重连目标匹配成功后立即 stopScan() 再 connect(),避免重复连接。
命令超时与重试
OTA 命令超时(JL_OTAResultFailCmdTimeout)在 Demo 中采用"最多 3 次、延迟按次数递增(1s/2s/3s)"的重试策略,重试内容为 cmdTargetFeature(重新查询设备能力)。重试应在主线程用 dispatch_after 调度,避免在 BLE 回调线程做延时操作。
MTU 边界
mLimitMtu默认 40:在系统返回的最大 MTU 基础上预留余量。若设备侧 MTU 协商值较小而mMaxMtu配置过大,分片可能超过设备接收能力导致丢包;- 头文件明确提示
mMaxMtu初始化值不一定准确,应在连接建立后用[peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse]重新获取; - 写入必须使用
CBCharacteristicWriteWithoutResponse类型(SDK 依赖didIsReadyForWrite做背压流控),若误用WithResponse会导致流控失效。
并发与线程注意
- CoreBluetooth 委托回调默认在主线程(若在非主队列初始化
CBCentralManager则在其指定队列)。JL_Assist的assist*方法应在与系统回调相同的线程上调用,避免跨线程修改内部状态; - 写入节奏由
assistDidReady控制,App 不应在收到该信号前连续写入多个大包;iOS 对WriteWithoutResponse有内部 buffer 限制,违反背压会造成数据被系统丢弃; - 扫描 3 秒自动停止(Demo 默认),防止长时间扫描耗电与回调风暴;
discoverPeripherals去重使用removeAll(where: { $0.identifier == ... })后追加,保证列表唯一。
性能与运维建议
- 日志:调试期开启
mLogData = YES打印 RCSP 裸数据,配合JLLogManager.logLevel可快速定位"发送了但设备没回"的问题;上线前关闭以减小开销; - MTU 优化:OTA 大文件传输性能直接受 MTU 影响,建议连接后动态读取实际 MTU 并回填
mMaxMtu,让 SDK 按更大的分片发送; - 发送节流:若 App 需要自行排队发送(例如多业务并发写),应在
assistDidWriteData:中做 FIFO 队列 + 单飞(同一时刻只有一个 in-flight 包),避免与 SDK 内部流控冲突; - 断线感知:把
assistDisconnectPeripheral:与 UI 状态机联动(如"升级中"态禁止重连 UI 操作),并在 OTA 阶段对JL_OTAResultReconnect*自动触发重连,减少用户手动干预。
扩展点
JL_Assist 的设计天然提供了三个扩展面:
- 连接策略扩展:
BleManager层可自由实现任何过滤/重连逻辑(UUID、MAC、广播数据),只需保证最终把系统回调正确转发给assist*方法; - 写通道扩展:通过
JL_AssistDelegate可以插入自己的发送队列、加密(在assistDidWriteData:中先处理再写入)、日志统计或流量整形; - 命令层扩展:
mCmdManager(JL_ManagerM)暴露给上层,任何基于 RCSP 的业务(OTA、EQ、闹钟等)都可以复用同一条自定义连接,无需重复实现协议封装。
测试关注点
官方 Demo 以文档示例形式给出了可复现的验收路径(见 JLAssistOTADemo),覆盖以下场景,可作为回归测试清单:
- 扫描 → 连接 → 服务/特征发现 → 订阅 → 认证成功的完整握手;
JL_Assist_BK返回NO(认证失败)时业务命令被拦截;assistDidReady缺失时发送被阻塞(验证流控);- 断线后
assistDisconnectPeripheral:清理状态,重连后命令恢复正常; - UUID 重连与 MAC 重连两条路径在扫描回调中正确命中目标;
- 命令超时 3 次重试后仍未成功时的最终失败提示。
Related Links
- JL_Assist.h(接口定义,本页核心源文件)
- OTA 升级开发示例(JL_Assist 自定义蓝牙连接).md(官方 Demo 文档)
- 相关能力页:JL_OTAManager(OTA 状态机与命令)、JL_ManagerM(命令中心)、JLAdvParse(广播数据解析)——如需了解这些模块的完整实现,请参见对应目录页。