iOS使用CoreBluetooth框架实现BLE中央设备

iOS使用BLE实现简单的周边设备广告实现了最基本的周边设备。BLE中有两种角色:

  1. 周边设备:Peripheral
  2. 中央设备:Central

二者的区别是,周边设备发射广告(Adertising),中央设备接收广告。

iOS使用BLE实现简单的周边设备广告中使用第三方App(LightBlue)实现中央设备,下面要通过iOS CoreBlue编写接收周边设备广告的示例。

编写最简单中央设备的步骤

源代码见:https://github.com/MarshalW/BleCentralDemo/tree/m1

基本步骤为:

  1. 创建iOS Single View Application
  2. 项目引入CoreBlue.framework,并在需要的代码前import
  3. ViewController实现CBCentralManagerDelegate代理
  4. 编写CBCentralManagerDelegate需要实现的方法:判断蓝牙状态是否正常,以及收到广告的回调
  5. 在ViewController的viewDidLoad方法中,创建CBCentralManager对象

前两步不细说了,从第三步说一下。

ViewController实现CBCentralManagerDelegate代理:

1
@interface ViewController () <CBCentralManagerDelegate>

编写CBCentralManagerDelegate需要实现的方法。

判断蓝牙状态是否正常:

1
2
3
4
5
6
7
8
9
10
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state == CBCentralManagerStatePoweredOn) {
[self.centralManger scanForPeripheralsWithServices:nil
options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@(YES)}];
NSLog(@">>>BLE状态正常");
}else{
NSLog(@">>>设备不支持BLE或者未打开");
}
}

收到广告的回调:

1
2
3
4
5
6
7
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
NSLog(@">>>>扫描周边设备 .. 设备id:%@, rssi: %@",[peripheral.identifier UUIDString],RSSI);
}

最后,CBCentralManager对象:

1
@property (strong, nonatomic) CBCentralManager *centralManger;

在viewDidLoad方法中实例化:

1
2
3
4
5
6
7
- (void)viewDidLoad
{
[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.
self.centralManger = [[CBCentralManager alloc] initWithDelegate:self
queue:nil];

运行后的日志:

1
2
3
4
5
6
7
8
9
10
2013-12-25 11:34:17.404 BleCentralDemo[3095:60b] CoreBluetooth[WARNING] <CBCentralManager: 0x15661240> is disabling duplicate filtering, but is using the default queue (main thread) for delegate events
2013-12-25 11:34:17.410 BleCentralDemo[3095:60b] >>>BLE状态正常
2013-12-25 11:34:18.918 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -44
2013-12-25 11:34:18.919 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -45
2013-12-25 11:34:19.810 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -45
2013-12-25 11:34:19.813 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -45
2013-12-25 11:34:21.626 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -45
2013-12-25 11:34:21.629 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -45
2013-12-25 11:34:22.538 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -45
2013-12-25 11:34:22.542 BleCentralDemo[3095:60b] >>>>扫描周边设备 .. 设备id:DD3EF12C-ADE8-6B6D-02F2-5EFAA4096B13, rssi: -46

测试到的是我桌上放置的一个iBeacons设备。

这里的id是周边设备的UUID,RSSI是接收信号强度。

连接周边设备并浏览该设备的数据

源代码见:https://github.com/MarshalW/BleCentralDemo/tree/m2

在上面示例代码基础上,可实现连接周边设备,然后读取周边设备的服务(service)及特性(characteristic)。

基本情况是:

  • 一个周边设备(peripheral)可以有多个服务(service)
  • 一个服务又可以有多个特性(cheracteristic)
  • 特性包含了值,比如电池的电量百分数
  • 特性的值可以是只读的,也可以是通过中央设备编辑的

和上面测试方式不同(iBeacons),下面我的测试,是连接到一部iPhone手机。在手机上安装了LightBlue并且模拟一个周边设备,见下图:

这样做的目的是好控制,可以加一些自定义的服务和特性。

代码方面的增加,首先是增加了CBPeripheralDelegate:

1
@interface ViewController () <CBCentralManagerDelegate,CBPeripheralDelegate>

连接到周边设备

然后,对centralManager:didDiscoverPeripheral:advertisementData:RSSI:方法做了扩展:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
{
NSLog(@">>>>扫描周边设备,id:%@, rssi: %@",[peripheral.identifier UUIDString],RSSI);

//以下是扩展部分
peripheral.delegate=self;
self.peripheral=peripheral;
[self.centralManger connectPeripheral:self.peripheral options:nil];
}

需要保持peripheral对象,所以增加了一个成员变量:

1
@property (strong,nonatomic) CBPeripheral *peripheral;

然后,要设置代理,否则后面无法浏览服务和特性。

连接时可能需要输入蓝牙配对密码。

一旦连接操作(connectPeripheral方法)被执行,系统将会调用:

1
2
3
4
5
6
7
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"和周边设备连接成功。");

[peripheral discoverServices:nil];//发现服务
NSLog(@"扫描周边设备上的服务..");
}

这个方法将调用发现当前外围设备上的服务。

浏览服务和特性

发现服务的方法被调用,一旦找到一个服务,系统就会调用这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error
{
if (error) {
NSLog(@"发现服务时发生错误: %@",error);
return;
}

NSLog(@"发现服务 ..");

for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:service];
}
}

在这个方法里,遍历服务,并调用发现特性的方法。

那么系统当发现一个特性,就会调用下面的方法:

1
2
3
4
5
6
7
8
9
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSLog(@"发现服务 %@, 特性数: %d", service.UUID, [service.characteristics count]);

for (CBCharacteristic *c in service.characteristics) {
[peripheral readValueForCharacteristic:c];
NSLog(@"特性值: %@",c.value);
}
}

这个方法,遍历当前服务的特性,并打印特性的值。

运行结果类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
2013-12-25 21:41:14.234 BleCentralDemo[660:60b] >>>BLE状态正常
2013-12-25 21:41:14.268 BleCentralDemo[660:60b] >>>>扫描周边设备,id:DBA27D08-74AE-2A7F-F8A6-CAD6FFD2FF5B, rssi: -54
2013-12-25 21:41:14.621 BleCentralDemo[660:60b] 和周边设备连接成功。
2013-12-25 21:41:14.626 BleCentralDemo[660:60b] 扫描周边设备上的服务..
2013-12-25 21:41:14.913 BleCentralDemo[660:60b] 发现服务 ..
2013-12-25 21:41:14.917 BleCentralDemo[660:60b] 发现服务 Battery, 特性数: 1
2013-12-25 21:41:14.919 BleCentralDemo[660:60b] 特性值: <51>
2013-12-25 21:41:14.920 BleCentralDemo[660:60b] 发现服务 Current Time, 特性数: 2
2013-12-25 21:41:14.921 BleCentralDemo[660:60b] 特性值: <dd070c19 15252d03 0000>
2013-12-25 21:41:14.922 BleCentralDemo[660:60b] 特性值: <2000>
2013-12-25 21:41:14.925 BleCentralDemo[660:60b] 发现服务 Unknown (<8690>), 特性数: 2
2013-12-25 21:41:14.926 BleCentralDemo[660:60b] 特性值: <68686868>
2013-12-25 21:41:14.927 BleCentralDemo[660:60b] 特性值: <>

这里得到的电池值是16进制的51,也就是10进制的81,ok,是正确的,和我手机上显示一致。