Performing Common Peripheral Role Tasks
上一个章节,你学习了怎么执行最普通的蓝牙低能量任务从central 端。在这个章节,你学习在peripheral端怎么执行最普通的蓝牙低能昂任务。下面的代码演示你开发你的app实现peripheral角色在你本地的设备。规制,你将学习什么:
- 开启一个peripheral管理者对象
- 在你的本地的peripheral 搭建services和characteristic
- 发布你的services和characteristics到你的设备的本地数据库
- 广播你的service
- 响应来自一个连接的central 读取和写入请求
代码例子你会发现在这个章节是简单和抽象的,你可能需要进行适当的更改在你的实际的app中。更高级的实现central 角色的相关主题–包括提示,技巧,和最佳的练习在之后的章节。
Starting Up a Peripheral Manager
第一步在你的本地设备alloc和init一个peripheral manager实例(代表CBPeripheralManager对象)实现peripheral角色。开启你的peripheral管理者通过调用CBPeripheralManager类的initWithDelegate:queue:options:,像这样:
myPeripheralManager =
[[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
在这个例子中,self是作为delegate,接收任何的peripheral角色的事件。当你指定dispathc queue为nil,peripheral管理者分配peripheral角色事件使用main queue。
当你创建一个peripheral管理者,central管理者调用delegate中的peripheralManagerDidUpdateState::方法。你必须实现这个delegate方法去确保是支持蓝牙低能量和在central设备中是可获得的。关于更多怎么实现delegate方法的信息,看CBPeripheralManagerDelegate Protocol Reference
.
Setting Up Your Services and Characteristics
如图1-7所示,一个本地的peripheral的数据库services和characteristics是组织树状的方法 你必须组织他们在这个树状方式设置本地peripheral的services和characteristics。你的执行这些任务的第一步是了解services和识别characteristics。
Services and Characteristics Are Identified by UUIDs
peripheral的services和characteristics的标识是128位蓝牙指定UUIDs,代表Core Bluetooth framework中的CBUUID对象。虽然并不是所有的UUIDs识别service或者预定义的characteristic通过Special Interest Group(SIG),蓝牙SIG定义和发布一些常用的uuid,为方便缩短16位。例如,蓝牙SIG有预定义的标识一个心率services的16位UUID为180 d。UUID是缩短从它的等效128位的UUID,0000180 d - 0000 - 1000 - 8000 - 00805 - f9b34fb,基于蓝牙UUID定义在蓝牙4.0规范中,卷3 F部分,3.2.1节。
当开发你的app的时候,CBUUID提供了工厂方法使用它处理长的UUIDs更容易。例如,在你的代码中而不是通过字符串表示的心率services的128位UUID,您可以简单地使用UUIDWithString方法创建一个CBUUID对象从service的预定义的16位UUID,像这样:
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
当你从一个预定义的16位创建一个CBUUID对象时,Core Bluetooth预填充剩余的128位UUID和蓝牙基本的UUID。
Create Your Own UUIDs for Custom Services and Characteristics
你可能通过预定义的蓝牙UUID没有识别services和characteristics。如果你这样做,你需要生成自己的128位uuid来识别它们
使用命令行uuidgen轻松的创建128位UUID。首先打开一个终端窗口。接下来,为每个service和characteristic,您需要识别UUID,在命令行中输入uuidgen,接收一个惟一的ASCII字符串形式的128位值的连字符,如以下例子:
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7片
你你可以使用这个UUID创建一个CBUUID对象通过UUIDWithString方法,像这样:
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
Build Your Tree of Services and Characteristics
你你在你的services和characteristics有UUID之后,您可以创建可变的services和characteristics和组织在上述树状方式。例如,如果你有characteristic的UUID,您可以创建一个可变characteristics通过调用
CBMutableCharacteristic 类的
initWithType:properties:value:permissions:方法,像这样:
myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable];
你当你创建了一个可变的characteristic,你设置它的属性,值,和权限。属性和权限设置好后,除此之外,characteristic的值是否可读的还是可写的,以及连接central是否可以订阅characteristic的值。在这个例子中,characteristic的值被设置为被连接central可读。更多信息支持的属性和权限范围可变的characteristic,看到CBMutableCharacteristic Class Reference
Note:如果你指定为characteristic指定一个值,这个值是缓存的和它的属性和权限是设置成可读的。因此,如果你需要一个可以写的characteristic的值,或者如果你期望值变化期间characteristic属于发布的service的生命周期,您必须指定值是nil。下面这种方法保证了所请求的值是动态和peripheral管理者只要peripheral收到一个读或写请求连接central。
现在您已经创建了一个可变的characteristic,您可以创建一个可变的service关联characteristic。要做到这一点,调用
CBMutableService 类的
initWithType:primary:方法:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
在这个例子中,第二个参数设置为YES,表明service是主要与次要的。主要service描述设备的主要功能和可以包含另一个service(引用)。次要服务描述服务相关的只有在上下文中引用的另一个服务。例如,心率监视器的主要service可能暴露心率监测器的心率传感器的数据,而第二个servuce可能暴露传感器的电池数据。
你创建一个服务之后,你可以设置这个service的charactertics的数组,像这样:
myService.characteristics = @[myCharacteristic];
Publishing Your Services and Characteristics
你已经构建树状services和characteristics之后,下一步是实现peripheral角色,在你的本地设备发布他们到设备的services和characteristics数据。这是是非常容易的使用Core Bluetooth framework。你调用CBPeripheralManager类的addService:方法,像这样:
[myPeripheralManager addService:myService];
当你调用这个方法发布你的services的时候,peripheral管理者调用它的代理对象方法peripheralManager:didAddService:error:。如果一个错误发生和你的services不能发布,实现这个代理方法获取错误的原因,以下例子演示:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error {
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
...
>Note:发布一个service和任何它相关的characterictics到peripheral的数据库,service是缓存的,你可以不再对其进行更改。
Advertising Your Services
当你已经发布你的services和characteristics到你的设备的services和characteristics数据库,你准备开始广播他们到任何可能监听到的central.下面的例子,你可以广播一些services通过调用CBPeripheralManager的startADvertising:方法,通过在一个字典中实例化广播数据:
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
在这个例子中,字典中的key是唯一的, CBAdvertisementDataServiceUUIDsKey
,期望一个代表你想广播的services的UUID的CBUUID对象数组作为一个值,你可以去CBCentralManagerDelegate Protocol Reference的Advertisement Data Retrieval Keys的常量描述中寻找广播数据key的细节。只有两个key支持peripheral管理者对象:
CBAdvertisementDataLocalNameKey和CBAdvertisementDataServiceUUIDsKey
当你在你的本地peripheral开始广播一些数据,peripheral管理者调用他的代理对象方法:peripheralManagerDidStartAdvertising:error:
如果发生一个错误和你的service是不能广播的,实现这个他的代理方法访问这个错误的原因,像这样:
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...
Note:数据的广播是“best effort”的基础上完成的,因为空间有限,同时可能会有多个app的广播。有关更多信息,请参见startAdvertising:的讨论:方法在CBPeripheralManager Class Reference。
广播的行为也影响了当你的应用程序在后台。在下一章中讨论过这个问题,核心蓝牙iOS应用程序的后台处理。
一旦你开始广播数据,远程central能发现和初始化一个连接和你。
Responding to Read and Write Requests from a Central
你连接一个或者多个远程central之后,你可能从它们那里开始接受读取或者写入请求。当你这样做确保在一个适当的方法响应那些请求。下面的例子描述了怎样处理这样的请求。
当一个连接的central请求读取你的characteristics中的一个值,peripheral管理者调用它的代理对象方法:peripheralManager:didReceiveReadRequest:,代理方法将该请求以CBATTRequest对象的形式传递给你,它有许多的属性,您可以使用它们来完成请求。
例如:当你接受一个简单的请求读取一个characteristic的值,你接收的代理方法中的CBATTRequest参数的属性可用于确保在你的设备数据库的characteristic匹配远程central指定的读取请求,像这样:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
...
如果characteristic的UUID匹配,下一步是确保读取请求不要求读取索引的范围以外的位置的characteristic的值。如以下示例所示,您可以使用一个CBATTRequest对象的偏移属性,以确保外的读取请求不是试图读取合适的范围以外:
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}
假设请求的偏移量是正确的,现在设置请求的characteristic属性的值(默认的值为nil)到你在你的本地peripheral创建的characteristic的值,考虑到读取请求的偏移量:
request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];
设置值后,响应远程central表示请求成功完成。通过调用CBPeripheralManager类的respondToRequest:withResult:方法,通过请求(其值更新)和请求的结果,是这样的:
[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
...
调用respondToRequest:withResult:方法每次都会调用代理方法这里写链接内容peripheralManager:didReceiveReadRequest:
Note:如果characteristic的UUID没有匹配,或者如果读取因为任何的其他原因没有完成,你将不会完成这个请求。反而,你将立即调用respondToRequest:withResult:方法和提供一个失败的原因。对可能结果的列表你可以指定,看 Core Bluetooth Constants Reference.的CBATTError Constants
处理写请求从一个连接central也很简单。连接central发送写的一个或多个characteristic的值请求时,peripheral管理者调用它的的代理对象方法 peripheralManager:didReceiveWriteRequests:。同一时间,代理方法包含一个或多个CBATTRequest对象数组的形式向你发送请求,每个代表一个写请求。确保写请求后完成后,,您可以编写characteristic的价值,像这样:
myCharacteristic.value = request.value;
虽然上面的例子不能证明这一点,一定要考虑到请求的偏移量属性,当你写入你的characteristic的值的时候。
就像你响应一个读取请求,调用respondToRequest:withResult:方法同一时间会调用代理方法 peripheralManager:didReceiveWriteRequests: 。 respondToRequest:withResult: 的第一个参数希望一个单一的CBATTRequest对象,即使你已经从peripheralManager:didReceiveWriteRequests: 代理方法接收了一个包含多个CBATTRequest对象的数组。你应该通过数组第一个请求,像这样:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
Note:>对待多个请求作为单个请求,如果任何个人请求不能完成,你不应该满足其中任何一个。相反,立即调用respondToRequest:withResult:方法并提供结果,指出失败的原因。
Sending Updated Characteristic Values to Subscribed Centrals
通常,连接central将订阅一个或者多个你的characteristic值,作为在 Subscribing to a Characteristic’s Value的描述,当他们这样做,你是负责发送通知,当他们订阅characteristic的值改变。下面的例子描述。
当一个连接的central订阅你的characteristic的其中一个值的时候,periperhal管理者调用它的代理对象方法 peripheralManager:central:didSubscribeToCharacteristic:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
...
使用上面的代理方法作为开始返送central更新值的提示。
接下来,得到characteristic的更新的值,和发送它到central 通过调用
CBPeripheralManager类的
updateValue:forCharacteristic:onSubscribedCentrals:
NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];
当你调用这个方法发送更新的characteristic的值到订阅的centrals,你可以指定哪个central更新最后一个参数。在上面的例子中,如果你指定为nil,所有的连接和订阅的centrald是更新的(包括任何连接的central但没有订阅的)
updateValue:forCharacteristic:onSubscribedCentrals: 方法返回一个Boolean值声明发送订阅是否成功。如果底层的队列用于发送更新后的值是完成的,该方法返回NO。periperhal管理者然后调用它的的代理对象方法 peripheralManagerIsReadyToUpdateSubscribers:,当更多的空间传输队列是可获得的。然后您可以实现这个delegate方法来重新发送这个值,再使用
updateValue:forCharacteristic:onSubscribedCentrals:方法。
Note:使用通知发送一个单一的数据包到订阅的central.当您更新订阅的central,你应该发送整个更新值在单一的通知,通过调用
updateValue:forCharacteristic:onSubscribedCentrals:方法一次。
根据characterisric的值的大小,并不是所有的数据可以传输通过通知。如果发生这种情况,这种情况应该在central端通过调用CBPeripheral类的readValueForCharacteristic:方法c处理,检索整个价值。