Core Bluetooth库编程指南官方文档翻译1-4

执行普通外设角色任务

在上个章节,已经学习了如何在中央端执行大多数普通的蓝牙低功耗任务。在这个章节,将学习如何在外设端执行大多数普通的蓝牙低功耗任务。下面的基本代码示例将有助于在你的本地设备上开发你的应用程序实现外设角色。特别地,你将学习到如何:

  • 启动一个外设管理者对象
  • 在你的本地外设上设置服务和特征
  • 发布你的设备的本地数据库中的服务和特征
  • 广播你的服务
  • 根据一个已连接的中央的读写请求作出回应
  • 发送更新特征值到订阅的中央

在这个章节你看到的代码示例是简单和抽象的;你可能需要做一些合适的改变来把它们并入到你真实世界的应用程序中。在你的本地设备上实现外设角色相关联的主题有--包括提示,技巧和好的练习--在后面的章节会提到。Core Bluetooth Background Processing for iOS AppsBest 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 ReferenceAdvertisement Data Retrieval Keys中有详细描述。也就是说,外设管理者对象只支持两个键:CBAdvertisementDataLocalNameKeyCBAdvertisementDataServiceUUIDsKey
当你开始广播你本地外设的数据时,外设管理者会调用它的代理对象的peripheralManagerDidStartAdvertising:error:方法。如果有错误产生,你的服务不能被广播,实现这个代理方法来访问错误的原因。就像:

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

注意:数据的广播是在“尽力服务”的基础上完成的,因为空间的限制和有可能被多个应用程序同时广播。更多的信息,请看CBPeripheralManager Class ReferencestartAdvertising:方法的讨论。当你的应用程序处于后台时广播行为依然有效。这个主题在下一个章节讨论,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 ReferenceCBATTError 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容