执行常见的中心角色任务
在蓝牙低能量通信中发挥中心作用的设备执行许多常见任务,例如,发现和连接可用的外围设备,以及探索和与外围设备必须提供的数据交互。实现外围设备角色的设备还执行许多常见但不同的任务,例如发布和广告服务,以及响应连接中心的读、写和订阅请求。
启动中心设备
因为cbCentralManager对象是本地中央设备的核心蓝牙面向对象表示,所以在执行任何蓝牙低能耗事务之前,您可以分配和初始化中央管理器实例。通过调用其initwitdelegate:queue:options:method初始化中央管理器。
myCentralManager =
[[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
在本例中,self被设置为代理以接收任何中心角色事件。通过将调度队列指定为nil,中心管理器使用主队列来调度中心角色事件。
创建中央管理器时,中央管理器调用其委托对象的centralManagerDupdateState:方法。您必须实现此委派方法,以确保支持蓝牙低能耗,并可在中央设备上使用。
发现正在广告的外围设备
一旦初始化,中央管理器的第一个任务就是发现外围设备。正如centrals中所提到的,发现并连接到正在广播的外围设备,外围设备通过广播使它们的存在变得众所周知。您的应用程序通过调用中央管理器的外设扫描服务来发现附近正在进行广告宣传的外设:选项:方法:
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
如果为第一个参数指定了nil,则中心管理器将返回所有发现的外围设备,而不考虑其支持的服务。在真实的应用程序中,通常指定一个cbuuid对象数组,每个对象代表外围设备正在广告的服务的通用唯一标识符(uuid)。当您指定服务UUID数组时,中央管理器只返回通告这些服务的外围设备,允许您只扫描您感兴趣的设备。
每次中央管理器发现外围设备时,它都会调用其委托对象的centralManager:didDiscoverPeripheral:AdvertisementData:rssi:method。新发现的外围设备作为CBPeripheral对象返回。如果计划连接到发现的外围设备,请保持对它的强引用,这样系统就不会解除分配它。下面的示例显示了一个场景,其中使用类属性维护对发现的外围设备的引用
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered %@", peripheral.name);
self.discoveredPeripheral = peripheral;
...
如果您希望连接到多个设备,那么您可以保留一组发现的外围设备。在任何情况下,一旦你找到所有你想连接的外围设备,停止扫描其他设备以节省电力。
[myCentralManager stopScan];
发现后连接到外围设备
在发现您感兴趣的外围设备广告服务后,通过调用中央管理器的ConnectPeripheral:选项:方法,命名要连接到的发现的外围设备,请求连接到该外围设备:
[myCentralManager connectPeripheral:peripheral options:nil];
如果连接请求成功,中央管理器将调用其委托对象的centralManager:didconnectPeripheral:方法。在开始与外围设备交互之前,请设置其委托以确保委托接收到适当的回调:
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Peripheral connected");
peripheral.delegate = self
}
发现连接到的外围设备的服务
建立到外围设备的连接后,可以探索其数据。探索外围设备必须提供的服务的第一步是发现其可用的服务。由于外围设备可以公布的数据量存在大小限制,因此您可能会发现一个外围设备拥有的服务多于它公布的服务(在其广告包中)。您可以通过调用外设的discover services:方法来发现外设提供的所有服务,如下所示:
[peripheral discoverServices:nil];
在真实的应用程序中,通常不将nil作为参数传递,因为这样做会返回外围设备上可用的所有服务。因为外围设备可能包含比您感兴趣的更多的服务,发现所有这些服务可能会浪费电池寿命和不必要的时间使用。相反,您通常指定您已经知道有兴趣发现的服务的UUID
当发现指定的服务时,外围设备(您连接到的CBPeripheral对象)调用其委托对象的peripheral:DidDiscoverServices:method。核心蓝牙创建了一个CBService对象数组,每个对象对应于在外围设备上发现的每个服务。如下图所示,可以实现此委托方法来访问发现的服务数组:
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@", service);
}
}
发现服务的特性
当你找到你感兴趣的服务时,下一步探索外围设备必须提供什么是发现所有服务的特性。发现服务的所有特性与调用外围设备的discoverCharacteristics:forService:method、指定适当的服务一样简单,如下所示:
NSLog(@"Discovering characteristics for service %@", interestingService);
[peripheral discoverCharacteristics:nil forService:interestingService];
在真实的应用程序中,通常不会将nil作为第一个参数传递,因为这样做会返回外围设备服务的所有特性。由于外围设备的服务可能包含比您感兴趣的特性更多的特性,因此发现所有这些特性可能会浪费电池寿命,并且是不必要的时间使用。相反,您通常指定您已经知道有兴趣发现的特征的UUID。
当发现指定服务的特征时,外围设备调用其委托对象的外围设备:DidDiscoveryCharacteristicsForService:错误:方法。核心蓝牙创建了一个cbCharacteristic对象数组,每个被发现的特征对应一个。下面的示例说明如何实现此委托方法,以简单地记录发现的每个特征:
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"Discovered characteristic %@", characteristic);
...
}
...
检索特征值
一个特征包含一个表示外围设备服务信息的值。例如,健康温度计服务的温度测量特性可能具有指示摄氏温度的值。您可以通过直接读取特性值或订阅特性值来检索特性值。
读取特征值
找到感兴趣的服务的特征后,可以通过调用外围设备的readValueForCharacteristic:方法,指定适当的特征来读取特征值,如下所示:
NSLog(@"Reading value for characteristic %@", interestingCharacteristic);
[peripheral readValueForCharacteristic:interestingCharacteristic];
尝试读取特征值时,外围设备调用其委托对象的外围设备:didUpdateValueForCharacteristic:error:method以检索该值。如果成功检索到该值,则可以通过特征的Value属性访问该值,如下所示:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
NSData *data = characteristic.value;
// parse the data as needed
...
并非所有特征都可读。通过检查特性属性是否包括cbCharacteristicPropertyRead常量,可以确定特性是否可读。如果尝试读取不可读的特征值,则peripheral:didUpdateValueForCharacteristic:error:delegate方法返回适当的错误。
订阅特征值
虽然使用readValueForCharacteristic:方法读取特征值对静态值有效,但它不是检索动态值的最有效方法。检索随时间变化的特征值,例如,通过订阅它们来检索心率。当您订阅一个特性的值时,当该值改变时,您会收到来自外围设备的通知。
通过调用外围设备的setNotifyValue:ForCharacteristic:method,将第一个参数指定为yes,可以订阅您感兴趣的特征值,如下所示:
[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
当您订阅(或取消订阅)特征值时,该外围设备将调用其委托对象的外围设备:didUpdateMotificationStateForCharacteristic:error:method。如果订阅请求因任何原因失败,则可以实现此委托方法来访问错误的原因,如下例所示:
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error changing notification state: %@",
[error localizedDescription]);
}
...
并非所有特征都提供订阅。您可以通过检查特性属性是否包含cbCharacteristicPropertyNotify或cbCharacteristicPropertyIndicate常量来确定特性是否提供订阅。
写入数据量
有时候写一个特征的值是有意义的。例如,如果您的应用程序与蓝牙低能量数字恒温器交互,您可能希望为恒温器提供一个设置房间温度的值。如果特征值是可写的,则可以通过调用外设的writeValue:ForCharacteristic:Type:Method,使用数据(nsdata的实例)写入其值,如下所示:
NSLog(@"Writing value for characteristic %@", interestingCharacteristic);
[peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic
type:CBCharacteristicWriteWithResponse];
编写特征值时,指定要执行的写入类型。在上面的示例中,写入类型是cbCharacteristicWriteWithResponse,它指示外围设备通过调用其委托对象的peripheral:didWriteValueForCharacteristic:error:method来让应用程序知道写入是否成功。您可以实现此委托方法来处理错误条件,如下示例所示:
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
if (error) {
NSLog(@"Error writing characteristic value: %@",
[error localizedDescription]);
}
...
如果您将写入类型指定为cbCharacteristicWriteWithoutResponse,则将以最佳方式执行写入操作,并且既不保证也不报告传递。外围设备不调用任何委托方法。
特性可能只支持某些类型的写入,或者根本不支持。通过检查某个cbCharacteristicPropertyWriteWithoutResponse或cbCharacteristicPropertyWrite常量的属性属性,可以确定特性支持哪种类型的写入(如果有)。