杰理 SDK 文档中心
首页
首页
  • SDK 框架库

    • JL_BLEKit 蓝牙通信核心
    • JL_AdvParse 广播包解析
    • JL_HashPair 加密配对
    • JL_OTALib 固件升级
    • JLDialUnit 彩屏仓与表盘控制
    • JLBmpConvertKit 位图转换
    • JLPackageResKit 资源包处理
    • JLLogHelper 日志工具
  • 核心功能模块

    • 音乐与媒体控制
    • 音效调节与均衡器
    • 设备发现、连接与设置
    • Auracast 广播接收与发射
    • 文件浏览、闹钟、FM 与灯光控制
    • ANC、按键设置与查找设备
    • AI 翻译与自定义命令
  • 应用架构与工程支撑

    • 杰理之家 App 架构与导航
    • 数据存储与缓存
    • Swift 工具与扩展层
    • JLAudioUnitKit 示例工程
    • SDKTestHelper 测试工具
  • 开发文档与资源

    • 文档中心与 JL_OTALib API 说明
    • 自定义蓝牙接入方式
    • 调试技巧与问题排查
    • 版本历史与社区支持

自定义蓝牙接入方式

杰理 iOS 蓝牙 SDK(JL_BLEKit.framework)提供两种蓝牙接入方式:SDK 内部蓝牙管理(JL_BLEMultiple 封装扫描/连接/断开全流程)与自定义蓝牙接入(开发者自行管理 CBCentralManager/CBPeripheral,通过 JL_Assist 助手类把系统蓝牙事件桥接进 SDK 的 RCSP 协议栈)。本页围绕后者的完整接入机制展开。

Purpose and Scope

本页说明如何在 App 中不使用 SDK 内置的蓝牙管理模块,而是用开发者自己的 CoreBluetooth 链路(扫描、连接、特征发现、通知订阅、断线处理)接入杰理设备,并把系统蓝牙事件逐项转交给 JL_Assist,从而复用 SDK 全部业务能力(命令通道、设备信息、配对认证、OTA、音频传输等)。

本页覆盖:

  • 两种蓝牙管理模式(内部 vs 自定义)的选型与切换
  • JL_Assist 的职责、核心方法与属性
  • 自定义连接全流程(发现 → 连接 → 特征发现 → 配对 → 数据回传 → 断开)
  • 设备认证(Pairing)与 OTA 回连
  • 与上层能力(如翻译传输 JLTranslationManager)的衔接

不属于本页范围(请参阅对应目录页):

  • SDK 内部蓝牙管理:JL_BLEMultiple 的 scanStart/connectEntity 等封装流程,见「SDK 内部蓝牙管理」相关页面。
  • GATT Over EDR/CTKD 连接:自定义蓝牙在 CBConnectPeripheralOptionEnableTransportBridgingKey 下的特殊扫描与连接事件处理,见「GATT Over EDR 连接」页面。
  • 翻译传输功能本身(JLTranslationManager、OPUS/JLA_V2 编解码),见「翻译传输功能」页面。
  • 固件 OTA 升级库(JL_OTALib)的内部实现,见「OTA 升级」页面。

Overview

为什么需要自定义蓝牙接入

SDK 内置的 JL_BLEMultiple 已经封装了扫描、过滤、连接、配对、断线重连等逻辑,大多数场景直接调用即可。但当出现以下需求时,开发者需要接管蓝牙链路:

  1. 已有自研蓝牙栈:App 内已有 CBCentralManager 基础设施,不希望引入第二套扫描/连接逻辑。
  2. 特殊的扫描/过滤策略:需要自定义广播过滤、按 MAC 回连(OTA 场景)、或设备支持 ANCS 协议时按系统级已连接设备重建实体。
  3. GATT Over EDR / CTKD 连接:设备通过 BR/EDR 承载 GATT,需要 CBConnectPeripheralOptionEnableTransportBridgingKey 与 registerForConnectionEvents 等系统 API,此时必须由开发者管理连接事件。
  4. 多设备/多协议混合:同一 App 同时接入杰理设备与其他厂商 BLE 设备。

自定义接入的本质是:系统蓝牙的"状态机"由开发者驱动,JL_Assist 只负责把系统事件翻译成 SDK 内部的 RCSP 协议动作(特征写入、通知订阅、配对认证、分包解析)。

核心概念

概念说明
JL_Assist自定义蓝牙接入助手("自定义蓝牙接入助手"),桥接 CoreBluetooth 事件与 SDK 协议栈
JL_EntityM设备实体对象,承载 CBPeripheral、UUID、广播数据、配对状态、命令管理器等
JL_ManagerM命令管理器,通过 assist.mCmdManager 获取;上层功能(如 JLTranslationManager)均依赖它
mCmdManager.mEntity当前连接的设备实体,自定义连接成功后必须显式赋值
RCSP 通道SDK 与设备间的私有协议,需要 mSERVICE/mRCSP_W(写特征)/mRCSP_R(读特征)
配对认证依赖 JL_HashPair.framework,通过 assist.mNeedPaired 开启
SettingInfoDemo 中的配置开关,getCustomerBleConnect() 决定走自定义链路还是 SDK 链路

Architecture

flowchart TD
    subgraph sg_App["App 层(开发者)"]
        UI["UI / 业务层"]
        BleManager["BleManager 单例<br/>CBCentralManager + CBPeripheralDelegate"]
        SettingInfo["SettingInfo 配置开关<br/>getCustomerBleConnect / getATTComunication"]
    end

    subgraph sg_System["系统 CoreBluetooth"]
        CBCM["CBCentralManager<br/>扫描/连接/断开"]
        CBPeriph["CBPeripheral<br/>服务/特征/通知"]
    end

    subgraph sg_Bridge["桥接层"]
        Assist["JL_Assist<br/>自定义蓝牙接入助手"]
        Entity["JL_EntityM<br/>设备实体"]
        CmdMgr["JL_ManagerM<br/>mCmdManager"]
    end

    subgraph sg_SDK["SDK 能力层"]
        RCSP["RCSP 协议栈<br/>writeRcspData / 分包解析"]
        Pairing["设备认证<br/>JL_HashPair"]
        Upper["上层功能<br/>JLTranslationManager / OTA / 文件管理"]
    end

    subgraph sg_FW["依赖框架"]
        FW["JL_BLEKit / JL_AdvParse<br/>JL_HashPair / JL_OTALib / JLLogHelper"]
    end

    UI --> BleManager
    BleManager --> SettingInfo
    BleManager --> CBCM
    BleManager --> CBPeriph
    CBCM -->|"assistUpdate(central.state)"| Assist
    CBPeriph -->|"didDiscoverCharacteristics<br/>assistDiscoverCharacteristics"| Assist
    CBPeriph -->|"didUpdateValue<br/>assistUpdateValue"| Assist
    CBPeriph -->|"didDisconnect<br/>assistDisconnectPeripheral"| Assist
    Assist -->|"mCmdManager"| CmdMgr
    Assist -->|"构造/绑定"| Entity
    CmdMgr --> Entity
    Assist --> RCSP
    Assist --> Pairing
    CmdMgr --> Upper
    Assist -.->|"编译期依赖"| FW

架构说明:

  • App 层是自定义接入的"指挥官":BleManager 单例持有开发者自己的 CBCentralManager 与 CBPeripheral 代理,所有系统回调在这里集中处理;SettingInfo 决定是否走自定义链路(getCustomerBleConnect()),也决定 GATT Over EDR 特殊路径(getATTComunication())。
  • 桥接层是自定义接入的关键:JL_Assist 不持有任何扫描/连接逻辑,只把系统事件(状态更新、特征发现、通知状态、数据到达、断开)转换成 SDK 内部动作。JL_EntityM 把系统 CBPeripheral 包装为 SDK 设备实体,JL_ManagerM 则是上层所有功能的入口(assist.mCmdManager)。
  • SDK 能力层对自定义接入透明:只要 JL_Assist 桥接正确,RCSP 协议、配对认证、翻译传输、OTA 等能力无需任何改动即可工作。
  • 依赖框架在自定义接入模式下全部需要导入(见下文"环境准备"),其中 JL_HashPair 仅在开启配对认证时实际参与运行。

注:JL_Assist、JL_EntityM、JL_ManagerM 均为 JL_BLEKit.framework 编译产物中的类(仓库内为二进制文件,符号表见 JL_BLEKit),本页方法签名与行为依据 Demo 文档 BleManager 的实际调用方式归纳。

两种蓝牙管理模式对比

BleManager 在 Demo 中通过 SettingInfo.getCustomerBleConnect() 在两种模式间切换,这是理解自定义接入的入口。见 翻译传输功能说明.md 中 blesArray、currentEntity、currentCmdMgr 三个计算属性:

/// 搜索到的设备
var blesArray: [JL_EntityM] {
    // 注意这里的 SettingInfo 记录了开发者是否启用自定义蓝牙连接和是否启用了 GATT over EDR 连接 的
    if SettingInfo.getCustomerBleConnect() || SettingInfo.getATTComunication() {
        return devices
    } else {
        return mBleMultiple.blePeripheralArr as! [JL_EntityM]
    }
}

/// 当前连接的设备
var currentEntity: JL_EntityM? {
    get {
        if SettingInfo.getCustomerBleConnect() || SettingInfo.getATTComunication() {
            assist.mCmdManager.mEntity
        } else {
            _currentEntity
        }
    }
    ...
}

/// 当前连接的命令管理对象
var currentCmdMgr: JL_ManagerM? {
    if SettingInfo.getCustomerBleConnect() || SettingInfo.getATTComunication() {
        assist.mCmdManager
    } else {
        currentEntity?.mCmdManager
    }
}

Source: 翻译传输功能说明.md

关键差异:

维度SDK 内部管理(mBleMultiple)自定义接入(centerManager + JL_Assist)
扫描入口mBleMultiple.scanStart() / scanStop()centerManager.scanForPeripherals(withServices:options:) / stopScan()
连接入口mBleMultiple.connectEntity(_:) { st in ... } 带结果回调centerManager.connect(_:options:),结果看 CBCentralManagerDelegate
设备列表mBleMultiple.blePeripheralArr开发者自维护的 devices: [JL_EntityM]
当前实体来源_currentEntity(由 mutilUpdateEntity 维护)assist.mCmdManager.mEntity
命令管理器currentEntity?.mCmdManagerassist.mCmdManager
断开mBleMultiple.disconnectEntity(_:)centerManager.cancelPeripheralConnection(_:)
断线回调SDK 内部处理开发者回调中调用 assist.assistDisconnectPeripheral(_:) 并清空 mCmdManager.mEntity

设计意图:把"蓝牙链路状态机"与"SDK 业务状态机"解耦。SDK 内部模式中两者耦合在 JL_BLEMultiple 中,便于快速开发;自定义模式中开发者全权控制链路,SDK 只通过 JL_Assist 感知事件,保证两种模式下上层业务代码(currentCmdMgr)接口一致。

环境准备与框架导入

自定义接入需要导入以下框架(必选),见 翻译传输功能说明.md:

框架作用必选
CoreBluetooth.framework系统蓝牙库(自定义接入的直接基础)是
JL_BLEKit.framework主蓝牙业务库(JL_Assist/JL_EntityM/JL_ManagerM)是
JL_AdvParse.framework广播报解析库(JL_EntityM.mAdvData、OTA MAC 匹配依赖)是
JL_HashPair.framework配对认证库(mNeedPaired = true 时使用)是
JL_OTALib.frameworkOTA 升级库是
JLLogHelper.framework日志打印库是
JLAudioUnitKit.framework音频库(仅解码 Opus/Speex,翻译传输场景)可选

工程配置要求:

  • Targets -> Build Settings -> Other Linker Flags 添加 -ObjC
  • Info.plist 添加 Privacy - Bluetooth Always Usage Description
  • Info.plist 添加 Privacy - Bluetooth Peripheral Usage Description

日志初始化(BleManager.init 中完成):

/// 杰理 SDK 的日志
JLLogManager.clearLog()
JLLogManager.setLog(true, isMore: false, level: .DEBUG)
JLLogManager.log(withTimestamp: true)
JLLogManager.saveLog(asFile: true)

Source: 翻译传输功能说明.md

JL_Assist 的职责与核心接口

JL_Assist 是自定义接入的桥接核心,Demo 中直接注释为"自定义蓝牙接入助手"(翻译传输功能说明.md)。其方法集可从框架二进制符号表确认(JL_BLEKit),包括:

方法/属性触发时机作用
assistUpdate(_ state: CBManagerState)centralManagerDidUpdateState把系统蓝牙开关状态同步给 SDK(.poweredOn 时才可继续)
assistDiscoverCharacteristics(for:peripheral:)didDiscoverCharacteristicsFor识别 RCSP 服务与读写特征,建立协议通道
assistUpdate(_:peripheral:result:)didUpdateNotificationStateFor通知订阅完成后触发配对认证,回调 isPaired
assistUpdateValue(for:)didUpdateValueFor设备上报数据传入 SDK 解析("自定义连接是设备更新数据过来时,需要通过此方法传入 SDK 处理")
assistDisconnectPeripheral(_:)didDisconnectPeripheral通知 SDK 链路断开,释放通道资源
configSDK(withPeripheral:)(符号 configSDKWithPeriphal:)连接建立后可选一次性配置 SDK 所需的周边信息
writeRcspData(_:)(符号 writeRcspData:)内部向写特征发送 RCSP 数据包
mCmdManager: JL_ManagerM任意时刻命令管理器,上层功能入口;自定义模式下取代 entity.mCmdManager
mNeedPaired: Bool连接前是否启用设备认证(与 mBleMultiple.ble_PAIR_ENABLE 同步)
mPairKey: NSData连接前配对密钥
mService / mRcsp_W / mRcsp_R特征发现后RCSP 服务 UUID、写特征、读特征
mRcspPeripheral / mRcspWrite / mRcspRead特征发现后对应的系统 CBPeripheral 与特征对象
mBleName: NSString连接前设备名

设计意图:JL_Assist 对外暴露的每个方法都与一个 CoreBluetooth 回调一一对应,降低开发者接入成本——只需要在"对的地方调对的方法",SDK 内部的 RCSP 分包、应答、超时、重传逻辑全部由 JL_Assist 消化,上层(如翻译传输)无需感知链路实现差异。

自定义连接全流程(Core Flow)

分步控制流

自定义接入的完整流程由 CBCentralManagerDelegate 与 CBPeripheralDelegate 两个扩展承载(翻译传输功能说明.md):

1. 蓝牙状态更新 → 同步 SDK + 启动扫描

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    if central.state == .poweredOn {
        startSearchBle()
    }
    assist.assistUpdate(central.state)
}

Source: 翻译传输功能说明.md

注意:.poweredOn 时先 startSearchBle() 再 assist.assistUpdate(central.state)。startSearchBle() 内部再分支——GATT Over EDR 走 registerForConnectionEvents + scanForPeripherals(withServices:nil, options:[CBConnectPeripheralOptionEnableTransportBridgingKey: true]),自定义模式直接 scanForPeripherals,SDK 模式才调 mBleMultiple.scanStart()(翻译传输功能说明.md)。

2. 发现设备 → 构造 JL_EntityM 并去重

func centralManager(_: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi _: NSNumber) {
    if devices.contains(where: { $0.mUUID == peripheral.identifier.uuidString }) {
    } else {
        let newEntity = JL_EntityM()
        newEntity.mUUID = peripheral.identifier.uuidString
        newEntity.setBlePeripheral(peripheral)
        if peripheral.name != nil {
            if devices.contains(where: { $0.mPeripheral.name == peripheral.name }) {
            } else {
                devices.append(newEntity)
            }
        }
    }
    ...
    JL_Tools.post(kJL_BLE_M_FOUND, object: blesArray)
}

Source: 翻译传输功能说明.md

要点:JL_EntityM 需要显式设置 mUUID(系统 peripheral.identifier.uuidString)并调用 setBlePeripheral(_:) 绑定 CBPeripheral;同一设备通过 mUUID 或 mPeripheral.name 双重去重;发现结果通过 JL_Tools.post(kJL_BLE_M_FOUND, object:) 通知 UI。

3. 连接成功 → 发现服务与特征

func centralManager(_: CBCentralManager, didConnect peripheral: CBPeripheral) {
    peripheral.delegate = self
    peripheral.discoverServices(nil)
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices _: Error?) {
    for service in peripheral.services! {
        peripheral.discoverCharacteristics(nil, for: service)
    }
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?) {
    assist.assistDiscoverCharacteristics(for: service, peripheral: peripheral)
}

Source: 翻译传输功能说明.md

assistDiscoverCharacteristics 内部会匹配 RCSP 服务 UUID(mSERVICE)与读写特征(mRCSP_W/mRCSP_R),并保存到 JL_Assist 的属性中,为后续数据收发做准备。

4. 通知订阅完成 → 配对认证或直接成功

func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error _: Error?) {
    if SettingInfo.getPairEnable() {
        // 自定义蓝牙连接时,连上设备后需要进行设备认证
        assist.assistUpdate(characteristic, peripheral: peripheral) { isPaired in
            if isPaired {
                SettingInfo.setToHistory(peripheral.identifier.uuidString)
                self.handleCbp = peripheral
                let entity = JL_EntityM()
                entity.setBlePeripheral(self.handleCbp!)
                self.assist.mCmdManager.mEntity = entity
                JL_Tools.post(kJL_BLE_M_ENTITY_CONNECTED, object: peripheral)
            } else {
                self.centerManager.cancelPeripheralConnection(peripheral)
            }
        }
    } else {
        // 不需要认证时,直接回调成功
        SettingInfo.setToHistory(peripheral.identifier.uuidString)
        handleCbp = peripheral
        let entity = JL_EntityM()
        entity.setBlePeripheral(handleCbp!)
        assist.mCmdManager.mEntity = entity
        JL_Tools.post(kJL_BLE_M_ENTITY_CONNECTED, object: peripheral)
    }
}

Source: 翻译传输功能说明.md

这是自定义接入最容易出错的一步:连接成功的标志不是 didConnect,而是通知订阅完成(didUpdateNotificationStateFor)且(若开启认证)配对通过。认证失败必须主动 cancelPeripheralConnection 回收链路。成功后必须完成三件事:

  1. handleCbp = peripheral 记录当前周边对象(供断开使用);
  2. assist.mCmdManager.mEntity = entity 把新构造的 JL_EntityM 挂到命令管理器上(此后 assist.mCmdManager 才可用);
  3. 发 kJL_BLE_M_ENTITY_CONNECTED 通知业务层。

5. 设备数据到达 → 喂给 SDK 解析

func peripheral(_: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error _: Error?) {
    // 自定义连接是设备更新数据过来时,需要通过此方法传入 SDK 处理
    assist.assistUpdateValue(for: characteristic)
}

Source: 翻译传输功能说明.md

所有设备上报(包括 RCSP 响应、配对输出数据、音频/文件分包)都必须经 assistUpdateValue(for:) 进入 SDK,由 JL_Assist 完成粘包/拆包(onHandleOutputPKG:、onPairOutputData:、onManagerSendPackage: 等内部方法)与分发。

6. 断开连接 → 清理 SDK 状态

func centralManager(_: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error _: Error?) {
    assist.assistDisconnectPeripheral(peripheral)
    assist.mCmdManager.mEntity = nil
    handleCbp = nil
    currentEntity = nil
    JL_Tools.post(kJL_BLE_M_ENTITY_DISCONNECTED, object: peripheral)
}

Source: 翻译传输功能说明.md

断开时 assistDisconnectPeripheral(_:) 通知 SDK 释放通道资源,同时必须把 assist.mCmdManager.mEntity 置空,否则上层功能会继续向已断开的实体发送命令。

时序总览

sequenceDiagram
    participant App as App / BleManager
    participant CBC as CBCentralManager
    participant P as CBPeripheral
    participant A as JL_Assist
    participant M as JL_ManagerM (mCmdManager)
    participant Biz as 业务层

    App->>CBC: scanForPeripherals (自定义模式)
    CBC-->>App: didDiscover(peripheral, advData)
    App->>App: 构造 JL_EntityM (mUUID + setBlePeripheral)
    App->>CBC: connect(entity.mPeripheral, options:[CTKD])
    CBC-->>App: didConnect(peripheral)
    App->>P: discoverServices(nil)
    P-->>App: didDiscoverServices
    App->>P: discoverCharacteristics(nil, for:)
    P-->>App: didDiscoverCharacteristicsFor
    App->>A: assistDiscoverCharacteristics(for:peripheral:)
    P-->>App: didUpdateNotificationStateFor
    App->>A: assistUpdate(characteristic, peripheral:) { isPaired }
    A->>A: 配对认证 (JL_HashPair)
    A-->>App: isPaired = true
    App->>M: mEntity = JL_EntityM(handleCbp)
    App->>Biz: post(kJL_BLE_M_ENTITY_CONNECTED)
    P-->>App: didUpdateValueFor(characteristic)
    App->>A: assistUpdateValue(for:)
    A-->>M: 解析 RCSP 应答/事件
    M-->>Biz: 业务回调 (设备信息/翻译数据等)
    P-->>App: didDisconnectPeripheral
    App->>A: assistDisconnectPeripheral(peripheral)
    App->>M: mEntity = nil
    App->>Biz: post(kJL_BLE_M_ENTITY_DISCONNECTED)

设备认证(Pairing)

认证开关在 setPairEnable(_:) 中统一设置(翻译传输功能说明.md):

/// 设置是否启用设备认证
/// 若关闭,需要多方一起关闭包括设备端跟安卓端
/// - Parameter status: 设备认证开关
func setPairEnable(_ status: Bool) {
    SettingInfo.savePairEnable(status)
    mBleMultiple.ble_PAIR_ENABLE = status
    assist.mNeedPaired = status
}

Source: 翻译传输功能说明.md

要点:

  • 认证开启时,assist.assistUpdate(_:peripheral:) 的 isPaired 回调决定连接是否成立;关闭时(三方一致关闭,包括设备端与安卓端)直接走成功分支。
  • 配对数据流同样经过 didUpdateValueFor → assistUpdateValue(for:),由 JL_Assist 内部 onPairOutputData: 驱动,开发者无需处理配对协议细节。

OTA 回连(按 MAC 匹配广播)

OTA 升级后设备 MAC 不变但 UUID 可能变化,自定义模式通过广播厂商数据匹配回连([翻译传输功能说明.md](https://gitee.com/Jieli-Tech/iOS-JL_Bluetooth/blob/main/code/SDKTestHelper/Docs/翻译传输功能说明.md#L229-L235, L313-L323)):

func reConnectWithMac(_ mac: String) {
    pMac = mac
    startSearchBle()
}

// didDiscover 中:
if pMac != nil {
    if let blead = advertisementData["kCBAdvDataManufacturerData"] as? Data {
        if JL_BLEAction.otaBleMacAddress(pMac!, isEqualToCBAdvDataManufacturerData: blead) {
            stopSearchBle()
            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: DispatchWorkItem(block: {
                self.centerManager.connect(peripheral, options: [CBConnectPeripheralOptionEnableTransportBridgingKey: true])
                self.pMac = nil
            }))
        }
    }
}

Source: 翻译传输功能说明.md

JL_BLEAction.otaBleMacAddress(_:isEqualToCBAdvDataManufacturerData:)(来自 JL_AdvParse)比对广播厂商数据中的 MAC,匹配后停止扫描并延迟 1 秒发起连接,避免与 OTA 库的回连流程竞争。

使用示例

示例一:BleManager 骨架(自定义接入最小结构)

以下代码来自 Demo 文档的完整 BleManager 实现(节选),展示了自定义接入的全部要素——JL_Assist 实例、双模式切换、搜索/连接/断开入口:

class BleManager: NSObject {
    /// 单例
    static let shared = BleManager()

    /// 当前正在使用的设备(含广播信息对象)
    private var _currentEntity: JL_EntityM?

    /// 设备管理
    private var mBleMultiple: JL_BLEMultiple = .init()

    /// 自定义蓝牙接入助手
    private let assist: JL_Assist = .init()

    /// 搜索到的设备
    private var devices: [JL_EntityM] = []

    /// 当前连接的设备
    private var handleCbp: CBPeripheral?

    /// 设备的 mac 地址
    private var pMac: String?

    /// 蓝牙中心管理
    lazy var centerManager: CBCentralManager = .init(delegate: self, queue: .main)

    /// 开始搜索
    func startSearchBle() {
        // 启用 GATT over EDR 时需要先连接上设备的 EDR 否则会搜索不到
        if SettingInfo.getATTComunication() {
            devices.removeAll()
            let uuid = SettingInfo.getAttDevUUID() ?? "AE00"
            let matchingOptions = [CBConnectionEventMatchingOption.serviceUUIDs: [uuid]]
            centerManager.registerForConnectionEvents(options: matchingOptions)
            centerManager.scanForPeripherals(withServices: nil, options: [CBConnectPeripheralOptionEnableTransportBridgingKey: true])
            return
        }
        // 启用自定义蓝牙连接是开发者自行搜索设备
        if SettingInfo.getCustomerBleConnect() {
            devices.removeAll()
            centerManager.scanForPeripherals(withServices: nil, options: [CBConnectPeripheralOptionEnableTransportBridgingKey: true])
        } else {
            // 启用 SDK 蓝牙连接时,调用库里的方法直接搜索
            mBleMultiple.scanStart()
        }
    }

    /// 连接设备
    func connectEntity(_ entity: JL_EntityM) {
        if SettingInfo.getATTComunication() {
            centerManager.connect(entity.mPeripheral)
            return
        }
        stopSearchBle()
        if SettingInfo.getCustomerBleConnect() {
            // 这里的连接增加了一个连接参数,用于设备支持 CTKD 协议时的,GATT OVER EDR 连接时可忽略
            centerManager.connect(entity.mPeripheral, options: [CBConnectPeripheralOptionEnableTransportBridgingKey: true])
        } else {
            mBleMultiple.connectEntity(entity) { st in
                // 结果回调:bleOFF / connectFail / connecting / connectRepeat /
                // connectTimeout / connectRefuse / pairFail / pairTimeout /
                // paired / masterChanging / disconnectOk / null
                switch st { ... }
            }
        }
    }

    /// 断开连接
    func disconnectEntity() {
        if SettingInfo.getCustomerBleConnect() {
            centerManager.cancelPeripheralConnection(BleManager.shared.handleCbp!)
        } else {
            mBleMultiple.disconnectEntity(BleManager.shared.currentEntity!) { _ in }
        }
    }

    /// 根据历史记录连接
    func connectByHistory() {
        if let uuid = SettingInfo.getToHistory() {
            // 当设备支持 ANCS 协议时,可通过这个方法找到已连接的设备
            if let entity = BleManager.shared.mBleMultiple.makeEntity(withUUID: uuid) {
                if SettingInfo.getCustomerBleConnect() {
                    startSearchBle()
                } else {
                    connectEntity(entity)
                }
            }
        }
    }
}

Source: 翻译传输功能说明.md

示例二:CBPeripheralDelegate 桥接(核心回调)

自定义接入的成败取决于这四个代理回调是否准确转发给 JL_Assist:

extension BleManager: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error _: Error?) {
        assist.assistDiscoverCharacteristics(for: service, peripheral: peripheral)
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error _: Error?) {
        if SettingInfo.getPairEnable() {
            // 自定义蓝牙连接时,连上设备后需要进行设备认证
            assist.assistUpdate(characteristic, peripheral: peripheral) { isPaired in
                if isPaired {
                    SettingInfo.setToHistory(peripheral.identifier.uuidString)
                    self.handleCbp = peripheral
                    let entity = JL_EntityM()
                    entity.setBlePeripheral(self.handleCbp!)
                    self.assist.mCmdManager.mEntity = entity
                    JL_Tools.post(kJL_BLE_M_ENTITY_CONNECTED, object: peripheral)
                } else {
                    self.centerManager.cancelPeripheralConnection(peripheral)
                }
            }
        } else {
            // 不需要认证时,直接回调成功
            SettingInfo.setToHistory(peripheral.identifier.uuidString)
            handleCbp = peripheral
            let entity = JL_EntityM()
            entity.setBlePeripheral(handleCbp!)
            assist.mCmdManager.mEntity = entity
            JL_Tools.post(kJL_BLE_M_ENTITY_CONNECTED, object: peripheral)
        }
    }

    func peripheral(_: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error _: Error?) {
        // 自定义连接是设备更新数据过来时,需要通过此方法传入 SDK 处理
        assist.assistUpdateValue(for: characteristic)
    }
}

Source: 翻译传输功能说明.md

示例三:与上层功能衔接(翻译传输)

自定义接入成功后,上层功能通过 currentCmdMgr(即 assist.mCmdManager)初始化,无需感知链路差异(翻译传输功能说明.md):

// 这里的 BleManager.shared.currentCmdMgr 是当前连接的设备的命令管理器,确保在设备连接后初始化
JLBleManager *manager = [BleManager shared].currentCmdMgr;
if (manager) {
    self.translateHelper = [[JLTranslationManager alloc] initWithDelegate:self manager:manager];
}

// 设置翻译模式
JLTranslateSetMode *mode = [[JLTranslateSetMode alloc] init];
mode.modeType = JLTranslateSetModeTypeRecordTranslate;
mode.channel = 1;
mode.sampleRate = 16000;
mode.dataType = JL_SpeakDataTypeOPUS;
[self.translateHelper trStartTranslateMode:mode];

Source: 翻译传输功能说明.md

配置选项

配置项类型默认值(Demo)说明
SettingInfo.getCustomerBleConnect()Bool开发者自行设定是否启用自定义蓝牙连接(决定扫描/连接/断开走 centerManager 还是 mBleMultiple)
SettingInfo.getATTComunication()Bool开发者自行设定是否启用 GATT Over EDR 连接(自定义链路的特殊分支)
assist.mNeedPairedBool由 setPairEnable 设置是否启用设备认证,需与设备端/安卓端一致
mBleMultiple.ble_PAIR_ENABLEBool与 mNeedPaired 同步SDK 内部模式的配对开关(自定义模式下也需保持同步)
mBleMultiple.ble_FILTER_ENABLEBooltrue(Demo init)SDK 扫描过滤开关(自定义模式下 mBleMultiple 仍用于实体管理)
mBleMultiple.ble_TIMEOUTInt7(Demo init)SDK 连接超时秒数
SettingInfo.getAttDevUUID()String?"AE00"GATT Over EDR 模式下注册连接事件的服务 UUID
pMacString?nilOTA 回连目标 MAC,非空时在 didDiscover 中按广播厂商数据匹配
centerManager 队列DispatchQueue.mainCBCentralManager 初始化队列(init(delegate:queue:))

常用通知

自定义接入通过 JL_Tools.post(_:object:) 广播事件,业务层订阅这些通知驱动 UI:

通知触发时机
kJL_BLE_M_FOUND每次 didDiscover 后,携带当前设备列表
kJL_BLE_M_ENTITY_CONNECTED配对成功(或免认证)且 mCmdManager.mEntity 赋值后
kJL_BLE_M_ENTITY_DISCONNECTEDdidDisconnectPeripheral 且 SDK 状态清理完成后
kJL_CONNECT_FAILED连接失败(SDK 模式回调各失败态;自定义模式 didFailToConnect)
kJL_BLE_M_ON / kJL_BLE_M_OFF系统蓝牙打开/关闭(assistUpdate 同步)
kFLT_BLE_PAIR / kFLT_BLE_DISCONNECT配对流程/断开流程内部事件(SDK 模式回调中亦会发出)

API Reference

以下接口依据 Demo 文档中的实际调用方式与框架二进制符号表(JL_BLEKit)整理。方法签名为 Swift 调用形态。

JL_Assist

assistUpdate(_ state: CBManagerState)

  • 同步系统蓝牙状态给 SDK。
  • 参数:state — CBCentralManager 的状态(.poweredOn 前 SDK 不会进行任何收发)。

assistDiscoverCharacteristics(for service: CBService, peripheral: CBPeripheral)

  • 在 didDiscoverCharacteristicsFor 中调用,识别 RCSP 服务与读写特征。
  • 参数:service — 已发现特征的 CBService;peripheral — 所属周边设备。
  • 内部行为:匹配 mSERVICE/mRCSP_W/mRCSP_R 并缓存,随后 SDK 开始订阅通知。

assistUpdate(_ characteristic: CBCharacteristic, peripheral: CBPeripheral, result: (Bool) -> Void)

  • 在 didUpdateNotificationStateFor 中调用;通知订阅完成后触发配对认证。
  • 参数:characteristic — 已更新通知状态的特征;peripheral — 周边设备;result — 回调 isPaired(认证成功为 true)。
  • 注意:仅在 SettingInfo.getPairEnable() 为真时调用;为假时直接走成功分支,不需要此回调。

assistUpdateValue(for characteristic: CBCharacteristic)

  • 在 didUpdateValueFor 中调用,把设备上报数据传入 SDK 解析(RCSP 应答、配对数据、音频/文件分包)。
  • 参数:characteristic — 收到新值的特征。

assistDisconnectPeripheral(_ peripheral: CBPeripheral)

  • 在 didDisconnectPeripheral 中调用,通知 SDK 链路断开并释放通道资源。
  • 参数:peripheral — 断开的周边设备。

属性:

属性类型说明
mCmdManagerJL_ManagerM命令管理器;自定义模式下的上层功能入口,连接成功后需设置其 mEntity
mNeedPairedBool是否启用设备认证
mPairKeyNSData配对密钥
mService / mRcsp_W / mRcsp_RCBUUIDRCSP 服务 UUID、写特征 UUID、读特征 UUID
mRcspPeripheral / mRcspWrite / mRcspReadCBPeripheral / CBCharacteristic特征发现后缓存的通道对象
mBleNameNSString设备名

JL_EntityM

属性/方法类型说明
mUUIDString设备唯一标识(自定义接入取 peripheral.identifier.uuidString)
mPeripheralCBPeripheral系统周边设备对象
setBlePeripheral(_:)方法绑定 CBPeripheral 到实体(构造实体后必须调用)
mFilterKey / mPairKey / mAdvDataNSData过滤键、配对键、广播数据(JL_AdvParse 解析结果)
mBLE_TIMEOUTInt实体级超时
mBLE_FILTER_ENABLE / mBLE_PAIR_ENABLE / mBLE_IS_PAIRED / mBLE_NEED_OTABool过滤/配对/已配对/需要 OTA 状态
mCmdManagerJL_ManagerM该实体的命令管理器(自定义模式下统一走 assist.mCmdManager)
mType / isExclusive / isBound / mRSSI混合设备类型、独占/绑定标志、信号强度

失败模式、边界情况与并发

配对失败

  • 表现:assist.assistUpdate(_:peripheral:) 回调 isPaired = false。
  • 处理:必须 centerManager.cancelPeripheralConnection(peripheral) 回收链路,不能继续使用该实体。
  • 注意:关闭认证需要设备端、安卓端、iOS 端三方一致,否则 iOS 侧跳过认证而设备端等待认证会导致连接行为异常。

连接失败与超时

  • 自定义模式没有 SDK 模式那样丰富的状态回调(connectTimeout/connectRefuse/pairFail 等),失败统一从 didFailToConnect 进入,Demo 中直接广播 kJL_CONNECT_FAILED。
  • SDK 模式(mBleMultiple.connectEntity)会逐一回调各失败态(.bleOFF、.connectTimeout、.pairTimeout 等),业务层应分别处理(参考 翻译传输功能说明.md)。

断线后的状态残留

  • 若 didDisconnectPeripheral 中遗漏 assist.mCmdManager.mEntity = nil,上层功能(如翻译传输)会继续向已断开实体发命令,导致"发不出去也不报错"的假死状态。
  • 顺序要求:先 assistDisconnectPeripheral 再清 mEntity,最后广播 kJL_BLE_M_ENTITY_DISCONNECTED。

设备列表去重与多设备

  • didDiscover 中按 mUUID 与 mPeripheral.name 双重去重;相同名字但不同 UUID 的设备会被保留。
  • mBleMultiple.makeEntity(withUUID:) 可用于 ANCS 场景下按系统已连接设备重建实体(connectByHistory)。

OTA 回连竞争

  • reConnectWithMac 会缓存 pMac,扫描到匹配广播后先 stopSearchBle() 再延迟 1 秒连接,避免与 OTA 升级流程的自动回连竞争;连接发起后立即清空 pMac,防止重复匹配。

并发与线程

  • CBCentralManager 在 queue: .main 初始化(Demo),所有代理回调在主队列串行执行;JL_Assist 的配对回调 block 也在该链路上执行,不应在回调内同步执行耗时操作(如需网络请求应异步化)。
  • mCmdManager 的命令发送是串行的(SDK 内部管理 mCmdSN 与应答匹配),业务层可并发调用但 SDK 会排队。

性能与运维

  • 扫描开销:自定义模式下 scanForPeripherals(withServices: nil, ...) 全量扫描,回调频率高;Demo 中每次 didDiscover 都广播 kJL_BLE_M_FOUND,高频刷新列表时建议业务层做节流(例如固定刷新间隔),避免主线程频繁重建 UI。
  • OTA 回连:MAC 匹配只解析 kCBAdvDataManufacturerData,开销小;延迟 1 秒连接是为规避系统/OTA 库竞争的设计取舍。
  • 日志:JLLogManager.setLog(true, isMore: false, level: .DEBUG) + saveLog(asFile: true) 可落盘日志;联调自定义连接问题时,重点看配对输出与 RCSP 分包相关日志(JL_Assist 的 onPairOutputData:/onHandleOutputPKG: 链路)。
  • 超时:SDK 模式 ble_TIMEOUT = 7;自定义模式下连接超时需开发者自行用 DispatchQueue 或定时器兜底(Demo 中未内置)。

扩展点

  1. 自定义扫描策略:startSearchBle 是天然的扩展点——可替换 withServices/options,实现服务 UUID 过滤、RSSI 阈值、定向广播等业务定制,不影响后续 JL_Assist 桥接。
  2. GATT Over EDR / CTKD:通过 CBConnectPeripheralOptionEnableTransportBridgingKey 与 registerForConnectionEvents 扩展,connectionEventDidOccur 中构造 JL_EntityM 的流程与普通 BLE 一致(见 翻译传输功能说明.md)。
  3. 历史设备回连:connectByHistory 支持 ANCS 设备按 UUID 重建实体;自定义模式下先扫描再由业务层匹配历史 UUID。
  4. 上层能力挂载:任何基于 JL_ManagerM 的功能(翻译传输、文件管理、OTA、系统 EQ 等)都可通过 currentCmdMgr 直接复用,无需关心链路实现。

测试

仓库中未发现针对 JL_Assist 自定义接入链路的独立单元测试文件;现有验证方式为 code/JieLi_Home_Demo 与 code/SDKTestHelper 两个 Demo 工程中的真机联调。可参考的验证要点:

  • 开/关 SettingInfo.getCustomerBleConnect() 验证两种模式设备列表、连接、断开的 UI 一致性;
  • 开/关 setPairEnable 验证认证分支(成功 isPaired=true 广播 kJL_BLE_M_ENTITY_CONNECTED,失败自动断开);
  • 使用 reConnectWithMac 验证 OTA 场景下的 MAC 回连;
  • 连接后调用任意 mCmdManager 命令(如获取系统信息),确认 didUpdateValueFor → assistUpdateValue 回包链路完整。

Related Links

  • SDKTestHelper 翻译传输功能说明(自定义接入示例来源)
  • JL_BLEKit.framework(JL_Assist/JL_EntityM/JL_ManagerM 二进制)
  • 目录页「SDK 内部蓝牙管理」:JL_BLEMultiple 的扫描/连接/断开封装与状态回调
  • 目录页「GATT Over EDR 连接」:CBConnectPeripheralOptionEnableTransportBridgingKey、连接事件监听与注意事项
  • 目录页「翻译传输功能」:JLTranslationManager 初始化与音频传输链路
  • 目录页「OTA 升级」:JL_OTALib、MAC 回连与升级状态机
Prev
文档中心与 JL_OTALib API 说明
Next
调试技巧与问题排查