执行普通外设角色任务
在上个章节,已经学习了如何在中央端执行大多数普通的蓝牙低功耗任务。在这个章节,将学习如何在外设端执行大多数普通的蓝牙低功耗任务。下面的基本代码示例将有助于在你的本地设备上开发你的应用程序实现外设角色。特别地,你将学习到如何:
- 启动一个外设管理者对象
- 在你的本地外设上设置服务和特征
- 发布你的设备的本地数据库中的服务和特征
- 广播你的服务
- 根据一个已连接的中央的读写请求作出回应
- 发送更新特征值到订阅的中央
在这个章节你看到的代码示例是简单和抽象的;你可能需要做一些合适的改变来把它们并入到你真实世界的应用程序中。在你的本地设备上实现外设角色相关联的主题有--包括提示,技巧和好的练习--在后面的章节会提到。Core Bluetooth Background Processing for iOS Apps和Best Practices for Setting Up Your Local Device as a Peripheral;
启动一个外设管理者
在你本地设备上实现外设角色的第一步是给一个外设管理者实例(用CBPeripheralManager
对象表示)分配内存和初始化。通过调用CBPeripheralManager
类的initWithDelegate:queue:options:
方法来启动你的外设管理者。就像:
myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
在这个例子中,self
是作为代理来接受任何外设角色的事件。当你指定派遣队列为nil
,外设管理者会用主队列来派遣外设角色事件。当你创建了外设管理者,外设管理者会调用它的代理对象的peripheralManagerDidUpdateState:
方法;你必须实现这个代理方法来确保在本地外围设备上蓝牙低功耗是被支持和能有效使用的。关于如何实现这个代理方法更多的信息,请看CBPeripheralManagerDelegate Protocol Reference
设置服务和特征
在图1-7中显示,一个本地外设的服务和特征的数据结构是用树形方式组织。你必须用这个树形方式组织它们在你的本地外设上设置服务和特征。你执行这些任务的第一步是理解服务和特征是如何标识的。
服务和特征是通过UUIDs来标识
一个外设的服务和特征是通过128位蓝牙指定的UUIDs来标识,在Core Bluetooth
框架中用CBUUID
来表示。并不是所有的UUIDs通过蓝牙技术联盟识别预定义的服务和特征,蓝牙技术联盟定义和发布了一些常用的UUIDs,已经被缩短为16位以方便使用。例如,蓝牙技术联盟已预定义16位的UUID来标识一个心率服务如180D,这个UUID被缩短了从它等价的180位UUID,0000180D-0000-1000-8000-00805F9B34FB,这是由蓝牙4.0规范定义的蓝牙基本UUID。
当你开发你的应用程序的时候,CBUUID
类提供了工厂方法使它更容易处理长的UUIDs。例如,在你的代码中为了替代很长的心率服务128位UUID,你可以简单的使用UUIDWithString:
方法通过服务预定义的16位UUID来创建一个CBUUID
对象, 就像:
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString:@"180D"];
当你用预定义16位UUID创建一个CBUUID
对象,Core Bluetooth
会预先根据蓝牙基本UUID填充128位UUID剩下的部分。
为了自定义服务和特征创建你自己的UUIDs
你可能有一些通过预定义蓝牙UUIDs未标识的服务和特征,如果你在做,你需要生成你自己的128位UUID来标识它们。
使用命令行工具uuidgen
来简单地生成128位UUIDs。开始,打开窗口的终端,为了你需要标识的每个服务和特征的UUID,在命令行输入uuidgen
来接受一个由ASCII码形成的128位唯一值,通过连字符来间断,如下所示:
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
然后你可以利用UUIDWithString
方法使用这个UUID来创建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 permission:CBAttributePermissionsReadable];
当你创建了一个可变的特征,你可以设置它的属性,值,和权限。你设置的属性和特征由特征值是否可读或可写,已经连接的中央能否订阅特征值决定。在这个例子中,特征值设置成可以被连接的中央读取,关于可变特征支持的属性和权限的范围更多信息,请看CBMutableCharacteristic Class Reference。
注意:如果你为一个特征指定了值,这个值是缓存的,它的属性和权限是被设置为可读的。因此,如果你需要这个特征值可写,或者你期望这个值在它服务发布的生命期间改变,你必须指定这个值为
nil
。使用这个方法来确保这个值是动态的并在无论何时外设管理从已经连接的中央接受到读或写的请求时通过外设管理者来请求。
现在你已经创建了一个可变的特征,你可以创建一个可变的服务来关联这个特征。做到这一点,通过调用CBMutableService
类的initWithType:primary:
方法,如下所示:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
在这个例子中,第二个参数设置为YES
,表明这个服务是与次要相对的主要的。主要的服务描述设备主要的功能,能够包含其他的服务。次要服务描述一个只与其他服务上下文引用它相关的服务。例如,心率监测器的主要服务可能是用来探索监测器的心率传感器的心率数据,然而次要服务可能是用来探索传感器的电量数据。
在你创建了一个服务之后,你可以通过设置服务的特征数组来关联特征,就像:
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
对象的数组值,表示你想要广播的服务UUIDs,广播数据的字典中尼可以指定的可能的键在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:
方法的讨论。当你的应用程序处于后台时广播行为依然有效。这个主题在下一个章节讨论,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]) {
...
}
}
如果这个特征的UUIDs是匹配的,接下来的一步是确保读的请求不是从你的特征值以外的位置索引范围来读取。在下面的示例中显示,你可以使用CBATTRequest
对象的offset
属性来确保读的请求不是试图读合适的范围之外的地方:
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request withResult:CBATTErrorInvalidOffset];
return;
}
假设请求的偏移量是有效的,现在将请求的特征属性(值默认是nil
)的值赋给在你本地外设你创建的特征值,考虑到抵消读请求:
request.value = [myCharacteristic.value subdataWithRange:NSMakeRange(request.offset, myCharacteristic.value.length - request.length)];
在你设置了这个值之后,响应到远程中央来表明请求十分成功。通过调用CBPeripheralManager
类的respondToRequest:withResult:
方法,传递请求(你已经更新了值)和这个请求的结果,就像:
[myPeripheralManager respondsToRequest:request withResult:CBATTErrorSuccess];
...
每当有peripheralManager:didReceiveReadRequest:
代理方法回调时就调用一次respondToRequest:withResult:
方法。
注意:如果特征值的UUIDs不匹配,或者如果因为其他的原因读取不成功,你不想要一个完美的请求。为了代替它,你可以马上调用
respondToRequest:withResult:
方法并提供一个结果来表明失败的原因。你可以指定的可能原因的列表,请看Core Bluetooth Constants Reference的CBATTError Constants
枚举。
处理从已经连接的中央发来的写请求也是一样简单。当一个已经连接的中央发送过来一个请求来给你的一个或多个特征写入值,这个外设管理者会调用它的代理对象的peripheralManager:didReceiveWriteRequests:
方法。这次,代理方法给你传递的请求是一个数组包含一个或多个CBATTRequest
对象的形式,每一个都代表一个写的请求。你已经确保写的请求是完整的之后,你可以写入特征值,就像:
myCharacteristic.value = request.value;
虽然上面的例子不能阐述这个,确保在写入你的特征值是考虑请求的偏移量属性。
仅仅当你回应读的请求,每次有peripheralManager:didReceiveWriteRequests:
代理方法回调时都调用一次respondToRequest:withResult:
方法。也就是说,respondsToRequest:withResult:
方法的第一个参数,是一个CBATTRequest
单例对象,即使你从peripheralManager:didReceiveWriteRequests:
代理方法中接受到了一个数组包含多个请求。你应该通过数组中的第一个请求,就像:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0] withResult:CBATTErrorSuccess];
注意:对待多个请求时你要使用单例请求--如果任何一个个别的请求不完整,你不应该使用不完整的它们。为了替代它,马上调用
respondsToRequest:withResult:
方法并提供一个结果来表明失败的原因。
发送更新特征值到订阅的中央
经常,连接的外设会订阅你的一个或多个特征值,正如在Subscribing to a Characteristic's Value中描述。当它们这样做了,你有责任当它们订阅的值改变的时候给它们发送通知。下面的例子描述如何使用。
当一个连接的中央订阅了你的一个特征值,外设管理者会调用它的代理对象的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:
方法返回一个布尔值来表明给订阅的中央发送更新值是否成功。如果用来传递更新值的根本队列满了,这个方法会返回NO
。然后这个外设管理者会调用它的代理对象的peripheralManagerIsReadyToUpdateSubscribes:
方法在传递的队列有更多的空间成为有效的时候。你可以使用这个代理方法来重新发送更新值,然后使用updateValue:forCharacteristic:onSubscribedCentrals:
方法。
注意:使用通知给订阅的中央发送单例包数据。也就是,当你更新一个订阅中央,你应该在一个单独的通知中发送完整的更新值,通过调用一次
updateValue:forCharacteristic:onSubscribedCentrals:
方法。根据你的特征值的大小,通过通知可能�不能传递所有的数据。如果这发生了,这种情况应该在中央端处理,通过调用CBPeripheral
类的readValueForCharacteristic:
方法,来获得完整的数据。
--翻译的文档地址:Performing Common Peripheral Role Tasks