中心端执行常见类型的蓝牙任务,例如发现和连接到可用的外围设备,以及探索外围设备必须提供的数据并与之交互。
- 启动中央管理器
- 发现并连接正在广播的外围设备
- 连接到外围设备后探索外围设备上的数据
- 向外围服务的特征值发送读写请求
- 订阅特征值以在更新时得到通知
启动中央管理器
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
发现正在广播的外围设备
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
注意:如果指定第一个参数为nil,中央管理器返回所有发现的外围设备,而不管它们支持的服务。在实际的应用中,通常会指定一组CBUUID对象,每个对象代表外围设备正在广播的服务的唯一标识符(UUID),中央管理器仅返回广播这些服务的外围设备,从而允许扫描感兴趣的设备。
每次中央设备发现外围设备,就会调用下面的代理方法。新发现的设备由CBPeripheral对象返回。如果要连接发现的外围设备,保持对它的引用,以便系统不会释放它。下面的例子演示了使用属性对外围设备引用的场景。
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
NSLog(@"Discovered %@", peripheral.name);
self.discoveredPeripheral = peripheral;
...
如果希望连接到多个设备,则可以改为使用一个NSArray持有发现的外围设备。无论如何,一旦找到感兴趣的所有外围设备,停止扫描以节省电量。
[myCentralManager stopScan];
发现外围设备后连接它
发现感兴趣的外围设备广播服务后,调用中央管理器的连接方法,请求连接到外围设备
[myCentralManager connectPeripheral:peripheral options:nil];
如果连接请求成功,中央管理器调用其代理方法
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral {
NSLog(@"Peripheral connected");
peripheral.delegate = self;
...
发现连接的外围设备的服务
与外围设备连接后,可以探索其数据。探索数据的第一步是发现可用的服务。因为外围设备可广播的数据量存在大小限制,可能发现外围设备的服务比它广播的要多。可以通过调用外围设备的方法,来发现外围设备提供的所有服务
[peripheral discoverServices:nil];
在实际应用中,通常不会将nil作为参数传入,因为这样会返回外围设备上所有可用的服务。因为外围设备可能包含更多比你感兴趣的服务,所以发现这些服务会浪费电池的寿命并且是不必要的时间花费。相反,通常指定已经知道的感兴趣的服务的UUID。
当发现指定的服务时,外围设备(连接的CBPeripheral对象)调用委托对象的peripheral:didDiscoverServices:
方法。Core Bluetooth创建一个CBService数组,存储外围设备发现的所有服务。如下面演示的,你可以实现下面代理方法,去访问发现的服务数组。
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
for (CBService *service in peripheral.services) {
NSLog(@"Discovered service %@", service);
...
}
...
发现服务的特征
当找到感兴趣的服务时,探索外设必须提供的下一步是发现该服务的所有特征。发现服务的所有特征就像调用外围设备的发现特征方法一样简单
NSLog(@"Discovering characteristics for service %@", interestingService);
[peripheral discoverCharacteristics:nil forService:interestingService];
在实际应用中,通常不会讲nil作为第一个参数传入,因为这样做会返回外围设备服务的所有特征。因为外围设备的服务可能包含比你感兴趣的特征的更多的特性,所以发现这些特性可能会浪费电磁寿命并且是不必要的时间花费。相反,通常指定感兴趣的特征的UUID。
当发现服务的特征时,外围设备调用其委托对象的peripheral:didDiscoverCharacteristicsForService:error:
方法。Core Bluetooth创建一个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];
当尝试读取特征的值时,外围设备会调用其委托对象的peripheral:didUpdateValueForCharacteristic:error:
方法来检索值。如果成功检索到值,则可以通过特征的value属性访问它。
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
error:(NSError *)error {
NSData *data = characteristic.value;
// parse the data as needed
...
注意:并非所有的特征都是可读的。可以通过检查其属性是否包含CBCharacteristicPropertyRead常量来确定特征是否可读。如果尝试读取不可读的特征值,则
peripheral:didUpdateValueForCharacteristic:error:
委托方法会返回适当的错误。
订阅特征值
尽管使用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]);
}
...
注意:并非所有的特征都提供订阅。可以通过检索其属性是否包含CBCharacteristicPropertyNotify或CBCharacteristicPropertyIndicate常量来确定特征是否提供订阅。
成功订阅值后,外围设备会在值发生更改时通知您的应用。每次更改时,外围设备都会调用其委托对象的peripheral:didUpdateValueForCharacteristic:error:
方法。要检索更新的值,可以按照上述读取特征值中所述的相同方法实现此方法。
写特征的值
有时写特征的值是有意义的。例如,如果你的应用程序与蓝牙低能耗数字恒温器交互,可能希望为恒温器提供一个设置房间温度的值。如果一个特征的值是可写的,可以通过外设的writeValue:forCharacteristic:type:
方法用NSData写它的值,如下所示:
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,则将尽最大努力执行写入成功操作,并且不保证也不报告交付。外围设备不调用任何委托方法。
注意:特性可能只支持某些类型的写入,或者根本不支持。可以通过检索其属性CBCharacteristicPropertyWrite或常量CBCharacteristicPropertyWriteWithoutResponse中的一个来确定特征支持的写入类型。