CoreBluetooth初探(二) ---- 做为中心设备使用

执行常见的中心角色任务

在蓝牙低能量通信中发挥中心作用的设备执行许多常见任务,例如,发现和连接可用的外围设备,以及探索和与外围设备必须提供的数据交互。实现外围设备角色的设备还执行许多常见但不同的任务,例如发布和广告服务,以及响应连接中心的读、写和订阅请求。

启动中心设备

因为cbCentralManager对象是本地中央设备的核心蓝牙面向对象表示,所以在执行任何蓝牙低能耗事务之前,您可以分配和初始化中央管理器实例。通过调用其initwitdelegate:queue:options:method初始化中央管理器。

myCentralManager =
        [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];

在本例中,self被设置为代理以接收任何中心角色事件。通过将调度队列指定为nil,中心管理器使用主队列来调度中心角色事件。

创建中央管理器时,中央管理器调用其委托对象的centralManagerDupdateState:方法。您必须实现此委派方法,以确保支持蓝牙低能耗,并可在中央设备上使用。

发现正在广告的外围设备

一旦初始化,中央管理器的第一个任务就是发现外围设备。正如centrals中所提到的,发现并连接到正在广播的外围设备,外围设备通过广播使它们的存在变得众所周知。您的应用程序通过调用中央管理器的外设扫描服务来发现附近正在进行广告宣传的外设:选项:方法:

[myCentralManager scanForPeripheralsWithServices:nil options:nil];

如果为第一个参数指定了nil,则中心管理器将返回所有发现的外围设备,而不考虑其支持的服务。在真实的应用程序中,通常指定一个cbuuid对象数组,每个对象代表外围设备正在广告的服务的通用唯一标识符(uuid)。当您指定服务UUID数组时,中央管理器只返回通告这些服务的外围设备,允许您只扫描您感兴趣的设备。

每次中央管理器发现外围设备时,它都会调用其委托对象的centralManager:didDiscoverPeripheral:AdvertisementData:rssi:method。新发现的外围设备作为CBPeripheral对象返回。如果计划连接到发现的外围设备,请保持对它的强引用,这样系统就不会解除分配它。下面的示例显示了一个场景,其中使用类属性维护对发现的外围设备的引用

- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary *)advertisementData
                  RSSI:(NSNumber *)RSSI {
 
    NSLog(@"Discovered %@", peripheral.name);
    self.discoveredPeripheral = peripheral;
    ...

如果您希望连接到多个设备,那么您可以保留一组发现的外围设备。在任何情况下,一旦你找到所有你想连接的外围设备,停止扫描其他设备以节省电力。

[myCentralManager stopScan];

发现后连接到外围设备

在发现您感兴趣的外围设备广告服务后,通过调用中央管理器的ConnectPeripheral:选项:方法,命名要连接到的发现的外围设备,请求连接到该外围设备:

 [myCentralManager connectPeripheral:peripheral options:nil];

如果连接请求成功,中央管理器将调用其委托对象的centralManager:didconnectPeripheral:方法。在开始与外围设备交互之前,请设置其委托以确保委托接收到适当的回调:

- (void)centralManager:(CBCentralManager *)central
  didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"Peripheral connected");
    peripheral.delegate = self
}

发现连接到的外围设备的服务

建立到外围设备的连接后,可以探索其数据。探索外围设备必须提供的服务的第一步是发现其可用的服务。由于外围设备可以公布的数据量存在大小限制,因此您可能会发现一个外围设备拥有的服务多于它公布的服务(在其广告包中)。您可以通过调用外设的discover services:方法来发现外设提供的所有服务,如下所示:

[peripheral discoverServices:nil];

在真实的应用程序中,通常不将nil作为参数传递,因为这样做会返回外围设备上可用的所有服务。因为外围设备可能包含比您感兴趣的更多的服务,发现所有这些服务可能会浪费电池寿命和不必要的时间使用。相反,您通常指定您已经知道有兴趣发现的服务的UUID

当发现指定的服务时,外围设备(您连接到的CBPeripheral对象)调用其委托对象的peripheral:DidDiscoverServices:method。核心蓝牙创建了一个CBService对象数组,每个对象对应于在外围设备上发现的每个服务。如下图所示,可以实现此委托方法来访问发现的服务数组:

- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {
      for (CBService *service in peripheral.services) {
        NSLog(@"Discovered service %@", service);
      }
}

发现服务的特性

当你找到你感兴趣的服务时,下一步探索外围设备必须提供什么是发现所有服务的特性。发现服务的所有特性与调用外围设备的discoverCharacteristics:forService:method、指定适当的服务一样简单,如下所示:

NSLog(@"Discovering characteristics for service %@", interestingService);
    [peripheral discoverCharacteristics:nil forService:interestingService];

在真实的应用程序中,通常不会将nil作为第一个参数传递,因为这样做会返回外围设备服务的所有特性。由于外围设备的服务可能包含比您感兴趣的特性更多的特性,因此发现所有这些特性可能会浪费电池寿命,并且是不必要的时间使用。相反,您通常指定您已经知道有兴趣发现的特征的UUID。
当发现指定服务的特征时,外围设备调用其委托对象的外围设备:DidDiscoveryCharacteristicsForService:错误:方法。核心蓝牙创建了一个cbCharacteristic对象数组,每个被发现的特征对应一个。下面的示例说明如何实现此委托方法,以简单地记录发现的每个特征:

- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
             error:(NSError *)error {
 
    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"Discovered characteristic %@", characteristic);
        ...
    }
    ...

检索特征值

一个特征包含一个表示外围设备服务信息的值。例如,健康温度计服务的温度测量特性可能具有指示摄氏温度的值。您可以通过直接读取特性值或订阅特性值来检索特性值。

读取特征值

找到感兴趣的服务的特征后,可以通过调用外围设备的readValueForCharacteristic:方法,指定适当的特征来读取特征值,如下所示:

NSLog(@"Reading value for characteristic %@", interestingCharacteristic);
    [peripheral readValueForCharacteristic:interestingCharacteristic];

尝试读取特征值时,外围设备调用其委托对象的外围设备:didUpdateValueForCharacteristic:error:method以检索该值。如果成功检索到该值,则可以通过特征的Value属性访问该值,如下所示:

- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
 
    NSData *data = characteristic.value;
    // parse the data as needed
    ...

并非所有特征都可读。通过检查特性属性是否包括cbCharacteristicPropertyRead常量,可以确定特性是否可读。如果尝试读取不可读的特征值,则peripheral:didUpdateValueForCharacteristic:error:delegate方法返回适当的错误。

订阅特征值

虽然使用readValueForCharacteristic:方法读取特征值对静态值有效,但它不是检索动态值的最有效方法。检索随时间变化的特征值,例如,通过订阅它们来检索心率。当您订阅一个特性的值时,当该值改变时,您会收到来自外围设备的通知。
通过调用外围设备的setNotifyValue:ForCharacteristic:method,将第一个参数指定为yes,可以订阅您感兴趣的特征值,如下所示:

[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];

当您订阅(或取消订阅)特征值时,该外围设备将调用其委托对象的外围设备:didUpdateMotificationStateForCharacteristic:error:method。如果订阅请求因任何原因失败,则可以实现此委托方法来访问错误的原因,如下例所示:

- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error changing notification state: %@",
           [error localizedDescription]);
    }
    ...

并非所有特征都提供订阅。您可以通过检查特性属性是否包含cbCharacteristicPropertyNotify或cbCharacteristicPropertyIndicate常量来确定特性是否提供订阅。

写入数据量

有时候写一个特征的值是有意义的。例如,如果您的应用程序与蓝牙低能量数字恒温器交互,您可能希望为恒温器提供一个设置房间温度的值。如果特征值是可写的,则可以通过调用外设的writeValue:ForCharacteristic:Type:Method,使用数据(nsdata的实例)写入其值,如下所示:

NSLog(@"Writing value for characteristic %@", interestingCharacteristic);
    [peripheral writeValue:dataToWrite forCharacteristic:interestingCharacteristic
        type:CBCharacteristicWriteWithResponse];

编写特征值时,指定要执行的写入类型。在上面的示例中,写入类型是cbCharacteristicWriteWithResponse,它指示外围设备通过调用其委托对象的peripheral:didWriteValueForCharacteristic:error:method来让应用程序知道写入是否成功。您可以实现此委托方法来处理错误条件,如下示例所示:

- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {
 
    if (error) {
        NSLog(@"Error writing characteristic value: %@",
            [error localizedDescription]);
    }
    ...

如果您将写入类型指定为cbCharacteristicWriteWithoutResponse,则将以最佳方式执行写入操作,并且既不保证也不报告传递。外围设备不调用任何委托方法。
特性可能只支持某些类型的写入,或者根本不支持。通过检查某个cbCharacteristicPropertyWriteWithoutResponse或cbCharacteristicPropertyWrite常量的属性属性,可以确定特性支持哪种类型的写入(如果有)。

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