iOS蓝牙中心端开发

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

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

推荐阅读更多精彩内容