当设备作为 Central 的时候,需要做一系列常见的操作:搜索并连接周围 Peripheral,处理 Peripheral 提供的数据。其实在设备作为 Peripheral 的时候,依然有一系列的操作,不过和作为 Central 时不同(废话),例如它会去发起广播,会在读写数据时做出响应。
本章内容则是介绍设备在作为 Central 时,怎么使用 Core Bluetooth framework 来操作了。大致包含以下几点:
- 使用
CBCentralManager
- 搜索并连接可用的 Peripheral
- 连接时候,进行数据接收
- 对 Characteristic 进行数据读写
- 当 Characteristic 状态更新时,进行回调
下一篇文章才会介绍在设备作为 Peripheral 时的相关操作。
初始化 CBCentralManager
第一步先进行初始化,可以使用 initWithDelegate:queue:options:
方法:
myCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
上面的代码中,将 self
设置为代理,用于接收各种 Central 事件。将 queue
设置为 nil
,则表示直接在主线程中运行。
初始化 central manager 之后,设置的代理会调用 centralManagerDidUpdateState:
方法,所以需要去遵循 <CBCentralManagerDelegate>
协议。这个 did update state 的方法,能获得当前设备是否能作为 Central。关于这个协议的实现和其他方法,之后会讲到,也可以先看看 官方API 。
搜索当前可用的 Peripheral
可以使用 CBCentralManager
的 scanForPeripheralsWithServices:options:
方法来扫描周围正在发出广播的 Peripheral 设备。
[myCentralManager scanForPeripheralsWithServices:nil options:nil];
第一个参数为 nil
,表示所有周围全部可用的设备。在实际应用中,你可以传入一个 CBUUID
的数组,表示只搜索当前数组包含的设备(每个 Peripheral 的 Service 都有唯一标识——UUID)。所以,如果你传入了这样一个数组,那么 central manager 则只会去包含这些 UUID 的 Peripheral。
CBUUID
会在下一章中讲到,因为这是 Peripheral 相关的,和 Central 本身关系不大,如果你是做的硬件对接,那么可以向硬件同事询问。当然,下一节的内容也能帮助你理解这个属性。
在调用 scanForPeripheralsWithServices:options:
方法之后,找到可用设备,系统会回调(每找到一个都会回调) centralManager:didDiscoverPeripheral:advertisementData:RSSI:
。该方法会已 CBPeripheral
返回找到的 Peripheral,所以你可以使用数组将找到的 Peripheral 存起来。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { NSLog(@"Discovered %@", peripheral.name); }
当你找到你需要的那个 Peripheral 时,可以调用 stop
方法来停止搜索。
[myCentralManager stopScan]; NSLog(@"Scanning stopped");
连接 Peripheral
找到你需要的 Peripheral 之后,下一步就是调用 connectPeripheral:options:
方法来连接。
[myCentralManager connectPeripheral:peripheral options:nil];
当连接成功后,会回调方法 centralManager:didConnectPeripheral:
。在这个方法中,你可以去记录当前的连接状态等数据。
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@"Peripheral connected"); }
不过在进行其他操作之前,你应该给已连接的这个 Peripheral 设置代理,这样才能收到 Peripheral 的回调(可以就写在上面这个方法中)。
peripheral.delegate = self;
注意去遵循 <CBPeripheralDelegate>
协议。
搜索 Peripheral 的 Service
当与 Peripheral 成功建立连接以后,就可以通信了。第一步是先找到当前 Peripheral 提供的 Service,因为 Service 广播的数据有大小限制,所以你实际找到的 Service 的数量可能要比它广播时候说的数量要多。调用 CBPeripheral
的 discoverServices:
方法可以找到当前 Peripheral 的所有 Service。
[peripheral discoverServices:nil];
在实际项目中,这个参数应该不是 nil
的,因为 nil
表示查找所有可用的 Service
,但实际上,你可能只需要其中的某几个。搜索全部的操作既耗时又耗电,所以应该提供一个要搜索的 Service 的 UUID 数组。更加详细的内容会在后几章讲到。
当找到特定的 Service 以后,会回调 <CBPeripheralDelegate>
的 peripheral:didDiscoverServices:
方法。Core Bluetooth 提供了 CBService
类来表示 Service,找到以后,它们以数组的形式存入了当前 Peripheral 的 services
属性中,你可以在当前回调中遍历这个属性。
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { for (CBService *service in peripheral.services) { NSLog(@"Discovered service %@", service); } }
如果是搜索的全部 Service 的话,你可以选择在遍历的过程中,去对比 UUID 是不是你要找的那个。
搜索 Service 的 Characteristic
找到需要的 Service 之后,下一步是找它所提供的 Characteristic。如果搜索全部 Characteristic,那调用 CBPeripheral
的 discoverCharacteristics:forService:
方法即可。如果是搜索当前 Service 的 Characteristic,那还应该传入相应的 CBService
对象。
NSLog(@"Discovering characteristics for service %@", interestingService);[peripheral discoverCharacteristics:nil forService:interestingService];
同样是出于节能的考虑,第一个参数在实际项目中应该是 Characteristic 的 UUID 数组。
找到所有 Characteristic 之后,回调 peripheral:didDiscoverCharacteristicsForService:error:
方法,此时 Core Bluetooth 提供了 CBCharacteristic
类来表示 Characteristic。可以通过以下代码来遍历找到的 Characteristic :
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"Discovered characteristic %@", characteristic); } }
同样也可以通过添加 UUID 的判断来找到需要的 Characteristic。
读取 Characteristic 数据
Characteristic 包含了 Service 要传输的数据。例如温度设备中表达温度的 Characteristic,就可能包含着当前温度值。这时我们就可以通过读取 Characteristic,来得到里面的数据。
读取 Characteristic 数据
当找到 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 }
注意,不是所有 Characteristic 的值都是可读的,你可以通过 CBCharacteristicPropertyRead
枚举来进行判断。如果你尝试读取不可读的数据,那上面的代理方法会返回相应的 error。
订阅 Characteristic 数据
其实使用 readValueForCharacteristic:
方法并不是实时的。考虑到很多实时的数据,比如心率这种,那就需要订阅 Characteristic 了。
可以通过调用 CBPeripheral
的 setNotifyValue:forCharacteristic:
方法来实现订阅,注意第一个参数是 YES
。
[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
如果是订阅,成功与否的回调是 peripheral:didUpdateNotificationStateForCharacteristic:error:
,读取中的错误会以 error 形式传回:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@"Error changing notification state: %@", [error localizedDescription]); } }
当然也不是所有 Characteristic 都允许订阅,依然可以通过 CBCharacteristicPropertyRead
枚举来进行判断。
当订阅成功以后,那数据便会实时的传回了,数据的回调依然和之前读取 Characteristic 的回调相同(注意,不是订阅的那个回调) peripheral:didUpdateValueForCharacteristic:error:
。
向 Characteristic 写数据
写数据其实是一个很常见的需求,如果 Characteristic 可写,你可以通过 CBPeripheral
类的 writeValue:forCharacteristic:type:
方法来向设备写入 NSData
数据。
NSLog(@"Writing value for characteristic %@", interestingCharacteristic); [peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic type:CBCharacteristicWriteWithResponse];
关于写入数据的 type
,如上面这行代码, type
就是 CBCharacteristicWriteWithResponse
,表示当写入成功时,要进行回调。更多的类型可以参考 CBCharacteristicWriteType
枚举。
如果写入成功后要回调,那么回调方法是 peripheral:didWriteValueForCharacteristic:error:
。如果写入失败,那么会包含到 error 参数返回。
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { NSLog(@"Error writing characteristic value: %@", [error localizedDescription]); } }
注意:Characteristic 也可能并不支持写操作,可以通过 CBCharacteristic
的 properties
属性来判断。
基本的介绍就到此结束,下一篇会对本文没有详细讲解的内容进行补充,如会用到的枚举、类等。同时还包含实际项目中会遇到的一些需求。