执行通用中心角色任务
在BLE通信中实现中心角色的设备执行一些通用任务,例如:搜索并连接设备,扫描并且和提供数据的周边交互。对比着看,实现周边角色的设备也在执行一些通用的,但是不同的任务,例如:公开并广告服务,响应连接的中心设备的读写,订阅(read,write,subscription)请求。
在这一章,你会学到怎样通过CoreBluetooth执行中心角色这边的最基本的BLE任务。下面的示例代码可以帮助你在设备上开发app实现这一功能。特别地,你将学习怎样:
∙开始一个中心管理对象
∙发现并且连接广告中的设备
∙在连接设备之后扫描数据。
∙针对设备服务的特征发送读写请求
∙订阅支持notified(在数据更新时会主动上报)的特征。
在下一章,你会学习怎样在你的设备上通过开发实现一个周边角色。
本章的示例代码简单抽象;当你把它们迁入你的app中时可能需要做一点修改。更多和中心角色相关的高级课题(像贴士,技巧和最佳练习)在后面的章节将概括到。iOS应用CoreBluetooth的后台执行(Core Bluetooth Background Processing for iOS Apps)和与远程设备通讯的最佳训练(Best Practices for Interacting with a Remote Peripheral Device.)
开始一个中心管理对象
CBCentralManager对象是本地中心设备的CoreBluetooth面向对象的象征,在你可以执行任何BLE痛信之前你必须分配并且初始化一个中心管理者的实例。你可以通过调用CBCentralManager类中的initWithDelegate:queue:options: 方法来实例化一个中心管理者,就像这样:
myCentralManager =
[[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
在这个例子中,self作为接受中心角色事件的委托。指定dispatchQueue为nil,中心对象使用main queue派遣中心角色事件。
当你创建了中心管理者。中心管理者将为它的委托对象调用centralManagerDidUpdateState:方法。你必须实现这个委托方法用于验证在这个中心设备上对BLE的支持是否完备。不会实现这个委托方法?请看 CBCentralManagerDelegate 协议参考(CBCentralManagerDelegate Protocol Reference.)
发现正在广告的周边设备
你要执行的中心这边的任务第一任务就是发现周围可以被你连接的周边设备。像之前提到的中心发现并连接广告中的周边设备( Centrals Discover and Connect to Peripherals That Are Advertising),广告是周边设备证明自己存在的基础方式。你可以通过CBCentralManager类中的scanForPeripheralsWithServices:options: 方法发现周围正在广告的周边设备。就像这样:
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
注意:如果第一个参数你传了nil,中心管理者会返回所有被发现的周边,不管它们所支持的服务是什么。在实际的app中,你可以指定一个 CBUUID 对象的数组,每个CBUUID对象对应周边设备正在广告的服务的唯一表示符(UUID)。当你指定了服务的UUID,中心管理者将只返回正在广告这些服务的周边,帮助你只搜索你感兴趣的设备。
UUIDs,CBUUID的对象和它们对应,更多相关讨论请看 Services and Characteristics Are Identified by UUIDs.
在你调用 scanForPeripheralsWithServices:options: 发现可用的周边之后,中心管理者会为他的委托对象调用 centralManager:didDiscoverPeripheral:advertisementData:RSSI:方法返回它所发现的每一台周边。所有周边被发现的周边被 CBPeripheral 对象抽象返回。像下面展示的这样,你可以实现这个委托方法来排列被发现的周边:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered %@", peripheral.name);
...
当你发现了你想要连接的周边设备,停止扫描来节省电量。
[myCentralManager stopScan];
NSLog(@"Scanning stopped");
连接你所发现的周边设备(Connecting to a Peripheral Device After You’ve Discovered It)
在你发现了你感兴趣的正在广告服务的周边设备之后,你调用CBCentralManager中的connectPeripheral:options: 方法去请求连接周边。简单调用这个方法并且指定你想连接的已被发现的周边,就像这样:
[myCentralManager connectPeripheral:peripheral options:nil];
假设连接请求成功了,中心管理者将为它的委托对象调用 centralManager:didConnectPeripheral:,你可以通过实现这个方法来打印信息确定连接成功。想下面写的这样:
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Peripheral connected");
...
在你和周边通信之前,你应该设置周边的委托以确保它可以收到合适的回调,像这样:
peripheral.delegate = self;
发现你连接的周边的服务(Discovering the Services of a Peripheral That You’re Connected To)
和周边建立了连接之后,你可以开始探测它的数据,探索周边提供的第一步是发现它支持的服务,因为这里对周边的数据量有大小的限制,你可以发现一个周边广告好几个服务(在他的广告包里)。你可以通过调用CBPeripheral类中的discoverServices: 方法发现周边的所有服务,像这样:
[peripheral discoverServices:nil];
注意:在实际的app中,你不太可能会发nil作为参数,因为这样会返回周边上支持的所有的服务。由于一个周边上可能含有很多你不太感兴趣的服务,搜索它们只是浪费电池生命和搜索的时间。大多数情况下,你可以指定搜索你所感兴趣并且早就知道的服务的UUIDs,像明智地探索周边数据(Explore a Peripheral’s Data Wisely)里介绍的那样。
当指定的服务被发现时,周边(你连接的那个周边对象)会为他的委托对象调用peripheral:didDiscoverServices: 方法。Core Bluetooth创建了一个CBService对象的数组-每一个对象对应周边上发现的服务。就像下面展示的这样,你可以实现这个委托方法去接收这个已发现的服务数组。
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@", service);
...
}
...
发现服务的特征(Discovering the Characteristics of a Service)
当你发现了你感兴趣的服务之后,下一步就是探索这个周边的所有特征查看它提供了啥。发现服务的特征很简单,指定对应的服务,调用CBPeriphral的discoverCharacteristics:forService:方法就好了,就像这样:
NSLog(@"Discovering characteristics for service %@", interestingService);
[peripheral discoverCharacteristics:nil forService:interestingService];
注意:在实际开发中,你不太可能在第一个参数传入nil,这样做的话会返回这个服务的所有特征,因为周边的服务可能保护一些你不感兴趣的特征,发现它们在电量和时间上都是浪费,更多条件下,你应该指定搜索你早就知道的感性趣的特征的UUIDs。
在指定服务的特征被发现时,周边会为它的委托对象调用 peripheral:didDiscoverCharacteristicsForService:error:方法。CoreBluetooth创造了一个CBCharacteristic 对象的数组,数组中每一个对象对应相应的特征。下面的例子告诉你该如何实现这个委托方法并且简单打印出被发现的特征。
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic %@", characteristic);
...
}
...
获取特征的值 (Retrieving the Value of a Characteristic)
特征包含一个关于服务的更多信息的简单值。举例:一个健康体温计服务的温度测量值的特征可能表示了一个摄氏温度值。你可以接受这个特征值通过订阅它或者立马解读它。
读特征值 (Reading the Value of a Characteristic)
在找到你感兴趣的服务的特征之后,你可以通过调用CBPeripheral的readValueForCharacteristic:方法并且指定特定的特征来阅读特征值。像这样:
NSLog(@"Reading value for characteristic %@", interestingCharacteristic);
[peripheral readValueForCharacteristic:interestingCharacteristic];
当你准备读一个值时,周边为它的委托对象调用peripheral:didUpdateValueForCharacteristic:error: 方法来接受这个值,如果这个值被成功接收,你可以通过这个值的属性接收它,像这样:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
NSData *data = characteristic.value;
// parse the data as needed
...
注意:并不是所有特征都有可读有效值,你可以通过查看constant常数来判断特征值是否可读,详细信息在 CBCharacteristic类文档(CBCharacteristic Class Reference)中,如果你想读一个不可读的值, peripheral:didUpdateValueForCharacteristic:error:会返回对应的错误。
订阅特征值(Subscribing to a Characteristic’s Value)
通过使用 readValueForCharacteristic: 方法去读一些特征的值可以解决提供一些有效方案,但是对于经常改变的值这个方法便不是很有效—-举例:你的心率随时变化—-你可以通过订阅的方式实时获取它们,在你订阅了特征值之后,周边的特征值在变化时会通过通知的方式让你收到它。
你可以通过CBPeriphral类中的 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]);
}
...
注意:并不是所有的特征的配置都允许你订阅它,你可以通过特征属性的枚举来判断它的设置。详细信息在CBCharacteristic 类相关( CBCharacteristic Class Reference.)中。
在你成功订阅特征值之后,周边设备为你广播这个值只要它发生了变化,每次值变化时,周边为它的委托对象调用peripheral:didUpdateValueForCharacteristic:error:方法。为了接收这个更新的值,你可以像上面在读特征值介绍过的样子实现这个方法。
写特征值(Writing the Value of a Characteristic)
在一些情形下,需要给特征值写一些信息,举例:如果你的app和一个BLE的数字恒温计工作,你可能需要为房间温度设一个数值。如果这个特征是可写的,你可以写一些数据(NSData实例),通过调用CBPeripheral的 writeValue:forCharacteristic:type: 类,像这样:
NSLog(@"Writing value for characteristic %@", interestingCharacteristic);
[peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic
type:CBCharacteristicWriteWithResponse];
当你试图在特征写入值时,指定你要执行哪种写的操作。在上面的例子中,写操作类型是CBCharacteristicWriteWithResponse,这表示周边会让你知道什么时候写成功了。更多CoreBluetooth框架支持的写操作,请查阅CBPeripheral Class Reference.中的CBCharacteristicWriteType 枚举值。
周边会通过调用委托对象的peripheral:didWriteValueForCharacteristic:error: 方法反馈指定为CBCharacteristicWriteWithResponse的写操作。如果因为一写原因写入失败,你可以通过实现这个方法接收信息,像下面的例子:
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error writing characteristic value: %@",
[error localizedDescription]);
}
...
注意:特征可能只允许某些类型的写操作在他们的值上执行。想查看特征值对写操作的支持详情,你可以查看Characteristic Properties 枚举的相关属性。详细信息在 CBCharacteristic Class Reference.中。
执行通用周边角色任务(Performing Common Peripheral Role Tasks)