在上一章,你已经学习了怎样执行大多数的中心这边的BLE任务,在这一章中,你将学习怎样使用CoreBluetooth框架执行大多数的周边这边的BLE任务。基础代码示例可以帮助你在你的设备上实现周边角色,你将学习:
- 开始一个周边管理者对象(Start up a peripheral manager object)
- 在本地设备上设置服务和特征(Set up services and characteristics on your local peripheral)
- 发布你的服务和特征到设备本地数据库(Publish your services and characteristics to your device’s local database)
- 广告你的服务(Advertise your services)
- 对中心的读写应答(Respond to read and write requests from a connected central)
- 发送被中心订阅的更新特质值(Send updated characteristic values to subscribed centrals)
你在本章所见的代码示例是简单且抽象的,你需要作出适当的修改再混合近你的app之中,更多关于实现本地周边角色的高级技术专题-包括秘诀、技巧和最佳实践-包含在后面的章节中iOSAPP中CoreBluetooth的后代执行模式 (Core Bluetooth Background Processing for iOS Apps )and实现本地周边的最佳实践( Best Practices for Setting Up Your Local Device as a Peripheral.)
开始一个周边管理者对象(Start up a peripheral manager object)
在本地设备上实现周边角色的第一步是分配并初始化一个周边管理者对象实例饿(用CBPeripheralManager对象表示),可以通过调用CBPeripheralManager类中的 initWithDelegate:queue:options:方法开始你的周边管理者,像这样:
myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
在这个例子中,“self”被设置为委托者用于接收任何周边角色事件,当你指定分派队列为“nil”,周边管理者将在主队列分派周边角色事件。
当你创建一个周边管理者,这个周边管理者会为它的委托对象调用peripheralManagerDidUpdateState:方法。你必须实现这个委托方法来保证BLE在本地设备上是支持并且可用的。更多关于实现委托方法的内容,请看 CBPeripheralManagerDelegate Protocol Reference.
设置你的服务和特征(Setting Up Your Services and Characteristics)
一个本地周边上的服务和特征的数据库是被分类成树状结构的,在你的本地设备上你也必须要把他们整理出树状结构。你执行这项任务的第一步是理解服务和特征怎么被标示的。
服务和特征通过UUIDs标示
蓝牙指定128位的UUIDs标记周边的服务和特征,在corebluetooth中用CBUUID对象抽象。虽然并非所有的服务和特征的UUIDs都被蓝牙特别兴趣小组 (SIG)预先定义,为了方便,他们定义并公开了一些16位的通用UUIDs。举个例子,蓝牙兴趣小组为心率服务预先定义了180D的16位(2byte)UUID。这个UUID是它相等的一个128位UUID的简称:0000180D-0000-1000-8000-00805F9B34FB。这个定义在蓝牙4.0协议定义的bluetooth基础uuid的第三卷,F部分3.2.1节中有介绍。
CBUUID类提供工厂函数帮你解决开发时那么长的UUID的问题。举例,你可以使用UUIDWithString函数从一个预定义的16位UUID中创建一个CBUUID的对象来代替那个心率服务128位的UUID,像这样:
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
当你通过16位UUID创建一个CBUUID时,CoreBluetooth还原了128位的那个蓝牙基础UUID。
创建你自己定义的服务和特征的UUIDS
你可能有预定义蓝牙UUIDs未定义的服务和特征。如果是这样,你需要为他们自定义128位UUID。
用uuidgen命令行程序可以很轻松得到128位的UUIDs。开始做,打开一个命令行窗口。下一步,分别为你需要的每一个特征和服务获取128位值,用ASCII码的格式化,用连字符分开的字符串。像下面这个例子:
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
然后你可以用这个UUID和UUIDWithString函数创建一个CBUUID对象:
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
构建你的服务和特征树
在你拥有了特征和服务的UUIDs之后(用CBUUID对象替代),你可以创建可变的服务和特征并且按上面描述的树状结构规则组织它们。举例:如果你有特征的UUID,你可以创建一个可变的特征,通过调用CBMutableCharacteristic类中的initWithType:properties:value:permissions:函数。像这样:
myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable];
当你创建一个个可变的特征,你可以设置它的属性,值,还有权限。你设置的属性和权限决定了这个特征的值是否可读的和是否可写的,并且决定了特征的值是不是可以被连接的中心订阅。在这个例子中,这只特征的值被设置为对连接中心可读的
。更多关于可变特征的属性和全市支持范围,看 CBMutableCharacteristic Class Reference.
注意:当你为特征指定了一个值,那么这个值将被贮藏起来,并且特征的属性和权限会被设为可读的。所以,当你需要特征的值变成可写的,或者你希望这个值在公共服务的特征的生命周期里是可变的,你必须要指定值为nil。通过下面步骤确保这个值在周边管理者收到来自连接的中心的请求的时候可以动态被周边管理者请求。
现在,你可以创建一个可变的特征,还可以创建一个特征关联的可变的服务。假如要这么做,你可以调用 CBMutableService 类的initWithType:primary:方法,像这里展示的这样:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
在这个例子中,第二个参数被设置为YES,表面这个服务是主要的不是次要的。一个主要的服务描述设备主要的功能并且可以包括(关联)其它的服务。一个次要的服务只能在关联它的服务的上下文里发挥作用。举例:心率计的主要服务显示获取心率传感器的数据,然而一个次要的服务可能显示传感器的电量数据。
在你创建了服务之后,你可以通过设置characteristics这个服务的数组把它和特征值关联。像这样:
myService.characteristics = @[myCharacteristic];
发布你的服务和特征
您已经构建了服务和特性的树后,执行你的本地设备上外围设备角色的下一步是公布它们到设备的服务和特性的数据库。这个任务是很容易使用蓝牙CoreBluetooth框架来执行。调用在CBPeripheralManager类中的addService:的方法,如下:
[myPeripheralManager addService:myService];
当调用此方法来发布服务,周边管理器为它的委托对象调用peripheralManager:didAddService:error:方法。如果出现错误,你的服务则不能公布,实现这个委托方法来访问错误的原因,如下例所示:
-(void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error{
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
...
注意:您发布服务及任何与其关联的特点到周边的数据库之后,这个服务将被缓存,你不能再进行更改。
广告你的服务
当你将你的服务和特征发布到设备的服务特征数据库,你得准备好你开始广告一些服务和特征给其它可以收听到的任何中心。下面的例子展示了,你可以通过调用CBPeripheralManager类中的 startAdvertising: 方法广告你的一些服务,通过一个广告数据的字典(NSDictionary的实例):
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
在这个例子中,字典里唯一的一个键是CBAdvertisementDataServiceUUIDsKey,与之对应的值是一个象征你想要广告的UUIDs的CBUUID对象数组(NSArray实例)。广告数据内容中更多详细的key的介绍在CBCentralManagerDelegate Protocol Reference.中Advertisement Data Retrieval Keys那一节。也就是说,只有两个键是用于周边管理对象的支持:CBAdvertisementDataLocalNameKey 和 CBAdvertisementDataServiceUUIDsKey.
当你开始为本地周边广告一些数据时,周边管理者为他的委托对象调用peripheralManagerDidStartAdvertising:error: 方法,如果发生了错误导致服务不能被广告,实现这个委托方法查找错误的原因,像这样:
-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...
注意:数据的广告是“尽力而为”的基础上进行,因为空间是有限的,有可能同时有多个应用在投放广告。欲了解更多信息,请参阅在CBPeripheralManager Class Reference中startAdvertising方法的讨论。
当你的应用程序是在后台广告的行为也受到了影响。这个话题在下一章中,核心的蓝牙后台处理的iOS应用进行讨论( Core Bluetooth Background Processing for iOS Apps.)。
一旦你开始广告数据,远程中心就可以发现你并和你建立连接。
响应来自中心的读写请求
在你连接了一个或多个远程中心之后,你可以开始接受来自远程中心的读或写请求。当你这样做时,一定要以适当的方式响应这些请求。下面的例子说明如何处理这样的请求。
当连接的中心需要请求读你的特征值时,中心管理者会为他的委托对象调用peripheralManager:didReceiveReadRequest: 方法。这个委托方法封装了一个 CBATTRequest 对象,包含关于请求的属性的所有值。
例如,当您收到一个简单读取特性的值的请求,你从委托方法中收到的CBATTRequest对象的属性可以被用来确保在设备的数据库中的特征与远程中央指定的在原始读请求是否相匹配。你可以开始实现这个委托方法,如下所示:
-(void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
...
如果该特征'的UUID匹配,则下一个步骤是确保该读请求是不是要求从你的特征的值的范围外的索引位置读取。如下面的例子显示,你可以使用一个CBATTRequest对象的偏移属性,以确保读请求不试图在适当的范围之外阅读:
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}
假设请求的偏移得到验证,现在设置请求的特征属性的值(默认其值为零)为您在本地外围创建的特性的值;考虑到偏移的读出的请求:
request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];
设置值后,回复远程中心,表面请求十分成功。调用 CBPeripheralManager 类中的respondToRequest:withResult:方法回复那个请求(指定值的那个)回复请求结果。像这样:
[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
在委托对象每一个peripheralManager:didReceiveReadRequest: 回调方法里调用respondToRequest:withResult。
注意:如果特征的UUID不匹配,或者因为任何其他原因读不能完成,你不会试图完成请求。替代做法是,你讲立即调用respondToRequest:withResult:方法。并且提供结果描述失败。对于你可以指定可能的结果列表,请参阅Core Bluetooth Constants Reference的CBATTError常量枚举。
处理来自连接的中心的写请求的方式是直截了当的。当一个中心试图通过发送写请求来改变你的一个多个特征的值,周边管理者会通过调用委托对象的 peripheralManager:didReceiveWriteRequests:方法来通知。委托对象交付一个包含一到多个CBATTRequest对象的规则的数组,其中每个对象代表一个写请求。在你确定这个写请求是合格的,你就可以把它写入特征值当中,像这样:
myCharacteristic.value = request.value;
虽然上面的例子并不能说明这个问题,但请你写特性值时,要考虑到请求的偏移特性。
就像回复读请求一样,调用 respondToRequest:withResult:方法在peripheralManager:didReceiveWriteRequests: 方法被调用之后。这就是说,respondToRequest:withResult:方法需要的第一个参数是一个CBATTRequest对象,即使你从 peripheralManager:didReceiveWriteRequests:方法中得到了一个数组的多个对象,你可以通过数组的第一个请求,像这样:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0] withResult:CBATTErrorSuccess];
注意:对待多个请求当作你一个请求,如果有个别要求不能得到满足,你不应该满足其中任何一个请求。相反,立即调用respondToRequest : withResult 方法,并提供一个结果,指出错误的原因:。
上报更新的值给订阅特征值的中心(Sending Updated Characteristic Values to Subscribed Centrals)
通常,连接的中心会订阅一到多个特征的值,像前面章节说的,当他们这样做,你有责任向他们发送通知当他们订阅的特性的值变化时。下面的例子说明怎么做。
当连接的中心订阅了你的特征值时,周边管理者会为委托对象调用peripheralManager:central:didSubscribeToCharacteristic: 方法。
-(void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
...
使用上述委托方法作为一个线索开始向中心发送更新的值。下一步,通过调用 CBPeripheralManager类的updateValue:forCharacteristic:onSubscribedCentrals:方法得到更新的特征值并发送到中心
NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];
通过调用这个方法向订阅中心发送特征值时,你可以在最后一个参数指定特定的中心。像上面的例子,如果参数传入nil,所有订阅并连接的中心都会得到更新(连接但是没有订阅的被忽略)。
updateValue:forCharacteristic:onSubscribedCentrals: 方法返回一个bool值表示发送到中心的订阅的结果。如果底层队列需要传送的值满了,方法会返回no,当传输空间变得有用时中心管理者为委托对象调用peripheralManagerIsReadyToUpdateSubscribers:。你可以通过实现这个委托方法再次发送想要的值,如果这样做的话,你可以再次使用之前熟悉的updateValue:forCharacteristic:onSubscribedCentrals: 方法。
注意:使用通知发送单一值的数据包到订阅的中心。也就是说,当你更新了订阅中心,你应该发送全部的更新值在一个通知里,通过调用一次
updateValue:forCharacteristic:onSubscribedCentrals: 方法。依赖于特征值的规模,并不是所有数据都被通知传送。如果发生了,这种情形应该被中心端通过调用CBPeripheral类的 readValueForCharacteristic:方法处理,这样可以获取全部的值。