当前位置: 移动技术网 > IT编程>移动开发>IOS > 关于iOS蓝牙CB框架

关于iOS蓝牙CB框架

2018年03月30日  | 移动技术网IT编程  | 我要评论

大汉飞将传,东安黑豹,樱狼h文

To conserve power, most Bluetooth devices operate as low-power, 1 mW radios (Class 3 radio power).

Bluetooth is both a hardware-based radio system and a software stack that specifies the linkages between layers.

The Bluetooth specification defines a wide range of profiles, describing many different types of tasks, some of which have not yet been implemented by any device or system. By following the profiles’s procedures, developers can be sure that the applications they create will work with any device that conforms to the Bluetooth specification.

发送搜索广播并等待确认回应, 查询范围内的所有设备.

Core Bluetooth Overview

1 iOS蓝牙开发概述(使用Core Bluetooth framework)

import

1.1 主(central设备)从 (peripheral设备)的概念和它们的作用

和传统的客户-服务器结构类似, 从设备一般都是为主设备提供数据. 主设备则使用这些数据来完成特定任务.

1.1.1 主设备发现并连接正在广播(advertising)的从设备

从设备会将一些数据以包(advertising package)的形式进行广播.

An advertising packet is a relatively small bundle of data that may contain useful information about what a peripheral has to offer, such as the peripheral’s name and primary functionality.

这种数据包实际就是一小团数据, 包含比如从设备的功能或名字等信息.

比如一个温度计就会广播它的功能是: 可以提供当前室内温度.

In Bluetooth low energy, advertising is the primary way that peripherals make their presence known.

BLE中, 广播是从设备声明自己存在的主要途径.

主设备可以扫描和监听任何从设备, 只要从设备正在广播信息.

主设备会要求连接到它发现的从设备, 从而获取广播数据.

1.1.2 从设备数据

主设备连接到从设备的目的就是获取从设备提供的数据.

那从设备的数据是如何构造出来的?

从设备中可能会提供一个或多个服务, 或提供当前的连接信号强度等信息.

A service is a collection of data and associated behaviors for accomplishing a function or feature of a device (or portions of that device).

服务指的是一组数据以及同数据同数据相关联的行为, 它们共同作用来完成设备的部分或全部功能.

比如心跳检测设备的一个服务就是获取它的心跳传感器数据.

服务又是由特征或子服务(即其他服务的引用)所组成.

特征用于提供详细信息.

比如心跳检测设备的某个服务, 该服务包含两个特征, 其中一个特征提供心跳传感器当前处在身体哪个位置, 另外一个特征则提供心跳检测数据.

\

1.1.3 主设备得到从设备数据并进行处理

主从设备间建立连接之后, 主设备便可以访问从设备的所有服务(广播数据可能只包含一部分可用服务).

主设备可以读写服务特征, 以这样的方式同从设备进行交互.

比如APP可以从蓝牙温度计上获取当前室内温度, 或者说APP可以设置蓝牙恒温机上的恒定温度.

1.2 Core Bluetooth中的主从设备及数据表示

1.2.1 主设备侧的对象表示

当从APP连接其他设备时, 就是在主设备侧编程. 并且绝大多数都是这种情况.

主设备: CBCentralManager对象表示.

该对象用于管理扫描到的或已连接的从对象. 包括扫描(正在广播的), 发现(服务和特征), 连接(正在广播的)从设备.

\

1.2.2 从设备数据表示

从设备: CBPeripheral对象.

服务: CBService 对象.

特征: CBCharacteristic对象.

从设备上服务和特征的基本结构如下图所示:

\

即从设备上有多个服务, 服务中又可能包含多个特征.<喎? f/ware/vc/"="" target="_blank" class="keylink">vcD4NCjxwPii9q7G+tdjJ6LG41/fOqrTTyeixuL3awtQmaGVsbGlwOyk8L3A+DQo8aDIgaWQ9"2-将本地设备作为主设备">2 将本地设备作为主设备

本地设备作为主设备时, 通常都会进行如下工作: 发现并连接到可用从设备, 然后对从设备数据进行读写操作.

步骤通常都是:

创建一个主设备对象 扫描并连接到正在广播的从设备 发现从设备提供的各种数据服务 对服务中的特征进行读写操作(另外还可以设置当从设备数据改变时得到通知) 关闭连接

2.1 创建主设备对象

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

上面代码将self作为主设备对象代理, 即主设备对象的事件都交由self处理, 并且将queue设置为nil, 即主设备对象的事件处理是在main queue上完成.

建立主设备对象后, 首先它会调用代理中的centralManagerDidUpdateState:方法, 需要实现这个方法来保证BLE正常工作. (实现细节详见:CBCentralManagerDelegate Protocol Reference).

2.2 发现正在广播的从设备

前面就讲到了, 主设备要发现从设备的话, 必须是该从设备正在进行广播(advertising). 只要从设备在广播, 就可以被发现.

使用主设备对象的scanForPeripheralsWithServices:options:方法来发现从设备:

[myCentralManager scanForPeripheralsWithServices:nil options:nil];

上面方法第一个参数nil, 表示发现任何设备, 不管它的服务本程序是否支持. 但实际工作中, 需要指定一个CBUUID数组, 用UUID代表每个服务, 这样就可以只扫描到拥有特定服务的从设备.

从设备服务和特征都是使用UUID进行统一标识, 那么什么是UUID和CBUUID?

从设备中的服务和特征都是通过128位的UUID进行标识, 在Core Bluetooth框架中是CBUUID对象. 大多数常用的服务和特征的UUID都被SIG组织进行了预定义并且公布出来了, 并且为了方便使用都缩短成了16位.

比如SIG预定义的心跳检测服务UUID为180D, 是它的对应128位UUID “0000180D-0000-1000-8000-00805F9B34FB” 中间部分截取出来的. 128位这个UUID在蓝牙4.0规范卷3, F部分, 3,2,1节定义.

CBUUID类提供了一个类方法, 方便生成UUID, 使用方法如下所示:

CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];

一般情况下, 如果指定的是16位UUID, CoreBluetooth会自动自动将其填充为规范内的128位UUID.

如果自己有特殊的服务, 则可以使用终端生成自己的UUID

$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7

则可以自己构造对应CBUUID:

CBUUID *myCustomServiceUUID =
        [CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];

当使用scanForPeripheralsWithServices方法扫描从设备时, 每扫描到一个从设备, CBCentralManager对象就会调用一次代理中的- centralManager:didDiscoverPeripheral:advertisementData:RSSI:方法.

因为扫描到一个从设备, 就会返回一个CBPeripheral对象, 则可以像如下这样打印扫描到的从设备:

- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary *)advertisementData
                  RSSI:(NSNumber *)RSSI {

    NSLog(@"Discovered %@", peripheral.name);   //打印设备名
    ...

若扫描到了需要的设备, 则需要关闭扫描以节约电量:

[myCentralManager stopScan];    //停止扫描
    NSLog(@"Scanning stopped");

2.3 连接到从设备

调用如下方法连接到某从设备:

[myCentralManager connectPeripheral:peripheral options:nil];

假如连接成功, 主设备对象会调用下面的代理方法:

- (void)centralManager:(CBCentralManager *)central
  didConnectPeripheral:(CBPeripheral *)peripheral {

    NSLog(@"Peripheral connected");
    ...

建立连接完毕, 还需要设置从设备对象的代理, 以保证正常接收回叫, 如下所示:

peripheral.delegate = self;

2.4 发现从设备的服务

连接建立完毕, 就可以使用从设备的服务了, 首先即发现从设备的可用服务.

由于从设备的广播数据包大小收到限制, 所以要向从设备发现服务, 而非在广播数据包中发现服务(前面也说过, 广播数据包中只有一部分的可用服务).

使用如下格式获取从设备的所有服务:

 [peripheral discoverServices:nil];

该参数指定为nil表示发现所有服务, 当然实际APP中只会去发现部分服务. 也是指定服务UUID.

当发现指定服务之后, 从设备对象会调用代理对象中的 peripheral:didDiscoverServices:方法. CB框架会创建一个CBService对象数组, 每个对象代表一个服务, 通过这个数组就可以访问每个服务了:

- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error {

    for (CBService *service in peripheral.services) {
        NSLog(@"Discovered service %@", service);
        ...
    }
    ...

2.5 发现服务中的特征

当发现指定服务后, 需要获取服务中特征数据.

此时需要使用从设备对象的discoverCharacteristics:forService:方法:

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

同样地, 实际使用时不会设置nil来获取所有特征, 而是通过UUID获取某些指定的特征.

当发现特征后, 从设备对象会调用代理对象中的peripheral:didDiscoverCharacteristicsForService:error:方法.

CB框架会自动生成一个CBCharacteristic对象数组, 里面的每个对象代表一个发现的特征:

- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
             error:(NSError *)error {

    for (CBCharacteristic *characteristic in service.characteristics) {
        NSLog(@"Discovered characteristic %@", characteristic);
        ...
    }
    ...

2.6 获取特征中包含的数据

一个特征中包含服务的详细信息, 这个信息在特征中是单个值.

比如体温测量服务中的温度特征一般包含的就是测量到的温度数值, 那么就可以通过这个特征获取温度, 或是直接监控这个特征.

2.6.1 读取特征数据

当发现特定服务中的指定特征后, 就可以获取该特征中的数据了.

首先使用从设备对象的readValueForCharacteristic:方法指定需要读取的特征:

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

指定需要读取的特征后, 从设备对象就会调用代理对象中的peripheral:didUpdateValueForCharacteristic:error:方法获取数据, 若数据成功获取, 就可以通过特征对象的value属性来访问:

- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {

    NSData *data = characteristic.value;
    // parse the data as needed
    ...

需要注意的是: 并非所有的特征都保证其value是可读的, 可以设置特征对象的 CBCharacteristicPropertyRead属性来决定该特征的value是否可读. 若某特征的value设置为不可读, 读取该value时, peripheral:didUpdateValueForCharacteristic:error:代理方法就会返回一个相应的error.

2.6.2 订阅特征数据

上面读取数据是访问特征数据的一种方式, 但对于访问经常改变的数据, 更加高效的方式是直接订阅该特征数据.

比如某时段的心跳次数, 这个数据是一直在改变的, 则应通过订阅进行访问.

当订阅某特征数据后, 当该数据改变时, 就会从从设备对象收到一个通知(Notification).

使用从设备对象方法setNotifyValue:forCharacteristic:来订阅指定特征, 第一个参数设YES表示数据改变时发送通知:

[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];

当订阅或解除订阅某特征时, 从设备对象都会调用代理对象的peripheral:didUpdateNotificationStateForCharacteristic:error:方法, 并且当订阅由于某种原因失败时, 可以通过这个方法查看失败原因:

- (void)peripheral:(CBPeripheral *)peripheral
didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error {

    if (error) {
        NSLog(@"Error changing notification state: %@",
           [error localizedDescription]);
    }
    ...

并非所有特征都被设置为可订阅, 可以进行相应设置.

当订阅成功后, 若该特征数据改变, 从设备就会通知你的APP. 并且数据改变时, 从设备对象会调用peripheral:didUpdateValueForCharacteristic:error:代理方法, 就可以在这个方法中像上面一样获取特征数据了.

2.7 写特征数据

暂略.

3 iOS蓝牙后台处理!!!

iOS程序的前台和后台状态差别很大, 需要理解….详见官文…

对于CB框架而言, 当不具有后台运行模式的APP转入后台后, 就不会接收到蓝牙相关通知.

总的来说, 蓝牙相关APP分两种: 一种是只能前台运行的, 一种是具有后台运行模式的.

只能前台运行的程序, 当转入后台后短时间内就会被置为suspended挂起. 并且所有蓝牙相关事件在APP挂起时都会被放入到一个队列中, 当程序恢复到前台运行时再全部交由APP处理.虽然应用相关的事件只能当应用恢复前台运行时才进行处理, 但CB提供了一些手段来通知用户这个程序相关的事件发生. 如应用处于后台或挂起时建立连接成功的通知. 详见文档…

若想要后台处理蓝牙相关事件, 需要在应用的info.plist中修改设置, 设置应用的后台运行模式.

有两个模式, 一个是作为主设备的后台运行, 一个是作为从设备的后台运行.

但后台运行会导致耗电多, 所以应在需要时才使用, 使用完尽快转入挂起.

Because performing many Bluetooth-related tasks require the active use of an iOS device’s onboard radio—and, in turn, radio usage has an adverse effect on an iOS device’s battery life—try to minimize the amount of work you do in the background. Apps woken up for any Bluetooth-related events should process them and return as quickly as possible so that the app can be suspended again.

Any app that declares support for either of the Core Bluetooth background executions modes must follow a few basic guidelines:

Apps should be session based and provide an interface that allows the user to decide when to start and stop the delivery of Bluetooth-related events. Upon being woken up, an app has around 10 seconds to complete a task. Ideally, it should complete the task as fast as possible and allow itself to be suspended again. Apps that spend too much time executing in the background can be throttled back by the system or killed. Apps should not use being woken up as an opportunity to perform extraneous tasks that are unrelated to why the app was woken up by the system.

一种实际方案是使用CB提供的程序状态保存和恢复机制.

3.1 添加程序状态保存和恢复

需要按下列步骤进行操作, 以添加这个可选功能:

在建立主设备对象时设置合适的选项. 在代码中实现合适的恢复运行代码 实现合适的恢复APP运行的代理方法 更新主或从对象(可选).

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网