iOS之蓝牙4.0 BLE相关

由于最近工作的东家是一家物联网公司,网上BLE相关的资料确实比较少,尤其我还做了一些调试和加密相关的工作.
由于调试和获取数据的一些问题,自己一开始做开发的时候也不是那么从容.所以决定总结一下我在工作中遇到的问题和情况,希望这些东西能对其他做BLE相关的亲们有点帮助,其实也是对自己的情况的不断总结

一: 文件的初始化和配置

  1. 创建一个控制器,我这里就取名称为CJCenterManagerVC,并包含头文件

    #import <CoreBluetooth/CoreBluetooth.h>
    
  2. 遵守协议:

    @interface CJCenterManagerVC() <CBCentralManagerDelegate, CBPeripheralDelegate>
    

3.如果在开发中用到数据库的话还要配置数据库,libsqlite3.tdb框架,具体位置如下:


二: 蓝牙BLE4.0具体思路及其讲解

  • 1、BLE4.0 分为外设和中心管理者两个部分,通常说外设就是外部的连接设备,而中心管理者通常就是指手机,所以要声明这个两个属性,并且在合适的地方初始化中心管理者,我这里就写在了ViewDidLoad方法中,实际开发中可根据实际情况在合适的方法中初始化

声明属性:

   @interface CJCenterManagerVC () <CBCentralManagerDelegate, CBPeripheralDelegate>

   /** 中心管理者 */
   @property (nonatomic, strong) CBCentralManager *cMgr;
   /** 连接到的外设 */
   @property (nonatomic, strong) CBPeripheral *peripheral;
   @property (nonatomic, assign) sqlite3 *db;

   @end

初始化:

- (void)viewDidLoad 
{
  [self cMgr];
}

  • 2、当中心管理者初始化就会调用的方法:
// 只要中心管理者初始化,就会触发此代理方法, 在这里判断中心管理者的状态是否开启,如果开启,就搜索外设
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
    case CBCentralManagerStateUnknown:
        NSLog(@"CBCentralManagerStateUnknown");
        break;
    case CBCentralManagerStateResetting:
        NSLog(@"CBCentralManagerStateResetting");
        break;
    case CBCentralManagerStateUnsupported:
        NSLog(@"CBCentralManagerStateUnsupported");
        break;
    case CBCentralManagerStateUnauthorized:
        NSLog(@"CBCentralManagerStateUnauthorized");
        break;
    case CBCentralManagerStatePoweredOff:
        NSLog(@"CBCentralManagerStatePoweredOff");
        break;
    case CBCentralManagerStatePoweredOn:
    {
        NSLog(@"CBCentralManagerStatePoweredOn");
        // 在中心管理者成功开启后再进行一些操作
        // 搜索外设
        [self.cMgr scanForPeripheralsWithServices:nil // 通过某些服务筛选外设
                                          options:nil]; // dict,条
        
    }
        break;
        
    default:
        break;
  }
}
  • 3、当发现外设后代理会自动调用下面的方法,我这里通过判断外设的名称来连接外设,具体属性含义,看代码的注释

    // 发现外设后调用的方法
    - (void)centralManager:(CBCentralManager *)central // 中心管理者
    didDiscoverPeripheral:(CBPeripheral *)peripheral // 外设
        advertisementData:(NSDictionary *)advertisementData // 外设携带的数据
                RSSI:(NSNumber *)RSSI // 外设发出的蓝牙信号强度
    {
       // 在此处对我们的 advertisementData(外设携带的广播数据) 进行一些处理
       if ([peripheral.name isEqualToString:@"iTAG"] || [peripheral.name isEqualToString:@"ITAG"])
          {
              // 标记我们的外设,让他的生命周期 = vc
              self.peripheral = peripheral;
              // 发现完之后就是进行连接
              [self.cMgr connectPeripheral:self.peripheral options:nil];
          }
    }
    
  • 4、此时就会进行外设和手机之间的连接,这期间的连接由硬件完成,我们只需在连接完成后的回调里做事情

    // 中心管理者连接外设成功
    - (void)centralManager:(CBCentralManager *)central // 中心管理者
      didConnectPeripheral:(CBPeripheral *)peripheral // 外设
    {
        // 断开连接,让管理者不再继续搜索设备
        [self ccj_dismissConentedWithPeripheral:peripheral IsCancle:false];
        //  设置外设的代理
         self.peripheral.delegate = self;
    
        //  外设发现服务,传nil代表不过滤
        [self.peripheral discoverServices:nil];
        //  读取RSSI的值
        [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(readRSSIInfo) userInfo:nil repeats:YES];
    }
    
  • 4.1 断开连接的代码和读RSSI的代码
    // 断开连接


 - (void)ccj_dismissConentedWithPeripheral:(CBPeripheral 
 *)peripheral IsCancle:(BOOL)cancle
      {
          // 停止扫描
          [self.cMgr stopScan];
          if (cancle) {
        // 断开连接
        [self.cMgr cancelPeripheralConnection:peripheral];
          }
    
      }


      // 读RSSI值
      - (void)readRSSIInfo
      {
         [self.peripheral readRSSI];
      }
  • 4.2 注意点: readRSSIInfo的代码的含义是为了让设备发送读取指令,这样设备才能继续调用获取到RSSI值的回调方法

  • 5、如果外设没有连接上会调用如下方法

    // 外设连接失败
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    {
        NSLog(@"%s, line = %d, %@=连接失败", __FUNCTION__, __LINE__, peripheral.name);
    }
    
    
    // 丢失连接
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    {
        NSLog(@"%s, line = %d, %@=断开连接", __FUNCTION__, __LINE__, peripheral.name);
    }
    
  • 6、当发现外设的服务后会调用的方法

    // 发现外设的服务后调用的方法
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    {
        // 判断没有失败
        if (error)
           {
              NSLog(@"%s, line = %d, error = %@", __FUNCTION__, __LINE__, error.localizedDescription);
              return;
    #warning 下面的方法中凡是有error的在实际开发中,都要进行判断
           }
        for (CBService *service in peripheral.services)
           {
                // 发现服务后,让设备再发现服务内部的特征们
                [peripheral discoverCharacteristics:nil forService:service];
           }
    }
    
  • 7、发现外设的服务特征的时候调用的方法, 一般在这里我们会获取某些比如电量的值或者进行加密通道的写入数据

    // 发现外设服务里的特征的时候调用的代理方法
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
    {
        if (error) 
        {
            NSLog(@"%s, line = %d, %@", __FUNCTION__, __LINE__, [error description]);
        return;
        }
    
        for (CBCharacteristic *chara in service.characteristics)
        {
          // 外设读取特征的值
          
          // 电量特征和服务 (BatService和BatChara为自定义define的电量服务)
            if([chara.UUID isEqual:[CBUUID UUIDWithString:BatChara]] && [service.UUID isEqual:[CBUUID UUIDWithString:BatService]])
            {
               // 此时的chara 就是电量的特征,在这里通过chara.value获取值进而转化为电量值
            }
        }
    }
    
  • 7.1 其他获取值的方法同上类似,获取电量值的具体方法请自己谷歌,如果找不到的话也可以给我留言,我看到会回复
    注: 更新于: 2016年11月29日, 蓝牙开发者门户网站已改版,共用的协议接口汉语版本已找不到了, 下面更新的协议接口地址为英文版, 如果哪位小伙伴有汉语版也欢迎留言
    已采纳的公用的协议接口请参考: Bluetooth开发者门户

  • 8、只要特征和描述的value一更新就会调用的方法

    // 更新特征的value的时候会调用
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    {
        for (CBDescriptor *descriptor in characteristic.descriptors) 
            {
            // 它会触发
            [peripheral readValueForDescriptor:descriptor];
            }
    
        if (error)
           {
               return;
           }
    }
    
    
    // 更新特征的描述的值的时候会调用
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error
    {
    
        // 这里当描述的值更新的时候,直接调用此方法即可
        [peripheral readValueForDescriptor:descriptor];
    }
    

发现外设的特征的描述数组:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
    {
    // 在此处读取描述即可
    for (CBDescriptor *descriptor in characteristic.descriptors) {
        // 它会触发
        [peripheral readValueForDescriptor:descriptor];
    }
  • 9、接收到通知调用的方法

    //接收到通知
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    {
      if (error) {
      NSLog(@"%@", [error localizedDescription]);
      return;
     }
      if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:NotCharaOrDes]])
     {
      return;
     }
      [peripheral readValueForCharacteristic:characteristic];//接受通知后读取
      [peripheral discoverDescriptorsForCharacteristic:characteristic];
    }
    
  • 10、读取到信号回调,在这里我们可以拿到信号RSSI的值,这里也就是readRSSIInfo方法回调的地方.

备注: 在安卓端,只要控制了readRSSIInfo的方法调用频率, 下面的回调方法可以按照频率来调用,比如说0.1秒,但是我亲自试过, 在我们iOS端,如果你控制了readRSSIInfo的调用频率,这个频率大于1秒钟,那么就会按照你所控制的频率进行下面的方法回调,但是如果小于1秒钟,都是默认为1秒钟调用下面的回调方法一次! 所以也就是说RSSI的值只能最快1秒钟获取一次

  //读取到信号回调
  -(void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error
  {
      
  }
  • 11、收到反馈时回调

    // 收到反馈时调用
    - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
    {
      [peripheral readValueForCharacteristic:characteristic];
    
    }
    
  • 12、自定义方法

    // 外设写数据到特征中
    
    // 需要注意的是特征的属性是否支持写数据
    - (void)ccj_peripheral:(CBPeripheral *)peripheral didWriteData:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)characteristic
    {
        if (characteristic.properties & CBCharacteristicPropertyWrite)
           {
            // 核心代码在这里
            [peripheral writeValue:data // 写入的数据
           forCharacteristic:characteristic // 写给哪个特征
                        type:CBCharacteristicWriteWithResponse];// 通过此响应记录是否成功写入
            }
    }
    
    
    // 通知的订阅和取消订阅
    // 实际核心代码是一个方法
    // 一般这两个方法要根据产品需求来确定写在何处
    - (void)ccj_peripheral:(CBPeripheral *)peripheral regNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic
    {
         // 外设为特征订阅通知 数据会进入peripheral:didUpdateValueForCharacteristic:error:方法
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    }
    - (void)ccj_peripheral:(CBPeripheral *)peripheral CancleRegNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic
    {
        // 外设取消订阅通知 数据会进入 peripheral:didUpdateValueForCharacteristic:error:方法
        [peripheral setNotifyValue:NO forCharacteristic:characteristic];
    }
    

    // 断开连接

 - (void)ccj_dismissConentedWithPeripheral:(CBPeripheral *)peripheral IsCancle:(BOOL)cancle
 {
          // 停止扫描
       [self.cMgr stopScan];
       if (cancle) 
       {
           // 断开连接
           [self.cMgr cancelPeripheralConnection:peripheral];
        }
    
 }

三: 其他

关于蓝牙的加密通道的具体实现 和测试的具体实现会写在其他的篇幅中,如果发现本博客哪处有问题或者您有任何问题,欢迎留言指正和探讨,让我们共同进步!

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

推荐阅读更多精彩内容

  • 原文:http://www.myexception.cn/operating-system/2052286.htm...
    KYM1988阅读 1,916评论 2 2
  • 本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备 用的库就是core bluetoot...
    暮雨飞烟阅读 810评论 0 2
  • 这里我们具体说明一下中心模式的应用场景。主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般...
    丶逝水流年阅读 2,198评论 3 4
  • 首先进一则广告: 蓝牙技术联盟(Bluetooth SIG)2010年7月7日宣布,正式采纳蓝牙4.0核心规范(B...
    L泽阅读 1,412评论 3 4
  • 坠地呱呱,尔心犹念 哺育之艰,乐亦随形 尝有以忧,忧思故至 寄以大志,心念宏图 及至总角,晦暗若明 促吾学文,晓以...
    Vincent空阅读 488评论 1 3