自定义蓝牙接入方式
杰理 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 已经封装了扫描、过滤、连接、配对、断线重连等逻辑,大多数场景直接调用即可。但当出现以下需求时,开发者需要接管蓝牙链路:
- 已有自研蓝牙栈:App 内已有
CBCentralManager基础设施,不希望引入第二套扫描/连接逻辑。 - 特殊的扫描/过滤策略:需要自定义广播过滤、按 MAC 回连(OTA 场景)、或设备支持 ANCS 协议时按系统级已连接设备重建实体。
- GATT Over EDR / CTKD 连接:设备通过 BR/EDR 承载 GATT,需要
CBConnectPeripheralOptionEnableTransportBridgingKey与registerForConnectionEvents等系统 API,此时必须由开发者管理连接事件。 - 多设备/多协议混合:同一 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 开启 |
SettingInfo | Demo 中的配置开关,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?.mCmdManager | assist.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.framework | OTA 升级库 | 是 |
JLLogHelper.framework | 日志打印库 | 是 |
JLAudioUnitKit.framework | 音频库(仅解码 Opus/Speex,翻译传输场景) | 可选 |
工程配置要求:
Targets -> Build Settings -> Other Linker Flags添加-ObjCInfo.plist添加Privacy - Bluetooth Always Usage DescriptionInfo.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 回收链路。成功后必须完成三件事:
handleCbp = peripheral记录当前周边对象(供断开使用);assist.mCmdManager.mEntity = entity把新构造的JL_EntityM挂到命令管理器上(此后assist.mCmdManager才可用);- 发
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.mNeedPaired | Bool | 由 setPairEnable 设置 | 是否启用设备认证,需与设备端/安卓端一致 |
mBleMultiple.ble_PAIR_ENABLE | Bool | 与 mNeedPaired 同步 | SDK 内部模式的配对开关(自定义模式下也需保持同步) |
mBleMultiple.ble_FILTER_ENABLE | Bool | true(Demo init) | SDK 扫描过滤开关(自定义模式下 mBleMultiple 仍用于实体管理) |
mBleMultiple.ble_TIMEOUT | Int | 7(Demo init) | SDK 连接超时秒数 |
SettingInfo.getAttDevUUID() | String? | "AE00" | GATT Over EDR 模式下注册连接事件的服务 UUID |
pMac | String? | nil | OTA 回连目标 MAC,非空时在 didDiscover 中按广播厂商数据匹配 |
centerManager 队列 | DispatchQueue | .main | CBCentralManager 初始化队列(init(delegate:queue:)) |
常用通知
自定义接入通过 JL_Tools.post(_:object:) 广播事件,业务层订阅这些通知驱动 UI:
| 通知 | 触发时机 |
|---|---|
kJL_BLE_M_FOUND | 每次 didDiscover 后,携带当前设备列表 |
kJL_BLE_M_ENTITY_CONNECTED | 配对成功(或免认证)且 mCmdManager.mEntity 赋值后 |
kJL_BLE_M_ENTITY_DISCONNECTED | didDisconnectPeripheral 且 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— 断开的周边设备。
属性:
| 属性 | 类型 | 说明 |
|---|---|---|
mCmdManager | JL_ManagerM | 命令管理器;自定义模式下的上层功能入口,连接成功后需设置其 mEntity |
mNeedPaired | Bool | 是否启用设备认证 |
mPairKey | NSData | 配对密钥 |
mService / mRcsp_W / mRcsp_R | CBUUID | RCSP 服务 UUID、写特征 UUID、读特征 UUID |
mRcspPeripheral / mRcspWrite / mRcspRead | CBPeripheral / CBCharacteristic | 特征发现后缓存的通道对象 |
mBleName | NSString | 设备名 |
JL_EntityM
| 属性/方法 | 类型 | 说明 |
|---|---|---|
mUUID | String | 设备唯一标识(自定义接入取 peripheral.identifier.uuidString) |
mPeripheral | CBPeripheral | 系统周边设备对象 |
setBlePeripheral(_:) | 方法 | 绑定 CBPeripheral 到实体(构造实体后必须调用) |
mFilterKey / mPairKey / mAdvData | NSData | 过滤键、配对键、广播数据(JL_AdvParse 解析结果) |
mBLE_TIMEOUT | Int | 实体级超时 |
mBLE_FILTER_ENABLE / mBLE_PAIR_ENABLE / mBLE_IS_PAIRED / mBLE_NEED_OTA | Bool | 过滤/配对/已配对/需要 OTA 状态 |
mCmdManager | JL_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 中未内置)。
扩展点
- 自定义扫描策略:
startSearchBle是天然的扩展点——可替换withServices/options,实现服务 UUID 过滤、RSSI 阈值、定向广播等业务定制,不影响后续JL_Assist桥接。 - GATT Over EDR / CTKD:通过
CBConnectPeripheralOptionEnableTransportBridgingKey与registerForConnectionEvents扩展,connectionEventDidOccur中构造JL_EntityM的流程与普通 BLE 一致(见 翻译传输功能说明.md)。 - 历史设备回连:
connectByHistory支持 ANCS 设备按 UUID 重建实体;自定义模式下先扫描再由业务层匹配历史 UUID。 - 上层能力挂载:任何基于
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 回连与升级状态机