Core Bluetooth Programming Guide - Performing Common Peripheral Role Tasks 粗译

Core Bluetooth Programming Guide 粗译 续

以 Peripheral 的身份来执行常用任务

在上一章节中, 你已经学会了如何以 central 的身份来执行一些最常用的 BLE 任务(事务). 在这一章节中, 你会学会如何作为 peripheral 通过使用 Core Bluetooth 框架来执行一些最常见的 BLE Task. 后面的基于代码的几个例子可以帮助你在开发你自己的 app 的时候在本地设备中实现 peripheral 端. 具体来说, 你将学到:

  • 初始化一个 peripheral manager 对象
  • 在本地 peripheral 上设置一些 services 和 characteristics
  • 在你的设备上的本地数据库来发布你的 services 和 characteristics
  • 广告(广播)你的 services
  • 响应来自已连接的 central 端的读写请求
  • 给一个已订阅的 central 发送更新后的 characteristic 的值

你在本章节中找到的代码示例大多是简单而又抽象的; 适配你现实中的app可能需要对这些示例进行一定的修改. 关于在本地上实现 peripheral 端的更多信息—— 比如 tips, tricks, 以及 best practices——后面的章节都会有提到: Core Bluetooth Background Processing for iOS Apps 和 Best Practices for Setting Up Your Local Device as a Peripheral.

初始化一个 peripheral manager (对象)

在本地上实现 peripheral 端的第一步就是alloc 和 init 一个 peripheral manager 实例 (即一个 CBPeripheralManager 对象). CBPeripheralManager 类中的 initWithDelegate: queue: options: 方法可以帮助你初始化 peripheral manager

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

在这个例子当中, self 成为了代理用来接收所有 peripheral 端的事件. 如果你指定第二个参数(队列)为 nil, peripheral manager 将事件派遣到主队列中.

当你创建了一个 peripheral manager 以后, peripheral manager 就会调用代理中的 peripheralManagerDidUpdateState: 方法. 你必须实现该方法来用以确定当前的本地peripheral 设备是否支持 BLE, 并且 BLE 在当前设备上可用. 参阅 CBPeripheralManagerDelegate Protocol Reference 来获取关于实现该代理方法的更多信息

设置你的 services 和 characteristics

如图1-7(在 overview 章节中)所示一个本地的 peripheral 的services 和 characteristics 数据库是以一种树状形式存在的. 你在你的本地 peripheral 上也必须以这种形式来设置你的 services 和 characteristics. 搞定这些任务的第一步就是先搞清楚 services 和 characteristic 是如何被标识的.

用 UUIDs 来标识 Services 和 Characteristics

一个 peripheral 的 services 和 characteristics 一般是用一个蓝牙专用的 128-bit 的UUIDs 来标识, 这些 UUID 在 Core Bluetooth 框架中都是 CBUUID 对象. 虽然并不是所有的 UUID 都会被 Bluetooth SIG( 蓝牙技术联盟 ) 用来预定义 services 和 characteristics, 但是 Bluetooth SIG 发布了一些常用的 UUID 的 16-bit 缩写版本. 例如, Bluetooth SIG 已经给一个叫做心率的 service 预定义了一个 16-bit 版本: 180D, 这个简写对应的 128-bit 版本UUID为: 0000180D-0000-1000-8000-00805F9B34FB, 该 UUID 基于蓝牙基础UUID, 详细定义可参见基于蓝牙 4.0 手册中的 Volume 3, Part F, Section 3.2.1.

CBUUID 类可以提供一些工厂方法以便在开发的时候处理这些超长的 UUID 的时候更简单一点. 例如: 通过简单的调用方法: UUIDWithString: 方法通过传入预设 16-bit UUID来代替128-bit UUID 来创建一个 CBUUID 对象

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

当你用 16-bit 的 UUID 创建好一个 CBUUID 对象以后, Core Bluetooth 就会从 Bluetooth 的 base UUID 中找到对应的 128-bit 版本, 然后自动填充它

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

假如你有一些没有预定义的 Bluetooth UUID 标识的 Services 和 Characteristics. 这时候, 你需要自己生成 128-bit UUID 来标识它们

命令行工具 uuidgen 可以很容易的生成 128-bit 的 UUID. 首先你需要打开一个终端窗口. 由于每一个 Service 和 Characteristic 你都需要通过使用一个 UUID 来标识, 输入 uuidgen 来生成一个以 ASCII 字符串中的连字符串连的唯一的128-bit 值

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

这时候你就可以使用这个 UUID 来传入到 UUIDWithString: 方法中创建一个 CBUUID 对象:

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

创建你的 Services 和 Characteristics 树形结构

为你的 Services 和 Characteristics 生成了 UUID (一个CBUUID 对象)以后, 你就可以创建一个 mutable services 和 characteristics, 然后以树形结构来组织它们. 例如, 如果你的 characteristic 有一个 UUID, 你可以通过 CBMutableCharacteristic 类中的 initWithType: properties: value: permissions: 方法来创建一个 mutable characteristic, 像这样:

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

当你创建完一个 mutable characteristic以后, 设置其 properties, value 以及 permissions. 你得明确你设置的 properties 和 permissions, 例如 characteristic 的 value 是否能够读写, characteristic 的 value 是否能被订阅. 在这个例子当中, 这个 characteristic 的 value 是被设置为允许已连接的 central 进行写入操作. 获取更多关于 mutable characteristic 的可用的 properties 和 permissions, 参阅: CBMutableCharacteristic Class Reference.

注意: 如果你给一个characteristic 指定了value, 并且该 value 已被缓存过, 它的 properties 和 permissions 将会被设置为可读. 因此, 如果你希望 value 能够读取, 或者你希望该 characteristic 所属的 service 在被发布的整个周期里都能够被改变, 那么你必须指定 value 的值为 nil. 这么做能够使得 peripheral 在每次收到已连接的central 的读写请求的时候都能够被 peripheral 进行动态的处理或请求

现在既然你已经创建了一个 mutable characteristic, 你就可以创建一个 mutable service 来与之关联. 你只需要调用 CBMutableService 类中的 initWithType: primary: 方法就可以了, 例如:

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

在这个样例代码中, 第二个参数那里被设定为 YES, 表明该 service 是 primary(主要的) 而不是 secondary(次要的) . 一个 primary service 描述了一个设备的主要功能,并且可以被另一个服务包括(引用)。而 secondary service 描述了一个仅在引用它的另一个服务上下文中相关的服务. 例如, 一个心率监测仪的 primary service 可能就是从监测仪的心率感应器或得到的 心率数据, 然后 这种监测仪的一个 secondary service 可能就是描述感应器的电量数据

在你创建完一个 service 以后, 你就可以把一个 characteristic 以 characteristics 数组的形式这个 service 关联起来.

 myService.characteristics = @[myCharacteristic];

发布你的 Services 和 Characteristics

在你创建好关于你自己的 services 和 characteristics “树” 以后, 接下来你就可以以本地端的peripheral 身份将这些 services 和 characteristics 添加到本地数据库里面, 在 Core Bluetooth 框架下很容易完成这项工作, 调用 CBPeripheralManager 下的 addService: 方法就可以了:

 [myPeripheralManager addService:myService];

当你调用这个方法来发布你的 services 的时候, peripheral Manager 就会调用其代理方法中的 peripheralManager:didAddService:error: . 如果 发布的时候出错了, 实现该方法可以获取到具体错误内容:

- (void)peripheralManager:(CBPeripheralManager *)peripheral
            didAddService:(CBService *)service
                    error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error publishing service: %@", [error localizedDescription]);
    }
    ...

注意: 一旦你把 service 以及 相关联的 characteristics 发布到 peripheral 的数据库里面, 那么该 service 就会被缓存起来, 并且你再也无法修改了

广告 Services

当你在你的数据库里发布了你的 services 和 characteristics 后, 你接下来就可以准备去广告(广播)这些数据中的某部分给任意一个 正在监听的 central. 下面的例子所展示的是, 你可以调用 CBPeripheralManager 类中的 startAdvertising: 方法并传入一组关于广告的数据来广告一部分的 services 或者全部的:

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

在这个例子中, 字典的唯一一个key: CBAdvertisementDataServiceUUIDsKey, 对应的是一个元素全为 CBUUID 对象的数组, 这些 CBUUID 对象对应的就是你希望广播的 services 的UUID. CBCentralManagerDelegate Protocol 中的 Advertisement Data Retrieval Keys 详细的描述了其他你可以写在这个字典中的 key. 其实一共就两个 key 可用: CBAdvertisementDataServiceUUIDsKey 和 CBAdvertisementDataLocalNameKey

当你开始广告你的本地 peripheral 的部分数据的时候, peripheral manager 就会调用代理方法中的 peripheralManagerDidStartAdvertising:error: 方法. 同样也是用来获取错误原因的:

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
                                       error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error advertising: %@", [error localizedDescription]);
    }
    ...

注意: 数据的广告行为是建立在一种称之为”尽力而为”的基础上的, 因为空间是有限的, 并且会有许多的app也在同时的广告数据. 你可以参阅 CBPeripheralManager Class Reference 中的 startAdvertising: 方法来获取更多详细信息

当你的 app 正在后台的时候也会影响到其广告行为. 下一章节: Core Bluetooth Background Processing for iOS Apps 中会有详细讨论.

一旦你开始广告数据, 远程的 central 就可以发现你并且可以初始化一个指向你的连接

响应来自 Central 的读写请求

在你同一个或者多个远程 Central 建立连接以后, 你就需要准备接收来自这些 Central 的读 / 写请求. 接收的时候确保以合适的方式来响应这些请求. 下面的例子就是说明了如何正确的处理这些请求.

当一个已连接的 Central 请求读取一个 characteristic 的 value 的时候, peripheral manager 就会调用代理方法中的 peripheralManager:didReceiveReadRequest: 方法. 这个代理方法会把请求转换成为 CBATTRequest 对象的形式, 一个这样的对象拥有一系列的属性来实现这些请求

比如: 当你接收到一个简单读取 characteristic 的 value 请求的时候, 你从代理fang方法中得到的 CBATTRequest 对象的某个属性就可以用来确认你的peripheral 设备中的数据库中的 characteristic 是否能和远程的 Central 请求中指定的那个匹配.

- (void)peripheralManager:(CBPeripheralManager *)peripheral
    didReceiveReadRequest:(CBATTRequest *)request {
 
    if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
        ...

如果 characteristic 的 UUID 能够匹配, 下一步就是确定这个读取请求的起始索引有没有超出characteristic’s 的 value 的范围, 下面例子展示了一种通过使用 CBATTRequest 对象的 offset 属性来确定当前读取请求并不是试图读取范围之外的数据

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

假设现在请求中的 offset 已被确认(合法), 那么接下来就是把你创建的characteristic 的 value 赋给读取请求中的 characteristic 的value(默认都是nil), 赋值的时候一定要把读取请求中的 offset 考虑进来:

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

赋值完成以后, 响应远程的 Central, 以便表明当前的请求已经成功完成. 以上操作可以通过调用 CBPeripheralManager 类中的 respondToRequest:withResult: 方法, 返回请求(值已更新的请求)和请求的结果:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    ...

确保每次代理方法: peripheralManager:didReceiveReadRequest: 被调用的时候都会调用 respondToRequest:withResult:

注意: 如果 characteristic 的 UUID 不匹配, 或者由于任何原因导致的读取操作未完成, 这时候不要试图去完成请求. 相反, 你可以立即调用respondToRequest: withResult: 方法, 并同时提供一个结果用来表示失败原因. 想知道具体的错误对应的情况, 参阅: Core Bluetooth Constants Reference 中的 CBATTError Constants 枚举

处理已连接的Central的写入请求也很简单. 当一个已连接上的 Central 发送一个请求需要对一个或者多个 characteristic 的值进行写入操作的时候, peripheral manager 就会调用代理方法: peripheralManager:didReceiveWriteRequests: . 只不过这次代理方法是以数组形式返回一个请求, 这个数组包含了一个或者多个的 CBATTRequest 对象, 每一个对象代表一个写请求. 在确认了写入请求可以完成以后, 你就可以给 characteristic 的 value 写入值了:

 myCharacteristic.value = request.value;

虽然上面的这行代码并没有演示到, 但是依然要在给 characteristic 写入的时候依然还是要注意到 request 中的 offset

正如你响应了一个读取请求, 你会在代理方法peripheralManager:didReceiveWriteRequests: 每次被调用的时候同时在方法内调用respondToRequest: withResult: 方法一样, respondToRequest: withResult: 方法的第一个参数要求的是一个单独的 CBATTRequest 对象, 就算你从代理方法 peripheralManager:didReceiveWriteRequests: 那接收到的是一个包含了不止一个 CBATTRequest 对象的数组, 那你应该把数组第一个元素传给方法的第一个参数:

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

注意: 把多个请求当做一个请求来处理— 如果当中任意一个请求不能被满足, 那么所有请求都不需要被执行了. 在第一时间内调用方法: respondToRequest: withResult: 以获得结果来获取错误的原因

向已订阅的 Central 设备发送更新后的 Characteristic Values

我们在前面 Subscribe to a Characteristic’s Value (上一篇博文)有说过, 已连接的 Central 通常会订阅一个或者多个 characteristic values. 如果一个 Central 订阅了你, 那么你就有责向他们发送他们订阅的 Characteristic 在每次发生改变以后的值. 下面的示例告诉了你如何去做:

当一个已连接的 Central 订阅了一个或多个 characteristics 的 value, peripheral manager 就会调用代理方法: peripheralManager:central:didSubscribeToCharacteristic: :

- (void)peripheralManager:(CBPeripheralManager *)peripheral
                  central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
 
    NSLog(@"Central subscribed to characteristic %@", characteristic);
    ...

使用上面的代理作为发送给 Central 更新后的值的触发

接下来, 获取更新后的 characteristic 的 value, 并调用 CBPeripheralManager 类的 updateValue: forCharacteristic: onSubscribedCentrals:

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

当你调用该方法去给 Central 发送更新后的 characteristic values 的时候, 你可以在方法的最后一个参数指定特定的你想要发送的设备. 如果想上面这个例子一样设定为nil, 那么就会向所有已连接并已订阅的设备发送更新后的数据 (如果是已连接的但是并没有订阅的会被忽略掉).

方法: updateValue: forCharacteristic: onSubscribedCentrals: 返回的是一个 Boolean 值代表的是更是否已经成功发送给了订阅了该值的 Central. 如果用于传输更新后的值的底层队列已满,则该方法返回NO. Peripheral manager 会在传输队列可用空间变多的时候调用代理方法: peripheralManagerIsReadyToUpdateSubscribers: . 然后你就可以重新实现这段开头的那个方法来重新发送一遍数据.

注意: 使用一个通知来发送一个完整的数据包给订阅的Central. 也就是说, 当你更新的已订阅的 Central 的时候, 你应该把整个已更新的数据包含在一个通知里, 然后通过调用方法: updateValue:forCharacteristic:onSubscribedCentrals:

但是这也看characteristic value的大小, 也并不是所有的数据都要依靠通知来进行发送. 当数据不能用通知发送的时候, 在 Central 端就应该调用 CBPeripheral 的方法readValueForCharacteristic: 来处理所有的数据

转载于:https://www.cnblogs.com/SquirrelStock/p/5723872.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值