CoreBluetooth编程指南(一)
- 使用蓝牙低能耗和BR/EDR(“经典”)设备进行通信。
- 核心蓝牙框架为你的应用程序提供了与支持蓝牙的低能耗(LE)和基本速率/增强数据速率(BR/EDR)无线技术进行通信所需的类。
- 不要对核心蓝牙框架的任何类进行子类化。不支持重写这些类,并导致未定义的行为。
在macOS上运行的iPad应用程序不支持核心蓝牙后台执行模式,iOS可以通过配置后台能力使用蓝牙通信
权限配置:
关键类及功能概览
中央处理
- CBCentral: 一种连接到本地应用程序的远程设备,它充当外围设备。
- CBCentralManager: 扫描、发现、连接和管理外围设备的对象。
- CBCentralManagerDelegate: 为发现和管理外围设备提供更新的一种协议
外围设备
- CBPeripheral: 远程外围设备。
- CBPeripheralDelegate: 提供外设服务使用更新的一种协议。
- CBPeripheralManager: 一个对象,用于管理和公布此应用程序公开的外围服务。
- CBPeripheralManagerDelegate: 提供本地外设状态更新和与远程中央设备交互的协议。
- CBAttribute: 外围设备提供的服务的共同方面的一种表示。
- CBAttributePermissions: 表示特征值的读、写和加密权限的值。
服务
- CBService: 完成设备功能或特性的数据和相关行为的集合。
- CBMutableService: 具有可写属性值的服务。
- CBCharacteristic: 远程外设服务的一种特性。
- CBMutableCharacteristic: 本地外设服务的一种特性。
- CBDescriptor: 提供有关远程外设特性的进一步信息的一种对象。
- CBMutableDescriptor: 提供有关本地外设特性的附加信息的一种对象。
支持类型
- CBManager: 管理中心和外围对象的抽象基类。
- CBATTRequest: 使用属性协议(ATT)的请求。
- CBPeer: 表示远程设备的对象。
- CBUUID: 由蓝牙标准定义的通用唯一标识符
错误
- CBErrorDomain: 核心蓝牙错误的域。
- CBError: 核心蓝牙在蓝牙事务处理期间返回的错误代码。
- CBATTErrorDomain: 核心蓝牙ATT错误的域。
- CBATTError: GATT服务器(远程外围设备)在蓝牙低能量ATT事务处理过程中可能返回的错误。
介绍
- 核心蓝牙框架为iOS和Mac应用程序提供了与配备蓝牙低能耗无线技术的设备通信所需的类。例如,你的应用程序可以发现、探索和与低能量的外围设备交互,比如心率监视器和数字恒温器。从macos10.9和iOS6开始,Mac和iOS设备还可以作为蓝牙低能耗外围设备,为其他设备(包括其他Mac和iOS设备)提供数据
- 蓝牙低能耗无线技术基于蓝牙4.0规范:
- 该规范定义了一组用于低能耗设备之间通信的协议。蓝牙核心框架是对蓝牙低能耗协议栈的抽象。也就是说,它对开发者隐藏了规范的许多底层细节,使你更容易开发与蓝牙低能耗设备交互的应用程序。http://www.elecfans.com/tongxin/rf/20180112614425_a.html
- 中央和外围设备是核心蓝牙的关键
- 蓝牙低能量通信,有两个关键玩家:中央和外围。每个玩家都有不同的角色。外围设备通常具有其他设备所需的数据。中央计算机通常利用外围设备提供的信息来完成某些任务。例如,配备蓝牙低能耗技术的数字恒温器可以向iOS应用程序提供房间的温度,然后应用程序以用户友好的方式显示温度。
- 每个玩家在执行其角色时执行不同的任务集。外围设备通过广播宣传其拥有的数据,使其存在为人所知。Central扫描附近可能有他们感兴趣的数据的外围设备。当中央发现这样一个外设时,中心请求连接到外设,并开始探索和与外设的数据交互。外围设备负责以适当的方式响应中央
- 核心蓝牙简化了常见的蓝牙任务:
- 蓝牙核心规范从蓝牙核心4.0级抽象而来。因此,你需要在应用程序中实现的许多常见的蓝牙低能耗任务都得到了简化。如果您正在开发一个实现中心角色的应用程序,核心蓝牙可以使您轻松地发现和连接外围设备,以及探索和交互外围设备的数据。此外,核心蓝牙使您很容易设置您的本地设备来实现外围角色。
- iOS应用程序状态影响蓝牙行为
- 当iOS应用程序处于后台或处于挂起状态时,其蓝牙相关功能将受到影响。默认情况下,你的应用在后台或处于挂起状态时无法执行蓝牙低能耗任务。也就是说,如果你的应用需要在后台执行蓝牙低能耗任务,你可以声明它支持一种或两种核心蓝牙后台执行模式(一种用于中心角色,一种用于外围角色)。即使您声明了其中一种或两种后台执行模式,当应用程序处于后台时,某些蓝牙任务的运行方式也会有所不同。你想在设计你的应用程序时考虑到这些差异。
- 支持后台处理的应用程序也可能随时被系统终止,为当前的前台应用程序释放内存。从iOS7开始,核心蓝牙支持保存中央和外围管理器对象的状态信息,并在应用程序启动时恢复状态。您可以使用此功能来支持涉及蓝牙设备的长期操作。
- 遵循最佳实践来增强用户体验
- 核心蓝牙框架让你的应用程序可以控制许多常见的蓝牙低能耗事务。遵循最佳实践,以负责任的方式利用这一级别的控制,并增强用户体验。
- 例如,在执行中心或外围角色时,您执行的许多任务都使用设备的机载无线电通过空中传输信号。由于您的设备的无线电与其他形式的无线通信共享,并且由于无线电的使用会对设备的电池寿命产生不利影响,所以在设计您的应用程序时,应尽量减少使用无线电的次数。
概览
- 核心蓝牙框架允许iOS和Mac应用程序与蓝牙低能耗设备进行通信。例如,你的应用程序可以发现、探索低能耗的外围设备,如心率监视器、数字恒温器,甚至其他iOS设备,并与之交互。(局限于iOS设备)
CoreBluetooth
框架是蓝牙4.0规范的抽象,用于低能耗设备。也就是说,它对开发者隐藏了规范的许多底层细节,使你更容易开发与蓝牙低能耗设备交互的应用程序。因为框架是基于规范的,所以规范中的一些概念和术语被采纳了。本章向您介绍使用核心蓝牙框架开发优秀应用程序所需了解的关键术语和概念。中心和外围设备及其在蓝牙通信中的作用:
-有两个主要的参与者参与所有的蓝牙低能量通信:中央和外围。基于某种程度上传统的客户机-服务器体系结构,外围设备通常具有其他设备所需的数据。中心通常使用外围设备提供的信息来完成某些特定的任务。例如,如图1-1所示,心率监护仪可能有一些有用的信息,你的Mac或iOS应用程序可能需要这些信息,以便以用户友好的方式显示用户的心率
- Central发现并连接到广告的外围设备
- 外围设备以广告包的形式广播它们拥有的一些数据。广告包是一个相对较小的数据包,其中可能包含有关外围设备必须提供的有用信息,例如外围设备的名称和主要功能。例如,数字恒温器可能会宣传它提供房间的当前温度。在低能耗蓝牙中,广告是外围设备显示其存在的主要方式。
- 另一方面,中央计算机可以扫描和监听任何外围设备,这些设备是它感兴趣的广告信息,
- Central发现并连接到广告的外围设备
外设的数据结构
- 连接到外围设备的目的是开始探索它所提供的数据并与其交互。然而,在你做这件事之前,先了解一下外设的数据是如何构成的。外围设备可以包含一个或多个服务,或提供有关其连接信号强度的有用信息。服务是数据和相关行为的集合,用于实现设备(或设备的一部分)的功能或特性。
- 例如,心率监视器的一项服务可能是从监视器的心率传感器暴露心率数据。服务本身由特征或包含的服务(即对其他服务的引用)组成。特征提供有关外围设备服务的更多详细信息。例如,刚刚描述的心率服务可以包含描述设备的心率传感器的预期身体位置的一个特征和传输心率测量数据的另一个特征。图1-3说明了心率监测器的一种可能的服务结构和特性
Central在外围设备上探索并与数据交互
- 在中心成功地建立了与外围设备的连接后,它可以发现外围设备必须提供的全部服务和特性(广告数据可能只包含可用服务的一小部分)。
- 阅读或写入的中心服务的值也可以通过一个中心服务或写入服务的特点进行互动。例如,你的应用程序可以从数字恒温器请求当前的室温,也可以为恒温器提供一个设置房间温度的值。
如何表示中心、外围设备和外围数据
- 蓝牙低能量通信中的主要参与者和数据以简单、直接的方式映射到核心蓝牙框架上。
- 中央侧的对象: 当您使用本地中心与远程外围设备交互时,您正在蓝牙低能耗通信的中央端执行操作。除非您正在设置一个本地外围设备并使用它来响应中心的请求,否则大多数蓝牙事务都将在中心端进行
本地中心和远程外围设备
- 在中心端,本地中央设备由CBCentralManager对象表示,管理已发现或连接的远程外围设备(由CBPeripheral对象表示),包括扫描、发现和连接到广告外围设备
远程外设的数据由CBService和CBCharacteristic对象表示
- 当您与远程外围设备(由CBPeripheral对象表示)上的数据交互时,您正在处理它的服务和特性。在蓝牙核心框架中,远程外设的服务由CBService对象表示。类似地,远程外设服务的特征由CBCharacteristic对象表示。图1-5说明了远程外设服务的基本结构和特点。
外围的对象
- 从macos10.9和ios6开始,Mac和iOS设备可以充当蓝牙低能耗外围设备,为其他设备提供数据,包括其他Mac、iPhone和iPad设备。设置设备以实现外围角色时,您正在蓝牙低能量通信的外围设备上执行操作。
- 在外围设备方面,本地外围设备由
CBPeripheralManager
对象表示。这些对象用于管理本地外围设备的服务和特性数据库中已发布的服务,并将这些服务通告给远程中央设备(由CBCentral对象表示)。外围管理器对象也用于响应来自这些远程中心的读写请求。
本地外围设备的数据由CBMutableService和CBMutableCharacteristic对象表示
- 当您在本地外围设备(由cbperipherianmanager对象表示)上设置数据并与其交互时,您正在处理其服务和特性的可变版本。在核心蓝牙框架中,本地外围设备的服务由CBMutableService对象表示。类似地,本地外设服务的特征用CBMutableCharacter表示
执行通用的中央角色任务
- 在蓝牙低能耗通信中,实现中心角色的设备执行许多常见任务,例如,发现并连接到可用的外围设备,以及探索和交互外围设备必须提供的数据。实现外围角色的设备还执行许多常见但不同的任务,例如发布和广告服务,以及响应来自连接的中心的读、写和订阅请求
在本章中,您将学习如何使用核心蓝牙框架从中心端执行最常见类型的蓝牙低能耗任务。下面的基于代码的示例将帮助您开发应用程序,以便在本地设备上实现中心角色。具体来说,您将学习如何:
- 启动中央管理器对象
- 发现并连接到正在播放广告的外围设备
- 连接到外围设备后,可以浏览该设备上的数据
- 向外设服务的特征值发送读写请求
- 订阅要在更新时通知的特征值启动中央经理
因为CBCentralManager对象是本地中央设备的核心蓝牙面向对象表示,所以在执行任何蓝牙低能耗事务之前,您需要分配和初始化一个中央管理器实例。通过调用中央管理器的
initWithDelegate:queue:options:
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
在本例中,self被设置为接收任何中心角色事件的委托。通过将调度队列指定为nil,中央管理器使用主队列分派中心角色事件。创建中央管理器时,中央管理器将调用其委托对象的centralManagerDidUpdateState:方法.
发现正在做广告的外围设备
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
- 注意:如果为第一个参数指定nil,则中央管理器将返回所有发现的外围设备,而不管它们支持的服务是什么。在实际的应用程序中,通常指定一个CBUUID对象数组,每个对象代表外围设备正在发布的服务的通用唯一标识符(UUID)。当您指定一组服务uuid时,中央管理器只返回公布这些服务的外围设备,允许您只扫描您可能感兴趣的设备。
- 扫描到设备
- 注意:如果为第一个参数指定nil,则中央管理器将返回所有发现的外围设备,而不管它们支持的服务是什么。在实际的应用程序中,通常指定一个CBUUID对象数组,每个对象代表外围设备正在发布的服务的通用唯一标识符(UUID)。当您指定一组服务uuid时,中央管理器只返回公布这些服务的外围设备,允许您只扫描您可能感兴趣的设备。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered %@", peripheral.name);
self.discoveredPeripheral = peripheral;
...- 如果您希望连接到多个设备,您可以保留一系列已发现的外围设备。**在任何情况下,一旦你找到所有你感兴趣的外围设备,停止扫描其他设备,以节省电力**.`[myCentralManager stopScan];
发现后连接到外围设备
[myCentralManager connectPeripheral:peripheral options:nil];
- 如果连接请求成功,中央管理器将调用centralManager:连接外围设备:其委托对象的方法。在开始与外围设备交互之前,请设置其委托以确保代理接收到适当的回调:
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Peripheral connected");
peripheral.delegate = self;
...
`
- 发现连接到的外围设备的服务
- 在建立了与外围设备的连接之后,您可以浏览它的数据。探索外设所能提供的第一步是发现其可用的服务。因为外设可以公布的数据量有大小限制,您可能会发现外设的服务比它(在其广告数据包中)要多。您可以通过调用外围设备的discoverServices:方法来发现外围设备提供的所有服务,如[peripheral discoverServices:nil];
- 注意:在实际的应用程序中,通常不会将nil作为参数传入,因为这样做会返回外围设备上可用的所有服务。因为外围设备可能包含的服务比您感兴趣的要多得多,因此发现所有这些服务可能会浪费电池寿命,而且是不必要的时间浪费。相反,您通常会指定您已经知道有兴趣发现的服务的uuid,
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@", service);
...
}
...
发现服务的特征:
- 当你找到一个你感兴趣的服务时,下一步要探索的是外围设备所能提供的是发现该服务的所有特征。发现服务的所有特征就像调用外围设备的
discoverCharacteristics:forService:
一样简单材料:服务:方法,指定适当的服务,如下所示: - [peripheral discoverCharacteristics:nil forService:interestingService];, 同样尽量不要传入nil
- 服务发现错误的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic %@", characteristic);
...
}
...- 当你找到一个你感兴趣的服务时,下一步要探索的是外围设备所能提供的是发现该服务的所有特征。发现服务的所有特征就像调用外围设备的
检索特征值: 特征包含一个表示外围设备服务信息的单个值。例如,健康温度计服务的温度测量特性可能具有指示以摄氏度为单位的温度的值。您可以通过直接读取或订阅特征来检索特征值。
- [peripheral readValueForCharacteristic:interestingCharacteristic];
- 当您试图读取特征值时,外围设备调用peripheral:didUpdateValueForCharacteristic:error::其委托对象的方法来检索值。如果成功检索到值,则可以通过特征的value属性访问它,如下所示:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
NSData *data = characteristic.value;
// parse the data as needed
...- 并非所有特征都是可读的。通过检查特征的properties属性是否包含CBCharacteristicPropertyRead常量,可以确定特征是否可读。如果试图读取不可读的特征值,则peripheral:didUpdateValueForCharacteristic:error:delegate方法返回适当的错误。
订阅特征值
- 虽然使用readValueForCharacteristic:方法读取特征值对静态值是有效的,但它不是检索动态值的最有效方法。检索随时间变化的特征值,例如通过订阅心率。当您订阅一个特征值时,当值发生变化时,您会收到来自外围设备的通知。
- 您可以通过调用外围设备的
setNotifyValue:forCharacteristic:
用于特征:方法,将第一个参数指定为YES,如下所示: - [peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
- 当您订阅(或取消订阅)特征值时,外围设备调用
peripheral:didUpdateNotificationStateForCharacteristic:error:
其委托对象的方法。如果订阅请求由于任何原因失败,可以实现此委托方法来访问错误原因,如下例所示:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error changing notification state: %@",
[error localizedDescription]);
}
...写特征值:
- 您可能需要为恒温器提供一个设置房间温度的值。如果一个特征的值是可写的,那么可以通过调用外围设备的w来用数据(NSData的实例)来写入它的
writeValue:forCharacteristic:type:
方法,如下所示:
NSLog(@"Writing value for characteristic %@", interestingCharacteristic); [peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic
type:CBCharacteristicWriteWithResponse];- 写入特征值时,指定要执行的写入类型。在上面的示例中,写入类型是CBCharacteristicWriteWithResponse,它指示外围设备通过调
peripheral:didWriteValueForCharacteristic:error:
其委托对象的方法。您可以实现此委托方法来处理错误条件,如下例所示:
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error writing characteristic value: %@",
[error localizedDescription]);
}
...- 如果将写入类型指定为
CBCharacteristicWriteWithoutResponse
,则将尽最大努力执行写入操作,并且既不保证也不报告传递。外围设备不调用任何委托方法。
- 您可能需要为恒温器提供一个设置房间温度的值。如果一个特征的值是可写的,那么可以通过调用外围设备的w来用数据(NSData的实例)来写入它的
执行通用的外设角色任务
- 在上一章中,您学习了如何从中心端执行最常见的蓝牙低能耗任务。在本章中,您将学习如何使用核心蓝牙框架从外围设备端执行最常见类型的蓝牙低能耗任务。下面的基于代码的示例将帮助您开发应用程序,以便在本地设备上实现外围角色。具体来说,您将学习如何:
- 启动外围设备管理器对象
- 在本地外围设备上设置服务和特性
- 将服务和特性发布到设备的本地数据库
- 宣传你的服务
- 响应连接中心的读写请求
- 向订阅的中心发送更新的特征值
- 本章中的代码示例既简单又抽象;您可能需要进行适当的更改才能将它们合并到您的实际应用程序中。有关在本地设备上实现外围设备角色的更高级主题(包括提示、技巧和最佳实践)将在后面的章节“核心蓝牙”中介绍
启动外围设备管理器
- 在本地设备上实现外围设备角色的第一步是分配和初始化外围设备管理器实例(由CBPeripheralManager对象表示)。通过调用CbPeripherManager类的方法
initWithDelegate:queue:options
,如下所示:
myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
- 在本例中,self被设置为接收任何外围角色事件的委托。当您将调度队列指定为nil时,外围设备管理器使用主队列调度外围角色事件。
- 创建外围设备管理器时,外围设备管理器调用其委托对象的外围设备管理器didupdatestate:方法。您必须实现此委托方法,以确保支持蓝牙低能耗,并且可以在本地外围设备上使用。有关如何实现此委托方法的详细信息
- 设置服务和特征值
服务和特征由uuid标识:
- 外围设备的服务和特性由128位蓝牙专用uuid标识,这些uuid在核心蓝牙框架中由CBUUID对象表示。虽然并非所有标识服务或特征的uuid都是由蓝牙特殊兴趣组(SIG,Special Interest Group)预定义的,但是为了方便起见,Bluetooth SIG定义并发布了许多常用的uuid,这些uuid被缩短为16位。例如,Bluetooth SIG已将识别心率服务的
16位UUID预定义为180D
。此UUID从其等效的128位UUID 0000180D-0000-1000-8000-00805F9B34FB缩短,后者基于Bluetooth 4.0规范第3卷F部分第3.2.1节中定义的蓝牙基本UUID。 - CBUUID类提供了工厂方法,使开发应用程序时更容易处理长uuid。例如,不必在代码中传递心率服务的128位UUID的字符串表示形式,而只需使用UUIDWithString方法从服务的预定义16位UUID创建CBUUID对象,如下所示:
- CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
- 当您从预定义的16位UUID创建CBUUID对象时,Core Bluetooth预先用蓝牙基本UUID填充其余128位UUID。
为定制服务和特性创建自己的uuid
- 您可能拥有预定义的蓝牙uuid无法识别的服务和特性。如果需要,则需要生成自己的128位uuid来识别它们
- 使用命令行实用程序uuidgen可以轻松生成128位uuid。首先,在终端打开一个窗口。接下来,对于需要用UUID标识的每个服务和特征,在命令行中键入uuidgen,以接收一个以连字符标点的ASCII字符串形式的唯一128位值,如下例所示:
$ uuidgen 71DA3FD1-7E10-41C1-B16F-4430B506CDE7
- CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
- 外围设备的服务和特性由128位蓝牙专用uuid标识,这些uuid在核心蓝牙框架中由CBUUID对象表示。虽然并非所有标识服务或特征的uuid都是由蓝牙特殊兴趣组(SIG,Special Interest Group)预定义的,但是为了方便起见,Bluetooth SIG定义并发布了许多常用的uuid,这些uuid被缩短为16位。例如,Bluetooth SIG已将识别心率服务的
建立你的服务和特征树
- 在您拥有服务和特征的uuid(由CBUUID对象表示)之后,您就可以创建可变的服务和特征,并以上面描述的树状方式组织它们。例如,如果您有一个特征的UUID,那么可以通过调用ini来创建一个可变的特征
initWithType:properties:value:permissions:
,CBMutableCharacteristic类的方法,如下所示:Objective-C myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable]; - 创建可变特征时,可以设置其属性、值和权限。您设置的属性和权限决定了特征的值是可读还是可写的,以及连接的中心是否可以订阅特征的值。在该示例中,特征值被设置为可由连接的中心读取。
-注意:
- 在您拥有服务和特征的uuid(由CBUUID对象表示)之后,您就可以创建可变的服务和特征,并以上面描述的树状方式组织它们。例如,如果您有一个特征的UUID,那么可以通过调用ini来创建一个可变的特征
发布您的服务和特性
- 在构建了服务和特征树之后,在本地设备上实现外围角色的下一步是将它们发布到设备的服务和特征数据库中。使用核心蓝牙框架可以轻松执行此任务。调用
peripheralManager:didAddService:error:
如下所示:
- 在构建了服务和特征树之后,在本地设备上实现外围角色的下一步是将它们发布到设备的服务和特征数据库中。使用核心蓝牙框架可以轻松执行此任务。调用
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service
error:(NSError *)error {
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
...
```
![](media/16051920928247/16052037057820.jpg)- 在本地设备上实现外围设备角色的第一步是分配和初始化外围设备管理器实例(由CBPeripheralManager对象表示)。通过调用CbPeripherManager类的方法
- 宣传您的服务
- 当您将服务和特征发布到设备的服务和特征数据库中后,您就可以开始向任何可能正在监听的中心广播其中一些服务和特征。如以下示例所示,您可以通过调用CBPeriphereManager类的startAdvertising:方法,传递播发数据的字典(NSDictionary的实例)来播发某些服务
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
- 在本例中,字典中唯一的键
CBAdvertisementDataServiceUuidKey
需要CBUUID对象的数组(NSArray的实例)作为值,该对象表示要播发的服务的UUID。您可以在广告数据字典中指定的可能键在CBCentralManagerDelegate Protocol Reference中的“广告数据检索密钥”中描述的常量中进行了详细说明。也就是说,外围管理器对象只支持其中两个键:CBAdvertisementDataLocalNameKey
和CBAdvertisementDataServiceUuidKey。
当您开始在本地外围设备上播发某些数据时,外围设备管理器将调用
peripheralManagerDidStartAdvertising:error:
其委托对象的方法。如果发生错误并且无法播发您的服务,请实现此委托方法以访问错误原因,如下所示:- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...响应中心的读写请求
- 连接到一个或多个远程中心后,您可以开始接收来自它们的读或写请求。当你这样做时,一定要以适当的方式回应这些要求。下面的示例描述了如何处理此类请求。
- 当连接的中心请求读取您的某个特征值时,外围设备管理器会调用外围设备
peripheralManager:didReceiveReadRequest:
其委托对象的方法。delegate方法以CBATTRequest对象的形式向您传递请求,该对象具有许多属性,您可以使用这些属性来完成请求。 例如,当您收到读取特征值的简单请求时,可以使用从delegate方法接收到的CBATTRequest对象的属性来确保设备数据库中的特征与远程中心在原始读取请求中指定的特征相匹配。可以开始实现此委托方法,如下所示:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
...- 如果特征的uuid匹配,下一步是确保读取请求没有请求从超出特征值边界的索引位置进行读取。如下面的示例所示,可以使用**CBAttrRequest**对象的offset属性来确保读取请求不会试图读取超出适当边界的内容: ```Objective-C
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}假设已验证请求的偏移量,现在将请求的characteristic属性(其值默认为nil)的值设置为您在本地外围设备上创建的特征值,同时考虑读取请求的偏移量:
request.value = [myCharacteristic.value subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];设置该值后,响应远程中心以指示请求已成功完成。通过呼叫应答器来执行此操作任务:通过
respondToRequest:withResult:
,将请求(更新的值)和请求的结果传回,如下所示:[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
同样的,有时也会收到多个特征值的读写请求,
- [myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
- [myPeripheralManager respondToRequest:[requests objectAtIndex:0]
向订阅的中心发送更新的特征值
- 通常,连接的中心将订阅一个或多个特征值,如订阅特征值中所述。当它们这样做时,当它们订阅的特征值发生变化时,您负责向它们发送通知。下面的例子描述了如何。
当连接的中心订阅您的某个特征值时,外围设备管理器调用
peripheralManager:central:didSubscribeToCharacteristic:
其委托对象的方法:- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
...- 使用上面的delegate方法作为提示,开始发送中心更新值,接下来,获取特征的更新值并通过调用`updateValue:forCharacteristic:onSubscribedCentrals:`。 ```Objective-C
NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];
```
- 当指定为nil的时候会发送给所有的订阅者,
- 当返回为NO,则说明传送队列已满,当传送对类有空闲空间的时候执行`peripheralManagerIsReadyToUpdateSubscribers:`
- 在此方法中再次发送特征数据` updateValue:forCharacteristic:onSubscribedCentrals:`
iOS应用的核心蓝牙后台处理
- 对于iOS应用程序,了解应用程序是在前台运行还是在后台运行至关重要。应用程序在后台的行为必须与在前台不同,因为iOS设备上的系统资源更为有限。有关iOS上后台操作的全面讨论,请参阅iOS应用程序内后台执行编程指南。
- 默认情况下,当应用程序处于后台或处于挂起状态时,中央和外围设备的许多常见核心蓝牙任务都将被禁用。也就是说,你可以声明你的应用程序支持核心的蓝牙后台执行模式,允许你的应用程序从挂起状态唤醒,以处理某些与蓝牙相关的事件。即使你的应用程序不需要全套后台处理支持,它仍然可以要求系统在发生重要事件时发出警报。
即使你的应用支持一种或两种核心蓝牙后台执行模式,它也不能永远运行。在某个时候,系统可能需要终止你的应用程序来释放当前前台应用程序的内存,从而导致任何活动或挂起的连接丢失。从iOS7开始,核心蓝牙支持保存中央和外围管理器对象的状态信息,并在应用程序启动时恢复状态。您可以使用此功能来支持涉及蓝牙设备的长期操作。
前台运行
- 与大多数iOS应用程序一样,除非您请求执行特定后台任务的权限,否则您的应用程序在进入后台状态后很快就会转换为挂起状态。处于挂起状态时,你的应用程序无法执行与蓝牙相关的任务,也不会意识到任何与蓝牙相关的事件,直到它恢复到前台。
- 在中心端,只有前台的应用程序没有声明支持任何一种核心的蓝牙后台执行模式,在后台或挂起时无法扫描和发现广告外设。在外围设备方面,广告被禁用,任何试图访问应用程序发布服务的动态特征值的中心都会收到一个错误。
- 根据用例的不同,此默认行为会以多种方式影响应用程序。例如,假设您正在与当前连接的外设上的数据交互。现在假设你的应用程序进入暂停状态(例如,因为用户切换到另一个应用程序)。如果在应用程序挂起时与外围设备的连接中断,则在应用程序恢复到前台之前,您不会意识到发生了任何断开连接的情况。
利用外围连接选项
- 只有在前台的应用程序处于挂起状态时发生的所有与蓝牙相关的事件都由系统排队,只有当应用程序恢复到前台时,才会将这些事件传递给应用程序。也就是说,核心蓝牙提供了一种在某些中心角色事件发生时提醒用户的方法。然后,用户可以使用这些警报来决定某个特定事件是否值得将应用程序带回前台。
- 在调用connectP时,您可以通过包含以下外围设备连接选项之一来利用这些警报
connectPeripheral:options:
用于连接到远程外围设备的CBCentralManager类的方法:
后台执行模式
- 如果您的应用程序需要在后台运行以执行某些与蓝牙相关的任务,则它必须在其信息属性列表中声明它支持核心蓝牙后台执行模式(信息列表)文件。当你的应用程序声明这一点时,系统会将其从挂起状态唤醒,以允许它处理与蓝牙相关的事件。这种支持对于与蓝牙低能耗设备(如心率监测器)交互的应用程序非常重要。
- 应用程序可以声明两种核心蓝牙后台执行模式,一种用于实现中心角色的应用程序,另一种用于实现外围角色的应用程序。如果你的应用实现了这两种角色,它可能会声明它支持两种后台执行模式。核心Bluetooth后台执行模式是通过将UIBackgroundModes键添加到信息列表文件并将密钥的值设置为包含以下字符串之一的数组:
bluetooth central
应用程序使用核心蓝牙框架与蓝牙低能耗外围设备进行通信。bluetooth-peripheral
应用程序使用核心蓝牙框架共享数据。
蓝牙中央后台执行模式
当实现中心角色的应用程序在其信息列表文件,核心蓝牙框架允许你的应用程序在后台运行,以执行某些与蓝牙相关的任务。当你的应用程序处于后台时,你仍然可以发现并连接到外围设备,探索并与外围数据交互。此外,当调用任何CBCentralManagerDelegate或CBPeripherDelegate委托方法时,系统会唤醒您的应用程序,从而允许您的应用程序处理重要的中心角色事件,例如连接建立或断开时,当外围设备发送更新的特征值时,当中央经理的状态发生变化时。
虽然你可以在应用程序处于后台时执行许多与蓝牙相关的任务,但请记住,应用程序在后台时扫描外围设备的操作方式与应用程序在前台时的操作方式不同。尤其是,当你的应用在后台扫描设备时:
- CBCentralManagerCanOptionalLowDuplicateSkey扫描选项键被忽略,广告外围设备的多个发现合并到单个发现事件中。
- 如果正在扫描外围设备的所有应用程序都在后台,则中央设备扫描广告包的时间间隔会增加。因此,可能需要更长的时间才能找到广告外围设备。这些更改有助于最大限度地减少无线电使用量并提高iOS设备的电池寿命。
蓝牙外设后台执行模式
- 除了允许唤醒应用程序以处理来自连接的Central的读、写和订阅请求外,核心蓝牙框架还允许应用程序在后台状态下进行广告。也就是说,你应该知道,当你的应用程序在后台时,广告的运作方式与你的应用程序在前台时的不同。尤其是,当你的应用程序在后台做广告时:
- CBAdvertisementDataLocalNameKey播发密钥被忽略,并且外围设备的本地名称不会被播发。
- CBAdvertisementDataServiceUUIDsKey播发密钥的值中包含的所有服务UUID都放在一个特殊的“溢出”区域;只有显式扫描它们的iOS设备才能发现它们
- 如果所有正在做广告的应用程序都在后台,则外围设备发送广告包的频率可能会降低。
- 除了允许唤醒应用程序以处理来自连接的Central的读、写和订阅请求外,核心蓝牙框架还允许应用程序在后台状态下进行广告。也就是说,你应该知道,当你的应用程序在后台时,广告的运作方式与你的应用程序在前台时的不同。尤其是,当你的应用程序在后台做广告时:
明智地使用后台执行模式
- 虽然声明你的应用支持一个或两个核心蓝牙后台执行模式可能是必要的,以满足特定的用例,你应该始终负责任地执行后台处理。因为执行许多与蓝牙相关的任务都需要主动使用iOS设备的车载收音机,而收音机的使用又会对iOS设备的电池寿命产生不利影响,请尽量减少您在后台所做的工作。被唤醒的任何蓝牙相关事件的应用程序应该处理这些事件并尽快返回,以便应用程序可以再次暂停。
- 任何声明支持任何一种核心蓝牙后台执行模式的应用程序都必须遵循以下几个基本准则:
- 应用程序应该是基于会话的,并提供一个接口,允许用户决定何时开始和停止蓝牙相关事件的传递。
- 当被唤醒时,应用程序有大约10秒的时间来完成一项任务。理想情况下,它应该尽快完成任务,并允许自己再次被挂起。在后台执行时间过长的应用程序可能会被系统限制或终止。
- 应用程序不应该利用被唤醒的机会来执行与应用程序被系统唤醒的原因无关的无关任务。
在后台执行长期操作
- 有些应用可能需要使用核心蓝牙框架在后台执行长期操作。举个例子,假设您正在为一个iOS设备开发一个家庭安全应用程序,该设备通过门锁(配备蓝牙低能耗技术)进行通信。应用程序和锁相互作用,当用户离开家时自动锁门,当用户返回时,当应用程序在后台时,自动开锁。当用户离开家时,iOS设备最终可能会超出锁定范围,导致与锁的连接丢失。此时,应用程序可以简单地调用
connectPeripheral:options:
,CBCentralManager类的方法,并且由于连接请求没有超时,当用户返回家中时,iOS设备将重新连接。现在假设用户离家几天。如果应用程序在用户不在时被系统终止,当用户返回家时,应用程序将无法重新连接到锁上,用户可能无法打开门。对于这样的应用程序来说,能够继续使用核心蓝牙来执行长期操作(如监视活动和挂起的连接)是至关重要的。
- 有些应用可能需要使用核心蓝牙框架在后台执行长期操作。举个例子,假设您正在为一个iOS设备开发一个家庭安全应用程序,该设备通过门锁(配备蓝牙低能耗技术)进行通信。应用程序和锁相互作用,当用户离开家时自动锁门,当用户返回时,当应用程序在后台时,自动开锁。当用户离开家时,iOS设备最终可能会超出锁定范围,导致与锁的连接丢失。此时,应用程序可以简单地调用
状态保护与恢复
- 因为核心蓝牙内置了状态保存和恢复功能,因此你的应用程序可以选择使用此功能,要求系统保留应用程序中央和外围管理器的状态,并代表他们继续执行某些与蓝牙相关的任务,即使你的应用程序不再运行。当其中一个任务完成后,系统会将应用程序重新启动到后台,并为应用程序提供恢复其状态和适当处理事件的机会。在上述家庭安全应用程序的情况下,系统将监视连接请求,并重新启动该应用程序以处理
centralManager:didConnectPeripheral:
当用户返回原点并且连接请求完成时,委派回调 - 核心蓝牙支持实现中心角色、外围角色或两者兼而有之的应用程序的状态保存和恢复。当您的应用实现中心角色并添加对状态保存和恢复的支持时,系统会在系统即将终止应用程序以释放内存时保存中央管理器对象的状态(如果您的应用程序有多个中央管理器,您可以选择希望系统跟踪哪些中央管理器)。
- 特别是,对于给定的CBCentralManager对象,系统会跟踪:
- 中央管理器正在扫描的服务(以及扫描开始时指定的任何扫描选项)
- 中央管理器正试图连接或已连接到的外围设备
- 中央经理所订阅的特点
- 实现外围角色的应用程序同样可以利用状态保护和恢复。对于CBPeripherinerManager对象,系统会跟踪:
- 外围设备管理器正在发布的数据
- 外设管理器发布到设备数据库的服务和特性
- 你的特征值的中心点
- 当系统将应用程序重新启动到后台时(例如,因为发现了应用程序正在扫描的外围设备),您可以重新初始化应用程序的中央和外围管理器并恢复其状态。以下部分详细描述如何在应用程序中利用状态保存和恢复。
- 因为核心蓝牙内置了状态保存和恢复功能,因此你的应用程序可以选择使用此功能,要求系统保留应用程序中央和外围管理器的状态,并代表他们继续执行某些与蓝牙相关的任务,即使你的应用程序不再运行。当其中一个任务完成后,系统会将应用程序重新启动到后台,并为应用程序提供恢复其状态和适当处理事件的机会。在上述家庭安全应用程序的情况下,系统将监视连接请求,并重新启动该应用程序以处理
增加对状态保护和恢复的支持
核心蓝牙中的状态保存和恢复是一个可选功能,需要应用程序的帮助才能工作。您可以通过以下过程在应用程序中添加对此功能的支持:
(必需)在分配和初始化中央或外围管理器对象时选择状态保留和恢复。这一步骤在状态保护和恢复的选择中描述。
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil
options:@{ CBCentralManagerOptionRestoreIdentifierKey:
@"myCentralManagerIdentifier" }];(必需)在系统重新启动应用程序后,重新初始化任何中央或外围管理器对象。这一步在重新调整您的中央和外围经理中描述。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *centralManagerIdentifiers =
launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];
//UIApplicationLaunchOptionsBluetoothPeripheralsKey(必需)执行适当的恢复委派方法。在实现适当的恢复委托方法中描述了此步骤。
```Objective-C
- (void)centralManager:(CBCentralManager *)central
willRestoreState:(NSDictionary *)state {
NSArray *peripherals =
state[CBCentralManagerRestoredStatePeripheralsKey];
```(可选)更新中央和外围管理器的初始化过程。更新初始化过程中描述了此步骤。
```Objective-C NSUInteger serviceUUIDIndex =
[peripheral.services indexOfObjectPassingTest:^BOOL(CBService *obj,
NSUInteger index, BOOL *stop) {
return [obj.UUID isEqual:myServiceUUIDString];
}];
if (serviceUUIDIndex == NSNotFound) {
[peripheral discoverServices:@[myServiceUUIDString]];
...
```
与远程外围设备交互的最佳实践
- 注意无线电的使用和功率消耗
- 只在需要时扫描设备
- 仅在必要时指定CBCentralManagerScanOptionAllowDuplicatesKey选项
- 明智地探索外围设备的数据
[peripheral discoverServices:@[firstServiceUUID, secondServiceUUID]];
- 订阅经常变化的特征值
- 当您需要断开所有数据时,请断开与设备的连接
[myCentralManager cancelPeripheralConnection:peripheral];
- 重新连接到外围设备
- 你可能不想每一次都用它来发现外设。相反,您可能需要先尝试使用其他选项重新连接。如图5-1所示,一个可能的重新连接工作流可能是按照上面列出的顺序尝试这些选项中的每一个。
将本地设备设置为外围设备的最佳实践
- 广告考虑因素
- 关于广告数据的限制:
- 尽管广告包通常可以包含有关外围设备的各种信息,但您可以只公布设备的本地名称和要公布的任何服务的uuid。也就是说,在创建广告字典时,只能指定以下两个键:CBAdvertisementDataLocalNameKey和CBAdvertisementDataServiceUuidKey。如果指定任何其他键,则会收到一个错误。
- 在发布数据时,您可以使用的空间也有限制。当你的应用程序处于前台时,它可以在初始广告数据中为两个支持的广告数据键的任意组合使用最多28字节的空间。如果这个空间用完了,扫描响应中还有另外10个字节的空间只能用于本地名称。任何不适合分配空间的服务uuid都会被添加到一个特殊的“溢出”区域;它们只能被显式扫描的iOS设备发现。当你的应用程序在后台时,不会公布本地名称,所有服务uuid都放在溢出区。
- 关于广告数据的限制:
- 只在需要时公布数据
- 让用户决定何时做广告
- 配置您的特征
- 允许连接的中心订阅您的特征
- 防止敏感特征值被未配对的中心访问
- 配置特征以支持通知
- 对于频繁变动的值通过订阅来处理
- 需要成对连接才能访问敏感数据