iOS蓝牙开发梳理:广播端和扫描端实现
【前言】
* 目前项目里有蓝牙支付功能,对于蓝牙开发功能,要求比较高,包括iOS与Android之间的通讯。
* 今天整理了下iOS蓝牙SDK开发流程中的知识点,总结了这篇文章,希望给各位开发蓝牙功能的同学带来帮助。
【功能目标】
开发移动设备的蓝牙功能,目的用来实现设备之间数据自由通讯(数据发,收),完成移动服务端和客服端场景交互。
【定义场景】
1: 广播端:服务端定义,用于被多台扫描设备同时识别并订阅;
2: 扫描端:客服端定义,用于扫描并订阅广播端设备;
【实现方案】
CoreBluetooth:iOS原生SDK。
导入: <CoreBluetooth/CoreBluetooth.h> 。
开始广播功能:
【第一步、开启广播】
(1): 涉及的类
1: CBPeripheralManager;
外设管理器,管理设备广播状态。
2: CBUUID;
唯一标识,设备的服务,特性和特征描述符。
3: CBMutableService;
外设管理器的服务,用于设定服务特征。
4: CBMutableCharacteristic;
服务的特征,用于设定特征描述。
5: CBMutableDescriptor;
特征的描述。
(2): 类调用时序图
时序图备注: CBPeripheralManager 添加服务是很重要的一步。
(3): CoreBluetooth 原生函数
1: 蓝牙创建,用于权限判断:
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
2: 外设管理器添加服务完成,回调结果:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error;
3: 广播开启完成,回调结果:
-(void)peripheralManagerDidStartAdvertising: (CBPeripheralManager *)peripheral error:(NSError *)error;
(4): 广播开启失败,重启
1: 重置广播,目前重试次数: ADVERTISING_RETRY = 3;(注:根据自己需求设置)
2: 重置加载安全模块调用方法: [self setUpServiceSecurity]; (注:需要根据自己功能需求实现)
3: 重置外设管理器步骤: 1:关闭广播 2:清除设备 3:重置管理器
4: 重置创建服务和特征调用方法: [self setupServiceAndCharacteristics];(注:需要根据自己功能需求实现)
(5): 蓝牙广播,参数配置
1: 广播设备名称: 可通过CBPeripheralManager 函数:
- (void)startAdvertising:(nullable NSDictionary<NSString *, id> *)advertisementData;
进行设置:CBAdvertisementDataLocalNameKey 的值。
2: 信号强度 : iOS不支持设置。
3: 广播频率 : iOS不支持设置。
【第二步、广播被订阅】
(1): 涉及的类
1: CBCentral: 发起订阅的扫描设备;
2: CBCharacteristic: 扫描设备的特性信息;
(2): CoreBluetooth原生函数
1: 订阅成功回调,记录订阅扫描设备:
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
(3): 被取消订阅函数
l 扫描设备取消订阅,移除相应记录的设备信息:
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
(4):完成订阅
注:当有扫描设备订阅了广播设备后,广播端可以给扫描设备发送数据。
【第三步、发送数据】
(1): 涉及的类
DeviceInforModel: 数据容器对象。(注:自定义对象,用来记录外设设备部信息和接,发数据的记录)
-属性- messageSentData: 发送数据DATA;
-属性- sentStartIndex: 发送数据开始下标;
-属性- diffLength: 每个包最大字节;
(2): 发送流程图
(流程图备注):
1: Start: 分包首个数据包字符串标示;
2: End: 分包最后一个数据包字符串标示;
3: sentStartIndex: 每个发送数据包的开始标示,默认等于0;
4: messageSentData:总数据中按照 sentStartIndex 截取的分包数据;
5: IsReady: 外围设备已准备好发送特征值更新,有相应的回调函数;
6: sendMessagePart:进入数据分包流程;
(3): CoreBluetooth原生函数
1: 发送:通过通知或指示将更新后的特征值发送给一个或多个中心。
- (BOOL)updateValue:(NSData *)value forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:(nullable NSArray<CBCentral *> *)centrals;
2: 发送结果:告诉委托本地外围设备已准备好发送特征值更新。
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;
【第四步、接收数据】
(1): 涉及的类
1: CBATTRequest: 写入数据的请求。
-属性- lastObject: 目标数据;
2: DeviceInforModel: 数据容器对象。(自定义对象,用来记录外设设备部信息和接,发数据的记录)
-属性- messageReceivedData:发送数据DATA;
-属性- isStart: 开头标示;
-属性- startMsg: 开头数据;
-属性- isEnd: 结尾标示;
-属性- endMsg: 结尾数据;
(2): 接收流程图
(流程图备注):
1: Start: 分包首个数据包字符串标示,此时设置isStart=YES;
2: End: 分包最后一个数据包字符串标示,此时设置isEnd=YES;
3: CBATTRequest.lastObject:每个包的数据源;
4: messageReceivedData: 与历史数据进行合并;
5: hasPrefix:@"End":这步判断结束标示,有End标示,合并数据,输出完整包;
6: CBATTRequest: 没有End标示,等待分包数据;
(3): CoreBluetooth原生函数
l 中心设备写入数据的时候,回调函数:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests;
【完成:广播功能】
完成上面步骤,此时广播端可以自由发送和接收数据。
开始扫描功能:
【第一步、开启扫描】
(1): 涉及的类
1: CBCentralManager:中心设备管理器。
2: CBUUID: 唯一标识,设备的服务,特性和特征描述符。
3: CBService: 中心设备管理器的服务,用于扫描指定服务广播。
4: CBPeripheral:外设管理器,扫描到或连接的广播设备。
5: RSSI: 外设型号强度值。
(2): 类调用时序图
时序图备注:在识别到广播外设 CBPeripheral,并且完成记录后,就可以开启指定外设的连接请求,APP级外设标示通过 CBService 区分。
(3): 扫描开启失败
注:蓝牙功能不可用,或者未开启,会导致开启失败。
未发现符合要求的外设时,会继续扫描。
(4): 蓝牙扫描,参数配置
1: 扫描设备名称: iOS不支持设置,
备注:订阅广播端成功后,广播端区别扫描设备通过:central.identifier;
2: 扫描频率: iOS不支持设置。
【第二步、连接广播】
(1): 涉及的对象
1: self. centralManager:中心设备管理器;
2: self.peripheral: 中心设备管理器连接目标外设;
3: self.service: 用于查找的服务特征;
4: service.characteristics:对应服务的特征集合;
(2): CoreBluetooth原生函数
1: 发起连接外设,调用函数:
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
2: 连接成功,回调函数:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
3: 根据SERVICE_UUID来寻找服务,调用函数:
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
4: 寻找到特定服务,回调函数:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;
5:寻找特定服务特征,回调函数:
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;
6: 发现特征,回调函数:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error;
7: 读取特征数据,调用函数:
[peripheral readValueForCharacteristic:self.characteristic];
8: 发送订阅通知,调用函数(广播端会收到被订阅消息):
[peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
9: 连接失败,回调函数:
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
10: 断开连接,回调函数:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
(3): CoreBluetooth连接流程图
【第三步、发送数据】
注:扫描端处理发送数据流程,与广播端处理发送一致,详见广播端;
【第四步、接收数据】
注:扫描端处理接收数据流程,与广播端处理接收一致,详见广播端;
【第五步、断开连接】
1: self.centralManager 断开指定 外设 self.peripheral;
[self.centralManager cancelPeripheralConnection:self.peripheral];
l 断开连接,回调函数:
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
功能补充部分:
- 前面梳理了两端数据发送,接收流程,下面补充一下实现方案:
【一:广播端数据读,写方案】
CBMutableCharacteristic: 广播端主要通过设置 CBMutableCharacteristic 特性值属性实现:
CBCharacteristicPropertyWrite:设置广播允许写入特性值,用于接收数据;
CBCharacteristicPropertyNotify:设置广播允许特征值更新的通知,用于发送数据;
(以下代码,案例)
CBMutableCharacteristic *characteristic = [
[CBMutableCharacteristic alloc]
initWithType:characteristicID
properties:
CBCharacteristicPropertyNotify| (注:特性支持通知方案)
CBCharacteristicPropertyWrite (注:特性支持写入方案)
value:nil
permissions:CBAttributePermissionsReadable|
CBAttributePermissionsWriteable];
CBUUID *UUID_Descriptor = [CBUUID UUIDWithString:DESCRIPTORUUID];
// 初始化一个特征的描述
CBMutableDescriptor *mDescriptor = [[CBMutableDescriptor alloc]initWithType:UUID_Descriptor value:[NSData data]];
[characteristic setDescriptors:@[mDescriptor]];
// 特征添加进服务
service.characteristics = @[characteristic];
// 服务加入管理
[self.peripheralManager addService:service];
---- 写入方案:
1: 通过更新自身特性值实现写入数据,调用函数:
- (BOOL)updateValue:(NSData *)value forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:(nullable NSArray<CBCentral *> *)centrals;
2: 当自身特性值准备更新时,回调函数(写入完成,会通知扫描端):
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;
---- 读取方案:
1: 通过允许写入特性值,回调函数(扫描端写入特性值完成回调):
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests;
【第二:扫描端数据读,写方案】
---- 写入方案:
1: 扫描发送数据,调用函数(广播特性值需要支持writeValue: ):
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;
2: 写入数据完成,回调结果:
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
---- 读取方案:
1:通过实现外设代理方案:CBPeripheralDelegate:
self.peripheral.delegate = self;
2: CBPeripheralDelegate代理回调函数:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
结语:
蓝牙广播端和扫描端的实现流程,上面梳理完成。
蓝牙数据传输中的加密处理没有包括在里面。
有问题欢迎一起研究讨论,本人QQ号:497609288.