CoreBluetooth第二节:Performing Common Peripheral Role Task(执行常见的外设端任务)

本文官方文档链接


在前一章节中你掌握了执行常见的蓝牙低功耗核心端的任务.在本章中,你要学习执行常见的蓝牙低功耗外设端的任务.基于代码的例子会帮助你在本地设备中扮演外设端.特别的,你将掌握如何:

  • 启动一个peripheral manager对象
    • 在本地外设中建立services 和characteristics
    • 将services和characteristics发送到设备的本地数据库
    • 广播services
    • 响应与自身建立连接的central(核心端)的读写请求
    • 向订阅自己的central(核心端)更新特征值

本章中的样例简单并抽象,你可能需要在实际开发中做出相应更改.更多外设端的进阶主题-包括小贴士/技巧/最佳实践在随后的章节:Core Bluetooth Background Processing for iOS AppsBest Practices for Setting Up Your Local Device as a Peripheral.


启动一个Peripheral Manager

第一步就是分配内存并初始化一个peripheral manager实例(CBPeripheralManager对象)调用CBPeripheralManager类的 initWithDelegate:queue:options 方法:

 myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];

在本例中,self设置成代理对象来接收外设端的事件.当你指定队列为nil时,peripheral manager使用主队列来分发事件.
当创建一个peripheral manager, peripheral 调用peripheralManagerDidUpdateState代理方法.为了确保本地外设支持蓝牙低功耗并可用,你必须实现该代理.关于实现该代理方法的更多信息,请点击CBPeripheralManagerDelegate Protocol Reference


建立Services和Characteristics

1-7
如上图所示,一个本地外设的数据库(存储services和characteristics)是一个树状结构.在本地的外设上建立services和characteristics时必须将他们构造成这个树状结构.第一步就是理解services和characteristics是如何标识的.

Services和Characteristics通过UUIDs区分

外设的services和characteristics通过128位特定的蓝牙UUID区分,在Core BlueTooth框架中用作CBUUID对象.尽管SIG并未预定义用来区分service和characteristic的UUID,SIG定义并发布了一些常见的便于使用的16位的UUID.举个栗子,SIG将心率设备的标识符定义为16位的180D.这个UUID是由和它等价的128位UUID缩短而成,0000180D-0000-1000-8000-00805F9B34FB,基于基本的UUID定义为蓝牙4.0规范,频道3,part F, Section3.2.1
CBUUID类提供了可让你在开发中快速处理长UUID的工厂方法.例如,你可以简单的使用UUIDWithString来从系统预定义的16位UUID创建CBUUID对象,从而取代128位心率service的UUID:

CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];

为自定义的Services和Characteristics创建自己的UUIDs

你可能会有一些没有预定义UUID的services和characteristic.如果这么做,你需要为它们生成自己的128位UUID.
使用命令行工具uuidgen可以快速的生成128位UUID.首先,开启一个终端窗口,下一步为每一个需要标识的service和characteristic生成为一个128位使用-连接的ASCII值:

$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7

然后你可以使用这个UUID通过UUIDWithString创建一个CBUUID对象:

CBUUID *myCustomServiceUUID = [CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];

创建Services和characteristics的树形结构

在你拥有了services和characteristic的UUIDs之后,你可以创建可变的service和characteristics并且将它们构成树形.举例,如果你有一个characteristic的UUID,你可以通过initWithType:properties:value:permissions创建可变的characteristic:

 myCharacteristic =
        [[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
         properties:CBCharacteristicPropertyRead
         value:myValue permissions:CBAttributePermissionsReadable];

当创建了一个可变的characteristic后设置他的属性,授权.除了其他事项外,属性授权决定了characteristic value(特征值)是可读的还是可写的,以及与本设备建立连接的central(核心)设备是否可以订阅该characteristic.在本例中,特征值被设置成可以被连接的核心设备读取.更多关于可变characteristic相关的可用属性和授权请戳CNMutableCharacteristic Class Reference
Note:如果你为characteristic指定了一个值,这个值被缓存下来并且它的属性和授权被设置为了可读.如果你需要将characteristic设置为可写的,或者在发布service的生命周期中改变characteristic的值,你必须指定值为nil.如此确保在外设收到核心设备的读写请求时,值是动态变化的
现在你创建了一个可变的characteristic,你可以创建可变的service来联结characteristic,调用CBMutableService 类的 initWithType:primary:

myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];

在本例中,第二个参数设置成了Yes,意味着service是主要的.一个主service描述一个设备的主要功能并可以被其他service引用.次service描述了一个只在引用它的其他service的上下文中相关(翻译的好像不太对.A secondary service describes a service that is relevant only in the context of another service that has referenced it).举个栗子,心率检测器的主service可能从心率传感器的传输心率数据,而次service可能传输传感器的电量数据.
在创建了一个service后,你可以设置service的characteristic数组来联结它们:

myService.characteristics = @[myCharacteristic];

发布你的Service和Characteristic

在你创建好树形的services和characteristics后,下一步是将他们发布到本地设备的services和characteristics数据库中.使用CB可以简单地实现,你可以调用CBPeripheralManager 类的addService:

[myPeripheralManager addService:myService];

当你调用这个方法发布services时,peripheral manager调用peripheralManager:didAddService:error: 代理函数.如果错误发生了并且services不能被发出,实现该代理来捕获错误信息:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
            didAddService:(CBService *)service
                    error:(NSError *)error {

    if (error) {
        NSLog(@"Error publishing service: %@", [error localizedDescription]);
    }
    ...

Note:在你向数据库发布一个service以及相关的characteristic后,这个service被缓存下来并且你不能再改变它


广播Service

当你将services和characteristics发布到设备的service和characteristic数据库中后,你已经准备将他们广播给一些正在监听的central设备.如下例所示,你可以调用CBPeripheralManager 类中的 startAdvertising:,传入一个NSDictionary类型的广播数据.

[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[myFirstService.UUID, mySecondService.UUID] }];

在本例中,NSDictionary的唯一键CBAdvertisementDataServiceUUIDsKey,对应需要一个存放CBUUID对象的数组(代表着你想广播的service的UUIDs).其他你可能用到的NSDictionary中的键在这里.也就是说,只有两个键是支持peripheral manager对象的:CBAdvertisementDataLocalNameKeyCBAdvertisementDataServiceUUIDsKey.
当你开始在本地外设中广播数据时,peripheral manager调用peripheralManagerDidStartAdvertising:error:代理方法.如果发生错误可以在代理中捕获错误信息:

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {

    if (error) {
        NSLog(@"Error advertising: %@", [error localizedDescription]);
    }
    ...

Note:数据广播是”努力”的,因为容量有限并且同时会有多个app广播,更多相关信息请查看startAdvertiseing:,广播的行为也会因程序在后台受到影响,在下一章中将讨论该问题,Core Bluetooth Background Processing for iOS Apps
一旦你开始广播数据,远程的核心设备可以发现并且与你建立连接.


响应核心设备发来的读写请求

在你连接了一个或多个远程核心设备后,你可能开始接收他们的读写请求.当你这么做时,确保用适当的方式来响应这些请求.下例描述了如何处理这些请求.
当一个连接的核心设备请求读取一个你的characteristic, Peripheral manager调用peripheralManager:didReceiveReadRequest:代理方法.这个代理方法会以CBATTRequest对象向你传递核心端传来的请求(拥有许多你可以用来实现请求的属性)
举例,当你接收到一个简单的要读取characteristic的请求,从代理中获取的CBATTRequest 对象可以用来确定核心端指定要读的characteristic和你本地设备数据库中的characteristic相匹配.你可以实现这个代理:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
    didReceiveReadRequest:(CBATTRequest *)request {

    if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
        ...

如果characteristic的UUID匹配成功,下一步要确保这个读取请求要读取的index位置不在你的characteristic值的外界.如下例所示,你可一个使用CBATTRequestoffset属性来保证读取请求不是在读取属性外界的数据.

if (request.offset > myCharacteristic.value.length) {
        [myPeripheralManager respondToRequest:request
            withResult:CBATTErrorInvalidOffset];
        return;
    }

假如请求的偏移量成功验证,用本地外设端创建的characteristic值覆盖请求中的characteristic属性值,并且要顾及到读取请求的偏移量:

request.value = [myCharacteristic.value
        subdataWithRange:NSMakeRange(request.offset,
        myCharacteristic.value.length - request.offset)];

在设置完值之后,需要向远程核心设备声明请求已成功处理.需调用CBPeripheralManagerrespondToRequest:withResult:方法,将你更新的request和请求处理的结果一并返回:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];

在每一次peripheralManager:didReceiveReadRequest:被调用后你都需要调用一次respondToRequest:withResult:

Note:如果characteristic的UUID不匹配,或因其他原因不能完成读取请求,你无须尝试完成请求.相反的你应该立刻调用respondToRequest:withResult:并且提供一个表明失败原因的result.对于你可能需要指定的结果,查看CBATTError Constants
处理核心设备发送的写入请求也很直接.当连接的核心设备向你发送一个或多个characteristic读写请求时,peripheral manager调用peripheralManager:didReceiveWriteRequests:代理.此时代理方法会以一个包含多个CBRequest 对象的数组向你传输请求,其中每一个代表一个写入请求.在你确认完成了一个写入请求后,你可以写characteristic的值:

myCharacteristic.value = request.value;

尽管上述例子没有演示,你还要确保在写入characteristic时考虑请求的偏移量属性.
正如回应读取请求一样,在每一次peripheralManager:didReceiveWriteRequest:被调用后你都需要调用一次respondToRequest:withResult:,也就是说第一个参数需要一个CBATTRequest对象,即使你从peripheralManager:didReceiveWriteRequests:收到了多于一个Request的数组.你应该传入数组的第一个请求:

 [myPeripheralManager respondToRequest:[requests objectAtIndex:0]
        withResult:CBATTErrorSuccess];

将多个请求作为单个请求来处理,如果任一请求不能完成,你不应该完成其中的任何一个.相反,立刻调用respondToRequest:withResult:报错


向订阅的核心设备发送更新的characteristic

通常情况下,连接的核心设备会订阅你的一个或多个characteristic值,如上一节的订阅characteristic值.当订阅characteristic值发生改变,你有责任发出通知.下例将指导你如何去做.
当一个连接的核心设备订阅了你的若干characteristic.peripheral manager 调用peripheralManager:central:didSubscribeToCharacteristic:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
                  central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {

    NSLog(@"Central subscribed to characteristic %@", characteristic);
    ...

使用上面的代理方法来向核心设备发送更新值.
下一步,获得更新的characteristic值并且通过CBPeripheralManagerupdateValue:forCharacteristic:onSubscribedCentrals: 方法发送.

 NSData *updatedValue = // fetch the characteristic's new value
    BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
        forCharacteristic:characteristic onSubscribedCentrals:nil];

当你调用该方法时,可以通过最后一个参数来指定你想要更新的订阅characteristic值核心设备.上例中,如果你指定为nil,所有订阅的连接核心设备都将收到更新.
updateValue:forCharacteristic:onSubscribedCentrals:返回一个布尔值(意味向订阅的核心设备发送更新成功).如果用来传输更新数据的底层队列满了,返回NO.Perpheral manager 会在传输队列有空间时调用peripheralManagerIsReadyToUpdateSubscribers:,可以声明本方法来重发值,依旧使用updateValue:forCharacteristic:onSubscribedCentrals:

Note:使用通知来向订阅的核心设备发送的一个数据包,也就是说,当你更新一个订阅的核心设备,你应该在一个通知中发送全部更新数据,通过调用一次updateValue:forCharacteristic:onSubscribedCentrals:.
不是所有的数据都可以通过通知(notification)传输,这取决于characteristic值的大小.如果数据较大,应该在核心端调用CBPeripheralreadValueForCharacteristic:方法接收全部数据

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值