利用CoreBluetooth原生框架做蓝牙4.0开发

简介

最近做了一个智能门锁利用蓝牙与app交互的项目。整理一下相关蓝牙知识。下面要讲的是利用原生<CoreBluetooth.framework>框架封装demo,且支持蓝牙4.0。蓝牙官方文档

背景

蓝牙分为两种形式: 1)中心者模式 2)管理者模式,一般绝大部分我们都是使用第一种模式,中心者模式是我们手机作为主机,连接蓝牙外设,而管理者模式是我们手机自己作为外设,自己创建服务和特征,然后有其他的设备连接我们的手机。

接下来我们就是围绕第一种模式:中心者模式来讲解。

步骤

蓝牙连接可以大致分为以下几个步骤:
1.建立一个Central Manager实例进行蓝牙管理
2.搜索外围设备
3.连接外围设备
4.获得外围设备的服务
5.获得服务的特征
6.从外围设备读数据、给外围设备发送数据

简言之:就是我们的app创建一个蓝牙中心管理者对象,调用SDK的方法去搜索周围可发现的设备,搜索成功并发现有可用的设备后,进行连接,连接成功后再获取设备的服务与特征,最后进行数据的交互。
疑问:什么是服务?什么是特征?
下面用一张图进行讲解~

各层级关系

简言之:就是一个外设有几个服务,每一个服务又有几个特征,我们可以通过服务判断这个外设是不是我们要连接的外设,通过特征获取想要的数据。在特征有一个叫做UUID的属性,这个属性可以作为判断该特征是否是我们想要的特征的依据,这个跟硬件工程师要对接好UUID的值。

代码

在.h文件中定义一些全局属性:

//中心管理者
@property (nonatomic, strong) CBCentralManager *centralManager;
//外围设备
@property (nonatomic, strong) CBPeripheral *peripheral;
//读取设备信息的特征
@property (nonatomic, strong) CBCharacteristic *readCharteristic;
//收取消息的特征
@property (nonatomic, strong) CBCharacteristic *notifyCharteristic;
//发送消息的特征
@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;

首先,我们要创建一个中心管理者单例

  //queue中传入nil默认就是在主线程
  self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
  //设置代理
  self.centralManager.delegate = self;

实例化一个中心管理者之后,会自动调用下面代理方法:

-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
    
    switch (central.state) {
            
        case CBManagerStatePoweredOff:{
   
            NSLog(@"蓝牙关闭");
            break;
            
        case CBManagerStatePoweredOn:{
            NSLog(@"蓝牙已打开");
            [central scanForPeripheralsWithServices:nil options:nil];
        }
            break;
            
        case CBManagerStateResetting:

            break;

        case CBManagerStateUnauthorized:
            NSLog(@"系统蓝未被授权");
            break;

        case CBManagerStateUnknown:
            NSLog(@"系统蓝牙当前状态不明确");
            break;

        case CBManagerStateUnsupported:
             NSLog(@"系统蓝牙设备不支持");
            break;
            
        default:
            break;     
    }  
}

这个代理方法是判断系统的蓝牙状态,如果蓝牙已经打开,则调用下面的代码:

 //在给services和options都传入nil则是代表扫描所有条件的外围设备
 [central scanForPeripheralsWithServices:nil options:nil];

当扫描到外围设备后,会调用下面这个代理方法:(扫描到多少个外设就执行多少次)

- (void)centralManager:(CBCentralManager *)central // 中心管理
didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral  // 外设
advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData // 外设携带的数据 
RSSI:(nonnull NSNumber *)RSSI// 外设发出的蓝牙信号强度
{
         //注意:这里可以获取到扫描到外设的Mac地址,
         NSString *mac = advertisementData[@"kCBAdvDataManufacturerData"]
        //这里也可以过滤掉不需要的服务
        //接下来可以把我们想要连接的Mac地址与扫描到的外设的Mac地址进行匹配,如果一致就获取
            if ([self.peripheralMac isEqualToString:mac]) {
                   //这里就是我们要连接的外设
                   //获取外部设备
                   self.peripheral = peripheral;
                   //用中心管理者去调用连接获取到的外设的方法
                   [self.centralManager connectPeripheral:peripheral options:nil];    
                   //停止搜索
                   [self.centralManager stopScan];
            }
}

注意:代理方法是不会直接给我们返回外设的Mac地址的,我们可以通过advertisementData[@"kCBAdvDataManufacturerData"]去得到Mac地址,这个要跟硬件工程师沟通好怎么返回,以什么形式去返回。

连接成功后会自动执行下面这个代理方法:

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

    //获取到已经连接上的外设之后,设置代理
    self.peripheral.delegate = self;
    //用外设去获取服务
    [peripheral discoverServices:nil];
}

如果连接失败,会调用下面这个方法:

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

如果两个已经上的设置突然断开了连接,会自动调用下面的方法:

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
        //这种情况一般是,你蓝牙不稳定或者蓝牙断开等一些外部环境问题
        NSLog(@"已经断开蓝牙连接");
{

连接外设成功并调用获取外设服务的方法后,获取外设服务成功后会调用下面的方法:

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

   for (CBService *service in peripheral.services){
        NSLog(@"外设服务号:%@",service.UUID.UUIDString);
        if ([service.UUID.UUIDString isEqualToString:kServiceUUID]) {
            //外围设备查找指定服务中的特征,characteristics为nil,表示寻找所有特征
            [peripheral discoverCharacteristics:nil forService:service];
            break;
        }
        
    }
}

当获取外设服务的特征成功后,自动执行下面的代理方法:在这个方法中,就根据

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

//kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是与硬件对接好的特征的UUID,
 for (CBCharacteristic *characteristic in service.characteristics)
        
    {
        //写入特征
        if ([characteristic.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID]  ) {
            
            self.writeCharacteristic = characteristic;
        }
        //通知特征
        if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]) {
            
            self.notifyCharteristic = characteristic;
            /* 设置是否向特征订阅数据实时通知,YES表示会实时多次会调用代理方法读取数据 */
             [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }
        //读取特征
        if ([characteristic.UUID.UUIDString isEqualToString:kReadCharacteristicUUID]) {
            
            self.readCharteristic = characteristic;
            /* 读取特征的数据,调用此方法后会调用一次代理方法读取数据 */
            [peripheral readValueForCharacteristic:characteristic];
        }
        
    }
}

注意:kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是与硬件对接好的特征的UUID,用来判断是不是我们想要的特征,这里分为写入特征、读取特征、通知特征,分别是代表app想设备发送信息、app从设备获取信息、app获取设备的信息。

来到这里就已经获取到我们想要的特征了,接下来我们开始最后一步,写入信息和读取信息了。
下面的这个代理方法是,设备所有的数据都是从中这个代理方法返回的:

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
   
      //从我们想要的特征中获取返回的数据
      if ([self.notifyCharteristic isEqual:characteristic]) {
           NSLog(@"设备爱返回的数据:%@",characteristic.value);
       }

}

当我们想向设备写入数据的时候,调用下面这个方法:

 if(self.characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse)
    {
        //手机向外设发送数据,写数据
        [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
  

注意:这里写入的data是一个二进制形式

写到这里就结束了,上面就是讲解了开头的6个步骤的实现方法,利用原生的api去封装一个蓝牙通讯的SDK不难,关键是这个蓝牙通讯的SDK结合自己的项目穿插各种逻辑的时候,就需要谨慎思考了。

谢谢~

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