iOS蓝牙中心端开发

2017-09-11  本文已影响200人  oldSix_Zhu

iOS蓝牙开发分为两部分,一部分是手机作为中心端;一部分是手机作为外设端。
我的项目是医疗类的,手机作为中心端,收到病人穿戴的外设发过来的脑电、心电等数据传给服务器,一般的APP都是开发中心端。
原理性的东西我就不写了,网上有很多,主要记录我在实际开发项目时用到的方法与一些注意点。

第一步 配置文件
第二步 选择控制器
第三步 实现协议

形形色色的蓝牙硬件
第一步 配置文件

设置下工程plist文件,让用户允许APP使用蓝牙:

如果你想让手机在后台时也可以与外设进行交互的话,还要添加一个key:

或者这样配置:

BackgroundMode

具体解释看这里: iOS后台模式BackgroundModeiOS后台模式教程 (一)


第二步 选择控制器

首先要有一个前提,就是你的设备要在后台接收数据的话(也就是程序在后台,及手机黑屏),必须要有一个控制器引用着蓝牙中心类,最基础的是放在根控制器中。这一步很关键。
当然,我们一般根控制器是一个UITabBarController,但是上面的几个RootViewController都是可以的。
尤其需要注意的是,当你想要切换控制器还想操纵蓝牙外设的时候,也要把蓝牙中心和外设当做属性传给下一个控制器,然后在下一个控制器中再重新连接,设置代理,实现协议等等...

我们的控制器选好了后(我的是HomeVC),就在该控制器中引入框架,遵循协议,设置属性:

#import "JDHomeVC.h"
#import <CoreBluetooth/CoreBluetooth.h>

@interface JDHomeVC()<CBCentralManagerDelegate, CBPeripheralDelegate>

//中心
@property (nonatomic,strong) CBCentralManager *centralManager;
//外设
@property (nonatomic,strong) CBPeripheral *peripheral;
//特征
@property (nonatomic, strong) CBCharacteristic *characteristic;

@end

然后在你想要启动蓝牙的方法里加入以下代码(比如“连接外设”按钮),启动蓝牙相关的一系列代码

//初始化中心端,开始蓝牙模块
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
self.centralManager.delegate = self;

第三步 实现协议:
1、<CBCentralManagerDelegate>协议:

当centralManager创建之后,会立刻监测手机蓝牙状态,触发第一个代理方法,如果没有打开蓝牙,会有系统的弹窗“打开蓝牙来允许“XXXX”连接到配件”,并可以点击“设置”跳转到蓝牙。

第一个代理方法:

// 状态更新后触发
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    switch (central.state) {
        case CBCentralManagerStatePoweredOff://蓝牙关闭
            break;
        case CBCentralManagerStatePoweredOn:
            break;
        case CBCentralManagerStateResetting:
            break;
        case CBCentralManagerStateUnauthorized:
            break;
        case CBCentralManagerStateUnknown:
            break;
        case CBCentralManagerStateUnsupported://当前设备不支持蓝牙
            break;
        default:
            break;
    }
    // services参数为nil时, 表示扫描所有的蓝牙外设. 指定时只扫描匹配UUID的外设
    [central scanForPeripheralsWithServices:nil options:nil];
}

第二个代理方法:

// 扫描到外部设备后触发//多次调用的
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData RSSI:(nonnull NSNumber *)RSSI
{
//    扫描到的外部设备
//    NSString *msg = [NSString stringWithFormat:@"信号强度: %@, 外设: %@", RSSI, peripheral];
//    NSLog(@"%@",msg);
    if ([peripheral.name isEqualToString:@"BLE"])
    {
        //连接外部设备
        self.peripheral = peripheral;
        [central connectPeripheral:peripheral options:nil];
        //停止搜索
        [central stopScan];
    }
}

这时候可以打开你的外设的开关,就可以通过程序搜索出来外设的名字和信号值了;
在这里可以做处理是否直接连接外设,比如ofo是以“ofo”为开头的蓝牙名称;
连接外设时要注意强引用外设,保存一下;
我们在连接上外设后,必须要停止搜索,以免影响蓝牙连接的稳定性。

第三个代理方法:

//连接失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@",error.localizedDescription);
    [self.centralManager connectPeripheral:self.peripheral options:nil];
}

当连接失败的时候,我们要用中心端再次尝试连接我们保存的外设。

第四个代理方法:

// 当中心端连接上外设时触发
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"连接上外设");
    self.peripheral.delegate = self;
    [peripheral discoverServices:nil];
}

当我们的中心类已经连上了外设,就要开始实现外设协议的方法了,所以要设置代理。

第五个代理方法:

//如果连接上的两个设备突然断开了,程序里面会自动回调下面的方法
-   (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"设备断开重连");
    [self.centralManager connectPeripheral:self.peripheral options:nil];
}

当我们的外设与手机断开连接的时候,肯定要做一些处理,让他们再连接上。
在这里不得不说,蓝牙硬件芯片的好坏,信号的好坏太重要了,如果是便宜的芯片,在连接上之后会隔个几秒钟就调用这个方法。。。
所以我们不仅要做断开重连,如果你上传给服务器的数据是按赫兹来传的话,还要对上传的数据做缺省处理。

当然了,中心类还有别的代理方法,不过我就用到了这五个,下面是外设的代理方法。

2、<CBPeripheralDelegate>协议:

第一个代理方法:

// 外设端发现了服务时触发
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    NSLog(@"%@",peripheral.services);
    for (CBService *service in peripheral.services)
    {
        //只找有用的服务
        if ([service.UUID.description isEqualToString:@“外设服务的UUID名称”])
        {
            [peripheral discoverCharacteristics:nil forService:service];
        }
    }
}

一个外设有很多服务UUID,所以是一个数组,要从这些UUID中找出有用的服务,然后要discover一下,才能发现服中的特征。

第二个代理方法:

//从服务获取特征
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    NSLog(@"%@",service.characteristics);
    for (CBCharacteristic *characteristic in service.characteristics)
    {
       NSLog(@"%@",service.characteristics);
    for (CBCharacteristic *characteristic in service.characteristics)
    {
        // -------- 读特征的处理 --------
        if ([characteristic.UUID.description isEqualToString: @"读特征的UUID名称"])
        {
            NSLog(@"处理读特征");
            [self.peripheral readValueForCharacteristic:characteristic];
        }
        
        // -------- 写特征的处理 --------
        if ([characteristic.UUID.description isEqualToString: @"写特征的UUID名称"])
        {
            NSLog(@"处理写特征");
            //向外设发送0001命令
            NSData *data = [@"0001" dataUsingEncoding:NSUTF8StringEncoding];
            [self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
            self.characteristic = characteristic;
        }

        // -------- 订阅特征的处理 --------
        if ([characteristic.UUID isEqual:[CBUUID @"订阅特征的UUID名称"]])
        {
            NSLog(@"处理了订阅特征");
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
    }
}

这里就是真正与外设交互的地方了,一个UUID有三种属性:可读,可写,可监听。

可读属性,一般用来读取一次时使用。比如外设的名称,电量之类的,不常用。

可写属性,就是向外设发指令。这个很重要,不过当你写入失败的时候,记得更换你writeValue方法后面的type试试,有的是有回调的,用CBCharacteristicWriteWithResponse,有的是没有回调的,用type:CBCharacteristicWriteWithoutResponse
一般都保存下可写特征,在外面写入发指令。

可监听属性,就是接收外设发送的数据了。一般实时变化的数据都要监听外设的,很重要。值得注意的是,当监听成功后,特征的Notify属性的值会“=Yes”,以此判断你是否监听成功了。

第三个代理方法:

// 写特征CBCharacteristicWriteWithResponse的数据写入的结果回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error) {
        NSLog(@"数据写入失败: %@", error);
    } else {
        NSLog(@"数据写入成功");
        [peripheral readValueForCharacteristic:characteristic];
    }
}

这个就是当type为CBCharacteristicWriteWithResponse时,向外设写入命令后的回调

第四个代理方法:

//获取外设发来的数据,不论是read和notify,获取数据都是从这个方法中读取
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"%@",characteristic.value);
}

这里就是从外设获取数据的方法。
向外设读特征读后,该方法只调用一次;
监听外设的监听特征后,这个方法会在外设发送一个数据时调用一次,也就是说会多次调用。
可以在这个方法里做一些数据处理,然后将数据发送给服务器。

比如说服务器要接收的数据是按赫兹来接收的(就是一秒钟几个数据,还要对应时间),那就要自定义个网络工具类,不能直接用AFN了事,然后要把从外设接收的数据转成服务器要求的格式(比如多长时间一个包,定义字段等)。这时就要使用NSMutableData在这个方法里拼接数据,然后设置请求体。
如果遇到上述情况,想知道我是如何处理的,可以看看我的另一篇文章。

第五个代理方法:

// 订阅特征的值改变时触发的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"订阅特征的值改变了 : %@", characteristic);
    NSLog(@"%@",characteristic.value);
}

当订阅特征的值改变时会触发该方法,用处不大。

蓝牙的基本功能实现就是这些,高级一点会把蓝牙的相关代理方法封装成一个中心管理类,可以看下我的下一篇文章:iOS封装蓝牙中心管理者类
还有我在开发过程中遇到了什么问题,是如何处理的,会在另一篇文章记下来。
如果有帮助到你,给个喜欢吧:-D

上一篇下一篇

猜你喜欢

热点阅读