欢迎访问个人博客 muhlenXi,该文章出自个人博客, 欢迎转载,转载请注明来源: muhlenxi.com/2017/05/01/…。html
在这一节,你将会学到,如何经过 CoreBluetooth 框架来实现 Local Peripheral 方面的功能和代理方法。数据库
在 iOS BLE 开发小记[2]中,你已经学到了如何在 Central 方面去调用 BLE 的经常使用方法。在这一节中,你将学习用 CoreBluetooth 框架来调用 Peripheral 方面 BLE 的经常使用方法。经过本文的示例代码,将会引导你开发一个将你的 Local 设备实现为 Local Peripheral。你将会从中学到:数组
或许你发现示例代码过于简单和抽象,你须要在你的 App 中作些恰当的练习来掌握这些内容。更高级的技巧和最佳实践在后续的文章中将会讲解。缓存
在 Local Device(当前设备)实现 Peripheral 规范的第一步是分配(allocate)和初始化(initialize)一个周边管理(Peripheral Manager),(用 CBPeripheralManager 对象表示),经过调用 CBPeripheralManager 的 initWithDelegate:queue:options:
方法来建立管理对象,以下所示app
myPeripheralManager =
[[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
复制代码
在示例代码中,设置 Delegate 为 self 是为了接收 Peripheral 的事件响应,将参数 dispatch queue 置为 nil。意味着 Peripheral Manager 将会在主队列中分发事件。框架
当你建立一个 Peripheral Manger 对象时,Peripheral Manager 会经过 peripheralManagerDidUpdateState:
方法来代理回调,你必须实现这个代理方法来确保当前设备是否支持 BLE 技术,关于代理方法的详情能够查阅 CBPeripheralManagerDelegate Protocol Reference.学习
在第一节中,咱们了解到,一个 Local Peripheral 采用树形结构来组织 Services 和 Characteristics 的数据。所以必须采用树形结构方式来设置 Local Peripheral 的 Services 和 Characteristics。你第一步要作的是搞清和理解 Service 和 Characteristic 是如何标识的。fetch
Peripheral 的 Service 和 Characteristic 是经过 128 位的特定蓝牙 UUID(通用惟一识别码)来标识的,在 CoreBluetooth 中是用 CBUUID 对象来表示的。并非全部的 UUID 都是经过 Bluetooth Special Interest Group (蓝牙特别兴趣小组)预约义的。为了方便起见,Bluetooth SIG 定义和发布了许多通用的 16位 UUID。举个例子,Bluetooth SIG 事先定义了一个16位的 UUID 用来标识一个心率 Service,该 UUID 是 128位 UUID 0000180D-0000-1000-8000-00805F9B34FB 进行缩减而来的,这是基于蓝牙 4.0 规范中,第 3 卷 F 部分第 3.2.1 节定义的蓝牙基础 UUID。ui
CBUUID 提供了一个处理比较长的 UUID 的工厂方法,举个例子,生成一个表示心率 Service 的 UUID,能够调用 UUIDWithString
方法来经过预约义的 16位 UUID来建立 CBUUID 对象。spa
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
复制代码
当你经过预约义的 16位 UUID 来建立 CBUUID 对象时,CoreBluetooth 会基于128位Bluetooth Base UUID 填充剩下的的 UUID 位。
你的 Service 和 Characteristic 的UUID也许可能没有被 Bluetooth UUIDs 预约义,若是没有被预约义,你须要手动生成你本身的 128位 UUID 来表示 Service 和 Characteristic。
经过命令行命令 uuidgen
能够生成 128位的 UUID,打开你的 Terminal(终端),经过这种方式依次为你的 Services 和 Characteristics 生成一个 UUID (用连字符链接起来的字符串)来标识。举例以下:
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
复制代码
你能够用上面方法生成的 UUID 调用 UUIDWithString
方法来建立一个 CBUUID 对象。
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];
复制代码
当你为每一个 Service 和 Characteristic 建立 CBUUID 对象后,你能够建立 mutable Service(可变服务) 和 mutable Characteristic(可变特征),而后以树形的方式组织它们。举个例子,若是你如今有一个 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 的值是否能够读或者写,或者链接的 Central 可否订阅该 Characteristic 的值。下面的示例中,Characteristic 的值是被指定为可读的。关于 mutable Characteristic 的 properties 和 permissions 详情能够查阅 CBMutableCharacteristic Class Reference.
提示:若是你指定了 Characteristic 的值,那么该值将被缓存而且该 Characteristic 的 properties 和 permissions 将被设置为可读的。所以,若是你须要 Characteristic 的值是可写的,或者你但愿在 Service 发布后,Characteristic 的值在 lifetime(生命周期)中依然能够更改,你必须将该 Characteristic 的值指定为 nil。经过这种方式能够确保 Characteristic 的值,在 Peripheral Manager 收到来自链接的 Central 的读或者写请求的时候,可以被动态处理。
既然你建立了一个 mutable Characteristic,你也能经过调用 CBMutableService
类的 initWithType:primary:
方法建立一个 mutable Service。以下所示:
myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];
复制代码
在示例代码中,第二个参数被指定为 YES,用来代表该 Service 是 Primary(主要的),而不是 secondary(次要的)。一个 Primary Service 用来描述这个设备的主要功能,还能够用来引用其余的 Service。一个 Secondary Service 用来描述的是上下文中相关的或者被引用的 Service。举个例子,从心率传感器中获取心率的服务是 primary Service,而获取传感器电量的服务就能够被视为 secondary Service 。
当你建立完 Service 后。你须要设置 Service 的 Characteristic 数组属性,以下:
myService.characteristics = @[myCharacteristic];
复制代码
当你构建好服务特征树后,下一步就是按照 BLE 的规范发布到设备的服务特征库中,用 CoreBluetooth 能够很轻松的完成这一步,只须要调用 CBPeripheralManager
类 的 addService:
方法就能够了。 示例代码以下:
[myPeripheralManager addService:myService];
复制代码
当你调用该方法发布服务时,Peripheral Manager 会调用 peripheralManager:didAddService:error:
方法进行代理回调,实现这个代理方法能够获取到产生的错误,示例代码以下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error {
if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
// ...
}
复制代码
提示:当你发布 Service 和相关的 Characteristic 到 Peripheral 的数据库中后,设备已经将数据缓存,你不能再改变它了。
当你发布你的 Service 和 Characteristic 到设备的服务特征库时,你能够广播一些服务给正在监听的 Central,你能够经过调用 CBPeripheralManager
类的 startAdvertising:
方法来开始广播,传入的字典是要广播的数据。
[myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];
复制代码
在示例代码中,传入的字典中惟一的 key 是 CBAdvertisementDataServiceUUIDsKey
,用一个包含 CBUUID 对象的数组来表示你想要广播的服务的 UUID。你在字典中能够指定的其余 key 在 Advertisement Data Retrieval Keys 中有详细说明。也就是说,仅有 CBAdvertisementDataLocalNameKey
和 CBAdvertisementDataServiceUUIDsKey
这两个 key 支持 Peripheral Manager 对象。
当你在本地设备中广播一些数据时,Peripheral Manager 会经过 peripheralManagerDidStartAdvertising:error:
方法来代理回调。若是你的设备不能广播而发生错误时,实现这个代理方法能够获取产生的错误:
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error {
if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
// ...
}
复制代码
提示:广播数据方法会被尽力执行,由于空间是有限的和多个 APP 可能同时须要广播数据,更多详情能够查阅关于 startAdvertising: 方法的讨论。
当你的 APP 在后台运行时也会影响广播的行为,这一内容将会在下一篇中进行讨论。
当你链接一个或多个 Central 后,你可能会收到读或者写的请求,对这些请求做出响应须要采起恰当的方式,下面的示例代码将会描述如何处理这些请求。
当一个链接的 Central 发送读取某个 Characteristic 数据的请求时,Peripheral Manager 会调用 peripheralManager:didReceiveReadRequest:
方法进行代理回调。代理方法以 CBATTRequest
对象的方式来传递请求,它包含一些请求的属性。
好比,当你收到一个读取 Characteristic 值的简单请求时,能够经过代理方法回调的 CBATTRequest
对像来判断 Central 指定要读取的 Characteristic 是否和设备服务库中的 Characteristic 是否相匹配。你能够开始实现这个代理方法,示例代码以下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
// ...
}
}
复制代码
若是 Characteristic 的 UUID 可以匹配,下一步就是确保读取请求的位置没有超出 Characteristic 的值的边界。以下面代码所示,你能够经过使用 CBATTRequest
对象的 offset
属性来确保读取请求没有尝试读取范围以外的数据。
if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}
复制代码
假如读取请求的 offset(偏移)已经确认,如今就能够设置请求的 Characteristic 的属性(默认值为 nil)为你设备中的 Characteristic 的值了,你应该重视读取请求的偏移:
request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];
复制代码
设置完值后,经过调用 respondToRequest:withResult:
方法并传入 request(更新值后的)和 请求的结果参数来对 Remote Central 的请求做出响应表示请求已经被成功处理。示例代码以下:
[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
// ...
复制代码
只要代理方法 peripheralManager:didReceiveReadRequest:
方法被回调,就须要准确的调用 respondToRequest:withResult:
方法。
提示:若是 Characteristic 的 UUID 不匹配,或者由于某种缘由不能彻底读取,没必要去填充请求,直接调用 respondToRequest:withResult:
方法并提供一个表示失败的结果便可。你可能指定的结果列表见 CBATTError Constants 常量枚举。
处理链接的 Central 写入请求也比较易懂。当 Central 发送一个写入请求给一个或多个你的 Characteristic 时,Peripheral Manager 会经过 peripheralManager:didReceiveWriteRequests:
方法来代理回调。这是,代理方法会传递一个包含一个或多个 CBATTRequest
对象的数组给你,数组中的每一个对象都表明一个写入请求。当你肯定写入请求可以处理时,你能够设置 Characteristic 的值,示例代码以下:
myCharacteristic.value = request.value;
复制代码
虽然上述例子没有证实这一点,但当你给 Characteristic 写数据的时候,你应该确保请求的 offset 属性的范围有效。
就像你响应读取请求同样,只要代理方法 peripheralManager:didReceiveWriteRequest:
方法被回调,就须要准确无误的调用 respondToRequest:withResult:
方法。也就是说,respondToRequest:withResult:
方法指望有一个 CBATTRequest
对象,即便你可能经过 peripheralManager:didReceiveWriteRequests:
代理方法接收到一个包含 CBATTRequest
对象的数组,你也应该传入数组中的第一个对象,示例代码以下:
[myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];
复制代码
提示:将多请求视为单一请求来对待,若是个别的请求不能被填充,你就没必要填充其他的请求了,直接调用 respondToRequest:withResult:
方法并提供一个表示失败的结果便可。
链接的 Central 常常会订阅一个或多个 Characteristic 的值,当这些值发生变化时,你应该发送通知给订阅的 Central 。
当一个链接的 Central 订阅一个或多个你的 Characteristic 值时,Peripheral Manager 会经过 peripheralManager:central:didSubscribeToCharacteristic:
方法来代理回调。示例代码以下:
- (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"Central subscribed to characteristic %@", characteristic);
// ...
}
复制代码
将上述的代理方法做为一个线索来开始给 Central 发送更新后的值。
接着,获取更新后的 Characteristic 的值,经过调用 CBPeripheralManager
类的 updateValue:forCharacteristic:onSubscribedCentrals:
方法来给 Central 发送通知。示例代码以下:
NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];
复制代码
当你调用这个方法给订阅的 Central 发送通知时,你能够经过最后的那个参数来指定要发送的 Central,示例代码中的参数为 nil,代表将会发送通知给全部链接且订阅的 Central,没有订阅的 Central 则会被忽略。
updateValue:forCharacteristic:onSubscribedCentrals:
方法会返回一个 Boolean 类型的值来表示通知是否成功的发送给订阅的 Central 了,若是 base queue (基础队列)满载,该方法会返回 NO,当传输队列存在更多空间时,Peripheral Manager 则会调用 peripheralManagerIsReadyToUpdateSubscribers:
代理方法进行回调。你能够实现这个代理方法,在方法中再次调用 updateValue:forCharacteristic:onSubscribedCentrals:
方法发送通知给订阅的 Central。
提示:用通知发送单个数据包给订阅的 Central,就是说,一旦订阅的 Central 发行更新时,你就应该调用 updateValue:forCharacteristic:onSubscribedCentrals:
方法用单一通知发送所有的更新值。
并非全部的数据都是经过通知来传输的,这主要取决于你的 Characteristic 的值的大小,只有当 Central 调用 CBPeripheral
类的 readValueForCharacteristic:
方法时,你能够检索所有的值。
一、Performing Common Peripheral Role Tasks
欢迎在本文下面留言一块儿交流心得...