蓝牙(CoreBluetooth)-外部设备(服务端)
蓝牙(CoreBluetooth)-外部设备(服务端)
主要内容
1. 创建外部管理器对象
2. 设置本地外设的服务和特征
3. 添加服务和特征到到你的设置的数据库中
4. 向外公布你的的服务
5. 相应来自连接上的中心设备的请求
6. 向订阅了特征值改变的中心设备发送通知
1. 创建外设管理器
首先你需要创建一个CBPeripheralManager
对象,通过CBPeripheralManager
的initWithDelegate:queue:options:
,像这样:
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
这里设置self来作为外设的任何事件的接受者.
queue:表示外设管理器分发事件的队列,当你指定为nil,外设管理器将在主队列中的分发外设事件.
当你创建CBPeripheralManager对象时,这个管理器对象会调用peripheralManagerDidUpdateState:
方法,你必须实现这个来判断你设备是否支持并开启蓝牙4.0
2. 设置服务和特征
你必须按树形结构进行管理服务
和特征
,树结构如图
![](http://upload-images.jianshu.io/upload_images/321489-169703d4ede75a39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
1. 服务和特征通过UUID
来标示的
外设的服务和和特征通过一个128位的蓝牙UUID
来标示的,在在CoreBlueTooth
框架中是通过CBUUID
来表示的,尽管不是所有服务和特质的UUID都需要通过蓝牙特定兴趣组来(SIG[ Special Interest Group])重定义,但是通过SIG
定义或分发的通用UUID
可以被缩短为16位.例如:通过蓝牙SIG
定义表示心率的服务的16为UUID180D
,这个UUID是蓝牙4.0,特定的服务UUID0000180D-0000-1000-8000-00805F9B34FB
简便形式.
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
当你通过16为的简便形式创建一个UUID,核心蓝牙会把填满为128位的UUID通过基UUID
2. 创建你自己的服务和特征的UUID
,
1. 你可以通过终端生成一个UUID
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
2. 你可以使用这个UUID,通过CBUUID
的UUIDWithString:
方法创建一个CBUUID
对象,像这样
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
3. 构建你的服务和特征树
当你有了服务或特征标示你就可以按照上面的特征服务树来组织你的服务和特征.你可以通过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
,它说明这个服务是一个主要
的服务而不是次要
的,主服务
描述了一个服务的主要功能,他可以包含
其他的服务
,次要服务
必须关联在他所参照的其他服务的上下文中.比如:心率检测器的主要服务
是提供心率的数据的,那么次要服务
可能就是提供心率检测器的电量数据的.
在你创建服务之后,你可以关联那个特征到这个服务上,像这样:
myService.characteristics = @[myCharacteristic];
3. 添加布服务和特征到外设数据库
在你创建了服务和特征之后,下一步就是添加服务到设备的服务和特征数据库中,在蓝牙核心框架中很容易做.你只需要调用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]);
}
...
注意
:当你发布一个关联在外设数据库的服务和特征时,这个服务将被缓存,并且你以后再也不能在修改它了.
4. 公布你的服务给监听的中心管理器
当你已经添加服务和特征到外设的服务特征数据库
中后,你已经公布给服务或特征给中心管理器
做好准备,你可以通过你的CBPeripheralManager
的startAdvertising:
,传入一个包含需要公布数据的字典.
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
这个例子中通过CBAdvertisementDataServiceUUIDsKey
这个key
来指定的需要公布分服务的 UUID
,可能允许使用的key
,在CBCentralManagerDelegate Protocol Reference.
的Advertisement Data Retrieval Keys
中描述:
1. CBAdvertisementDataLocalNameKey 对应的值是一个字符串,描述外设的名称
2.CBAdvertisementDataManufacturerDataKey 对应的值是一个NSData对象,包含外设的产生的数据
3. CBAdvertisementDataServiceDataKey 包含特定服务的分发数据,该字典的key为代表着该服务的CBUUID对象.值为NSData对象
4. CBAdvertisementDataServiceUUIDsKey 需要公布的服务的`UUID`数组
5. CBAdvertisementDataOverflowServiceUUIDsKey 代表着在公布数据的"overflow"区域能够被发现的服务的UUID的数组,因为存储在这个`UUID`列表是`最大努力的` 并且不总是精确的.如果设备资源不足这些属性可能不会被公布.
6. CBAdvertisementDataTxPowerLevelKey 一个包含外设发射功率NSNumber的数字,如果外设在广播的数据包中,提供了他的`Tx`功率级别时候,这个属性是可用的. 使用这个`RSSI` 值和电台功率,计算出路径损耗是有可能.
7. CBAdvertisementDataIsConnectable 一个布尔值,标示公布事件类型是否为可连接的,对应这个Key是一个 NSNumber对象,你可用使用这个值来检查一个外设当前是否为连接状态
8. CBAdvertisementDataSolicitedServiceUUIDsKey 一个代表着一个或多个服务的`UUID`
但是对于外设管理器只支持两个key CBAdvertisementDataLocalNameKey
和 CBAdvertisementDataServiceUUIDsKey
.
当你在你的本地外设开始公布公布你的外设数据的时候,会调用外设管理器代理的peripheralManagerDidStartAdvertising:error:
,你可以通过实现这个方法,来查看你公布数据时的错误
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...
注意:
数据发布是基于最大努力
的,由于带宽是有限并且多个app可能同时发布,更多信息参考CBPeripheralManager
的startAdvertising:
的注释.
'当你应用后台也可以进行发布行为,这个话题参照core bluetooth
文档的Core Bluetooth Background Processing for iOS Apps
一旦你开始公布数据,远程的中心设备就可用发现并连接上你.
5. 响应来自中心设备
的读
和写
请求
当你连接上一个或多个中心设备后,你便开始接受来自他们的读或写请求,请你确保以合适方式来响应的这些请求,下面的例子来描述如何相应这些请求
当一个中心管理器对去某个特征中的值的时候,会调用外设管理器的代理方法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;
}
假定已经验证通过,你可以通过本地外设的特征的值
给请求
设置值(它默认是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:
注意
: 如果特征的UUID
不匹配或因任何原因导致的读不能完成.你不要试图来填充请求的值,而是立即调用respondToRequest:withResult:
方法提供一个失败的原因.详情参照CBATTError
枚举
处理来自中央设备的写请求是简单的,当一个中心设备发送对象一个或多个特征值的写请求时,外设管理器就会调用其代理peripheralManager:didReceiveWriteRequests:
方法,在这个方法中传入了一个CBATTRequest
对象的数组.每一个都代表着一个写请求.在确认请示是可以被满足的后,你可以写特征的值,像这样:
myCharacteristic.value = request.value;
尽管上面的例子中没有演示,当写入你的特征值时,如何确保写入的偏移量.但是在你真实的app中需要进行处理.
和是响应读请求一样,在代理方法peripheralManager:didReceiveWriteRequests:
明确调用一次respondToRequest:withResult:
方法.这也就是说第一个参数是单独一个请求对象,尽管你可能在peripheralManager:didReceiveWriteRequests:
方法中受到了多个请求对象.你应该传入数组中的第一个请求作为参数.像这样:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
注意
:对于多个请求对象只有一次请求,只要任意的一个请求对象得不到满足,你任意一个都不要满足,而是立即调用respondToRequest:withResult:
方法提供一个结果,告诉失败的原因.
6. 发送更新特征的值给已经订阅的中心设备
通常情况,连接上的中央设备将订阅你的一个或多个特征值, 当他们这么做的时候,你有责任当某个订阅特征的值发生改变了通知他们.下面的例子描述如何做.
当一个连接上的中心管理器订阅了你的特征上的某个值,那么外设管理器就会调用其代理的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,表示所有订阅的中心设备都更新数据(连接上没有订阅的中心设备讲被忽略).该方法返回一个Bool
,来指示是否发送成功,如果用于传入更新值的队列是满的,那么该方法返回NO
,当传输队列可用的时,会调用外设管理器
的代理peripheralManagerIsReadyToUpdateSubscribers:
,你可以实现这个方法再次传送数据
注意:
使用这个方法发送通知给订阅的中心设备管理器一个单独的数据包,当你更新数据的应该把整个数据作为一个通知发送.只调用一次updateValue:forCharacteristic:onSubscribedCentrals:
方法,如果你的数据不能通过一次通知传递过去,解决方案就是在中心设备
一边,通过CBPeripheral
的readValueForCharacteristic:
方法,来获取数据,这个方法可以取回整个数据.
外设流程流程图