StupidBeauty
Read times:927Posted at:Fri Sep 27 19:24:56 2013
- no title specified

iOS开发文档翻译:完成常见的外设角色任务,Performing Common Peripheral Role Tasks

在上一章节中,妳学习了如何完成蓝牙低功耗通信过程中中央设备一侧的大部分常见任务。在此章节中,妳将会学到如何使用Core Bluetooth 框架来完成蓝牙低功耗通信过程中外设一侧的常见任务。以下这些基于代码的示例将会帮助妳学会如何开发出一个软件来将本地设备实现为一个外设。具体地,妳将会学到:

  • •.启动一个外设管理器对象

  • •.在本地外设上设置好服务和特性

  • •.将妳的服务和特性发布到设备的本地数据库中

  • •.广播妳的服务

  • •.对一个已连接的中央设备的读写请求作出响应

  • •.向已订阅的中央设备发送特性的更新值

此章节中的代码示例都是简单而抽象的;在实际软件的代码中使用时,妳可能需要对它们进行适当的修改。关于如何在妳的本地设备上实现外设角色的更高级话题—包括建议、技巧和最佳实践—都在后续章节 iOS 软件的 Core Bluetooth 后台处理”“将妳的本地设备实现为外设时的最佳实践”中。

启动一个外设管理器

在本地设备上实现外设角色的第一个步骤就是创建并初始化一个外设管理器实例(由 CBPeripheralManager 对象表示)。通过调用 CBPeripheralManager 类的 initWithDelegate:queue:options: 方法来启动妳的外设管理器,就像这样:

myPeripheralManager =

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

在这个示例中,self被设置为代表对象,用于接收任何与外设角色相关的事件。当妳将分发队列设置为 nil 时,外设管理器会使用主事件队列来分发外设角色相关的事件。

当妳创建一个外设管理器时,该外设管理器对象会调用它的代表对象的 peripheralManagerDidUpdateState: 方法。妳必须实现这个方法,以确保蓝牙低功耗在本地外设上是被支持并且是可用的。更多关于如何实现这个代表方法的信息,参考 CBPeripheralManagerDelegate协议参考

设置好妳的服务和特性

之前在图1-7里已经展示过了,本地外设的服务和特性数据库是以一种树型模式来组织的。妳必须在妳的本地外设中以这种树型模式来组织妳的服务和特性。要做这个,妳首先需要知道服务和特性都是怎么唯一标识的。

服务和特性是由宇宙唯一标识符(UUIDs)来标识的

一个外设的服务和特性是由128 位的蓝牙专用宇宙唯一标识符来标识的,后者在Core Bluetooth 框架中由 CBUUID 对象来表示。尽管并不是所有用来标识服务和特性的宇宙唯一标识符都由蓝牙特殊兴趣小组(SIG)来预定义,但是,蓝牙特殊兴趣小组确实定义并发布了一组常用的宇宙唯一标识符,它们都被缩写为16位,以便于使用。例如,蓝牙特殊兴趣小组定义了一个 16位的宇宙唯一标识符,用来标识一个心率服务,它就是180D。这个宇宙唯一标识符是由它的等价 128位宇宙唯一标识符0000180D-0000-1000-8000-00805F9B34FB 缩写而来的,后者是基于蓝牙基本宇宙唯一标识符的,在蓝牙4.0 规范第 3 卷F部分3.2.1 小节中定义。

CBUUID 类提供了工厂方法,使得妳在开发过程中可以更容易地处理长的宇宙唯一标识符。例如,妳不需要在代码中写出心率服务的128位完整宇宙唯一标识符,而只需要简单地使用 UUIDWithString 方法来从该服务预定义的 16位宇宙唯一标识符中创建出一个 CBUUID 对象,就像这样:

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

当妳使用16位预定义的宇宙唯一标识符来创建 CBUUID 的时候,Core Bluetooth 会将蓝牙基本宇宙唯一标识符填充进去形成一个 128位的宇宙唯一标识符

为妳的自定义服务和特性创建专用的宇宙唯一标识符

可能妳的服务和特性并没有对应的预定义蓝牙宇宙唯一标识符。对于这种情况,妳需要生成专用的128位宇宙唯一标识符来标识它们。

使用命令行工具uuidgen 可以轻松地生成128位宇宙唯一标识符。第一步,打开一个终端窗口。接下来,对于妳需要使用宇宙唯一标识符来标识的每一个服务和特性,执行一次 uuidgen 命令,每次将会产生一个唯一的 以美国标准信息交换码(ASCII)格式字符串表示的128位的值,各个部分之间会以连字符分隔,就像以下示例:

$ uuidgen

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

然后,妳可以使用 UUIDWithString 方法来创建一个针对此宇宙唯一标识符的 CBUUID 对象,就像这样:

CBUUID *myCustomServiceUUID =

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

构建妳的服务和特性树

当妳为妳的服务和特性创建好了宇宙唯一标识符(由 CBUUID 对象表示)之后,妳就可以创建可变的服务和特性对象并且像之前所说的那样以树型模式组织它们。例如,如果妳已经有了某个特性的宇宙唯一标识符,则可以通过调用 CBMutableCharacteristic 类的 initWithType:properties:value:permissions: 方法来创建一个可变特性对象,就像这样:

myCharacteristic =

[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID

properties:CBCharacteristicPropertyRead

value:myValue permissions:CBAttributePermissionsReadable];

当妳创建一个可变特性时,妳同时设置它的属性、值和权限。妳所设置的属性和权限将会起到控制作用,决定了:该特性的值是否可读、是否可写;是否允许已连接的中央设备订阅该特性的值。在这个示例里,该特性的值被设置为可被已连接的中央设备读取。关于可变特性所支持的属性和权限范围的更多信息,参考 CBMutableCharacteristic类参考

注意:如果妳为该特性指定了一个值,则该值会被缓存,它的属性和权限会被设置为可读。所以,如果妳需要让这个特性的值是可写的,或者妳预期着这个特性的值在它所从属的服务的生命期中会发生变化,那么妳必须将它的值指定为nil。这样做,就会确保,这个值被当作动态的值来处理,每当外设管理器对象从某个已连接的中央设备收到读取或写入请求时便会重新请求该值。

既然妳已经创建了一个可变特性,接下来就可以创建一个可变服务,以容纳这个特性。只需要调用 CBMutableService 类的 initWithType:primary: 方法,就像这样:

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

在这个示例中,第二个参数被设置为YES,表示这个服务是主要的(primary)而不是次要的(secondary)。主要的服务,用来描述一个设备的主要功能,并且可被另一个服务包含(引用)。次要的服务,描述的是,仅仅在它所引用的另一个服务的上下文条件下才有意义的服务。例如,一个心率监视仪,它的某个主要服务可能是用于报告它的心率传感器的心率数据,而它的某个次要服务呢,可能用于报告该传感器的电池数据。

当妳创建了一个服务之后,可通过设置该服务的特性数组来将某个特性与它关联,就像这样:

myService.characteristics = @[myCharacteristic];

发布妳的服务和特性

当妳构建完了妳的服务和特性树之后,接下来的步骤就是将它们发布到妳的本地设备的服务及特性数据库中。使用Core Bluetooth 框架可以轻易地完成这个任务。调用 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 ,需要由妳想要广播的服务的宇宙唯一标识符对应的 CBUUID 对象组成的数组(一个 NSArray 实例)作为它的键值。在 CBCentralManagerDelegate协议参考 广播数据获取键 里,那些常量说明了妳可以在一个广播数据字典里指定的键。不过呢,对于外设管理器对象,只有两个键是允许使用的: CBAdvertisementDataLocalNameKey CBAdvertisementDataServiceUUIDsKey

当妳开始广播本地外设上的某些数据时,外设管理器对象就调用它的代表对象的 peripheralManagerDidStartAdvertising:error: 方法。妳可以实现这个方法,以便当发生错误导致服务无法被广播时获取到错误的原因,就像这样:

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral

error:(NSError *)error {

if (error) {

NSLog(@"Error advertising: %@", [error localizedDescription]);

}

...

注意:数据的广播是以“尽力而为”原则来进行的,因为存储空间有限,并且可能会有多个软件在同时发送广播。参考 CBPeripheralManager类参考 中的 startAdvertising: 方法,以了解更多信息。

当妳的软件是处于后台状态时,广播行为也会受到影响。下一章节将会讲到这个, iOS 软件的 Core Bluetooth 后台处理”

一旦妳开始广播数据了,远端的中央设备就可以发现并且与妳连接。

对中央设备的读取及写入请求作出响应

当妳与一个或多个远端中央设备连接起来时,妳就可能会开始收到它们发来的读取或写入请求了。如果妳回复它们的话,请确保以一种适当的方式来回复。以下示例说明了该如何处理这种请求。

当某个已连接的中央设备请求读取妳的某个特性的值时,外设管理器对象会调用它的代表对象的 peripheralManager:didReceiveReadRequest: 方法。该代表方法会以一个 CBATTRequest 对象的形式来将该请求传递给妳,该对象拥有一系列的属性,可用来完成该请求。

例如,当妳收到一个针对某个特性的值的简单读取请求时,可使用妳从该代表方法中收到的 CBATTRequest 对象的属性来确保:在妳的设备数据库中的该特性与远端中央设备在原始读取请求中指定的特性是匹配的。妳可以开始实现这个代表方法了,就像这样:

- (void)peripheralManager:(CBPeripheralManager *)peripheral

didReceiveReadRequest:(CBATTRequest *)request {

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

...

如果该特性的宇宙唯一标识符是匹配的,那么,下一步就是,确保该读取请求没有要求读取超出妳的特性的值的数组范围的下标。就像以下示例展示的这样,妳可以使用一个 CBATTRequest 对象的 offset 属性来确保该读取请求没有尝试读取超出范围的下标:

if (request.offset > myCharacteristic.value.length) {

[myPeripheralManager respondToRequest:request

withResult:CBATTErrorInvalidOffset];

return;

}

假设该请求的偏移(offset)值已被验证是有效的,现在,将该请求的特性属性(其值在默认情况下是nil)的值设置为妳在本地外设上创建的特性的值,注意要将该读取请求的偏移值计算在内:

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: 方法。

注意:如果该特性的宇宙唯一标识符不匹配,或者由于别的什么原因而无法完成这次读取请求,则,妳不应当尝试完成该请求。这种情况下的正确做法是,立即调用 respondToRequest:withResult: 方法,返回一个可以表明失败原因的结果。参考 Core Bluetooth常量参考 中的 CBATTError常量 枚举,以了解妳可以用来返回的可选结果。

处理来自于某个已连接的中央设备的写入请求,也是非常直观的。当某个已连接的中央设备请求写入妳的一个或多个特性的值时,外设管理器对象就会调用它的代表对象的 peripheralManager:didReceiveWriteRequests: 方法。这种情况下,该代表方法会以一个数组的形式来将请求信息传递给妳,其中包含着一个或多个 CBATTRequest 对象,每个都表示着一个写入请求。当妳确认某个写入请求可被完成时,妳就写入该特性的值,就像这样:

myCharacteristic.value = request.value;

尽管以上示例没有展示出这一点,但是,当妳向妳的特性写入值时,记得要检查一下该请求的偏移(offset)属性啊。

就像对读取请求做出响应一样,每当 peripheralManager:didReceiveWriteRequests: 代表方法被调用一次时,就将 respondToRequest:withResult: 方法调用一次。也就是说, respondToRequest:withResult: 方法的第一个参数是一个单个的 CBATTRequest 对象,即使妳从 peripheralManager:didReceiveWriteRequests: 代表方法中收到了一个包含多个请求对象的数组的情况下也是如此。妳应当传入数组中的第一个请求对象,就像这样:

[myPeripheralManager respondToRequest:[requests objectAtIndex:0]

withResult:CBATTErrorSuccess];

注意:要像对待单个请求那样来对待多个请求—如果其中任何一个请求无法被完成,那么妳应当拒绝完成其它在一起的所有请求。这种情况下,应当立即调用 respondToRequest:withResult: 方法,返回一个结果以告知失败的原因。

向已订阅的中央设备发送已被更新的特性值

通常情况下,就像“订阅某个特性的值”中说明的那样,已连接的中央设备会订阅到妳的一个或多个特性值。当它们这样做了时,妳就要在它们所订阅的特性的值发生改变时向它们发出通知。以下示例展示了这个过程。

当某个已连接的中央设备订阅了妳的某个特性的值时,外设管理器对象就会调用它的代表对象的 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 = //获取该特性的新值

BOOL didSendValue = [myPeripheralManager updateValue:updatedValue

forCharacteristic:characteristic onSubscribedCentrals:nil];

当妳调用这个方法来向已订阅的中央设备发送更新的特性值时,妳可以通过最后一个参数来指定向哪些中央设备发送更新信息。就像以上示例中的这样,如果妳指定为nil,则所有已连接并且已订阅的中央设备都会收到更新信息(已连接却没有订阅的中央设备会被忽略)。

updateValue:forCharacteristic:onSubscribedCentrals: 方法会返回一个逻辑(Boolean)值,表明该更新信息是否成功地发送到那些已订阅的中央设备了。如果底层用来传输更新值的队列已经塞满,则此方法返回NO。之后,当传输队列里面有了更多空间可用时,外设管理器对象会调用它的代表对象的 peripheralManagerIsReadyToUpdateSubscribers: 方法。妳可以实现这个代表方法,在这种情况下重新使用 updateValue:forCharacteristic:onSubscribedCentrals: 方法来发送更新信息。

注意:使用通知功能来向已订阅的中央设备发送单个数据包。也就是说,当妳向一个已订阅的中央设备发送更新信息时,应当使用单个通知来发送整个更新值,即,只调用 updateValue:forCharacteristic:onSubscribedCentrals: 方法一次。

取决于妳的特性的值的存储空间尺寸,并不一定所有的数据都能在单个通知中发送完毕。在这种情况下,应当在中央设备一侧做补救,通过调用 CBPeripheral 类的 readValueForCharacteristic: 方法来取回整个值。

Your opinions
Your name:Email:Website url:Opinion content:
- no title specified

HxLauncher: Launch Android applications by voice commands

 
Recent comments
2017年4月~2019年4月垃圾短信排行榜Posted at:Thu Sep 26 04:51:48 2024
Qt5.7文档翻译:QWebEngineCookieStore类,QWebEngineCookieStore ClassPosted at:Fri Aug 11 06:50:35 2023盲盒kill -9 18289 Grebe.20230517.211749.552.mp4