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

    • 项目概述与功能特性
    • 工程结构与运行环境
  • 快速开始

    • SDK 集成步骤
    • 连接方式选择指南
  • 核心 SDK 架构

    • SDK 框架组成
    • JL_OTAManager 升级管理 API
    • 设备认证与广播解析
  • 蓝牙连接与设备发现

    • 设备扫描与广播发现
    • 原生 CoreBluetooth 连接
    • JL_BLEKit SDK 连接
    • JL_Assist 自定义连接
    • GATT Over BR/EDR 经典蓝牙升级
  • OTA 升级工作流

    • 标准升级流程
    • 自动化测试与批量升级
    • 广播音箱升级
    • 升级文件管理
  • 示例工程

    • 完整示例应用
    • 迷你示例工程
    • 第三方依赖与工具
  • 开发支持与版本发布

    • 文档中心与 API 说明
    • SDK 版本与构建产物
    • 调试技巧与日志辅助

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

上图展示了两条关键路径:

  1. 上行(读)路径:BleManager 收到的订阅通知数据 → 交给 JL_Assist 解析(assistUpdateValueForCharacteristic:)→ 进入 JL_ManagerM 命令中心 → JL_OTAManager 得到响应;
  2. 下行(写)路径: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 之后才可发送,这是整个自定义连接流程中最重要的时序约束。

核心属性语义

属性类型含义
mCmdManagerJL_ManagerM *命令中心。JL_Assist 在内部完成 RCSP 封装后把命令交给它,上层 JL_OTAManager 通过它收发指令
mServiceNSString *GATT 服务号(UUID 字符串),例如杰理设备服务 AE00
mRcsp_WNSString *RCSP 写特征 UUID(App 向设备下发指令的特征)
mRcsp_RNSString *RCSP 读/通知特征 UUID(设备主动上报的特征,需订阅 notify)
mAuthKey / mAuthEnableNSData * / BOOL设备认证密钥与开关。启用后连接建立即执行认证握手,认证通过才允许收发业务命令
mPairKey / mNeedPairedNSData * / BOOL旧版"握手(配对)秘钥"接口,头文件中标记为 deprecated("Use mAuthKey/mAuthEnable instead"),新代码应迁移到认证接口
mLogDataBOOL是否打印裸数据(调试用),开启后可观察 RCSP 帧收发
mLimitMtuNSInteger在最大 MTU 基础上减少的数据量,默认 40,用于给系统协议栈预留余量
mMaxMtuNSInteger最大 MTU。头文件注释明确指出初始化值不一定准确,应使用 [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse] 在实际连接后再获取
mRcspPeripheral / mRcspWrite / mRcspReadCBPeripheral * / CBCharacteristic *连接建立后由 assistDiscoverCharacteristicsForService:Peripheral: 填充的当前链路对象
mBleNameNSString *设备名字,用于日志与重连识别
mDelegateid<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)

流程要点:

  1. 扫描过滤:Demo 中 startScan 只扫描 3 秒后自动 stopScan;发现回调里同时支持两种重连目标——reconnectUUID(按 peripheral.identifier 匹配)与 reconnectMac(按 kCBAdvDataManufacturerData 中的 MAC 与 JLAdvParse.otaBleMacAddress 匹配)。
  2. 特征订阅是认证的触发点:assistUpdateCharacteristic:Peripheral:Result: 的 Result 块是 JL_Assist_BK,返回 isPaired。只有收到 YES 后业务代码才应发送 OTA 命令;NO 表示认证失败,应断开并提示用户。
  3. 写路径是"回调闭环":SDK 需要发送的任何数据都以 assistDidWriteData: 交还 App,App 写入 CBPeripheral 后,系统回调再触发 assistDidReady,SDK 才知道可以继续下一包。如果 App 忘记实现 mDelegate,链路将完全静默——数据不会发出,也不会有报错,这是集成时最常见的坑。

与 JL_OTAManager 的集成

自定义连接模式下,JL_Assist 通过 mCmdManager(JL_ManagerM)向 JL_OTAManager 提供命令通道,因此 OTA 流程对上层完全透明。官方 Demo(JLAssistOTADemo)给出的标准调用顺序为:

  1. 初始化 JL_OTAManager 单例并绑定 delegate;
  2. 完成自定义连接 + 特征订阅 + 认证(上文流程)后,把 CBPeripheral 的 UUID 与名称写入 otaManager.mBLE_UUID / mBLE_NAME,调用 noteEntityConnected() 通知 OTA 管理器设备已就绪,再调用 cmdTargetFeature() 查询设备能力;
  3. 用户点击升级时调用 cmdOTAData(data) 下发固件数据;
  4. OTA 管理器在需要发送数据时通过 otaDataSend(_ data: Data) 回调回到 App,App 再调用 BleManager.shared.assistManager.bleWrite(data) 走 JL_Assist 写路径;
  5. 取消升级调用 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 为准):

选项类型默认值说明
mServiceNSString *无(必填)GATT 服务号,必须与设备广播/服务一致,否则无法发现 RCSP 特征
mRcsp_WNSString *无(必填)RCSP 写特征 UUID,App 下发指令用
mRcsp_RNSString *无(必填)RCSP 读/通知特征 UUID,设备上报用,需 setNotifyValue:YES 订阅
mAuthEnableBOOLNO(需按产品开启)是否启用设备认证;开启后连接建立即执行认证握手
mAuthKeyNSData *nil设备认证密钥,与 mAuthEnable 配套使用
mNeedPairedBOOL—⚠️ 已废弃(deprecated),请迁移至 mAuthEnable
mPairKeyNSData *—⚠️ 已废弃(deprecated),请迁移至 mAuthKey
mLimitMtuNSInteger40在最大 MTU 基础上预留的余量,防止分片越界
mMaxMtuNSInteger初始化值不准确实际应取 [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse]
mLogDataBOOLNO是否打印 RCSP 裸数据,调试用
mDelegateid<JL_AssistDelegate>nil写数据回调代理;不设置则数据无法发出
mBleNameNSString *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 的设计天然提供了三个扩展面:

  1. 连接策略扩展:BleManager 层可自由实现任何过滤/重连逻辑(UUID、MAC、广播数据),只需保证最终把系统回调正确转发给 assist* 方法;
  2. 写通道扩展:通过 JL_AssistDelegate 可以插入自己的发送队列、加密(在 assistDidWriteData: 中先处理再写入)、日志统计或流量整形;
  3. 命令层扩展: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(广播数据解析)——如需了解这些模块的完整实现,请参见对应目录页。
Prev
JL_BLEKit SDK 连接
Next
GATT Over BR/EDR 经典蓝牙升级