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];
        }
    
 }

三: 其他

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

推荐阅读更多精彩内容

  • 原文:http://www.myexception.cn/operating-system/2052286.htm...
    KYM1988阅读 1,656评论 2 2
  • 本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备 用的库就是core bluetoot...
    暮雨飞烟阅读 634评论 0 2
  • 这里我们具体说明一下中心模式的应用场景。主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般...
    丶逝水流年阅读 1,793评论 3 4
  • 首先进一则广告: 蓝牙技术联盟(Bluetooth SIG)2010年7月7日宣布,正式采纳蓝牙4.0核心规范(B...
    L泽阅读 1,221评论 3 4
  • 坠地呱呱,尔心犹念 哺育之艰,乐亦随形 尝有以忧,忧思故至 寄以大志,心念宏图 及至总角,晦暗若明 促吾学文,晓以...
    Vincent空阅读 293评论 1 3
  • 跟我走吧,我们一起回家,手给你,怀抱给你,车票给你,长途跋涉给你,西瓜的最甜的那一口给你,等待给你,钥匙给你,家给...
    Dora_妮阅读 129评论 0 0
  • 探出头, 爬到阳台上, 我, 盯着“铲屎官”, 可怜巴巴。 可怕的怪爪, 撑开我的嘴, 肉球不停地塞了进来。 “啊...
    公知小白阅读 1,070评论 0 0
  • 我不介意孤独,比爱你舒服。 在我看来,这句话不单指爱情,还包括友情,亲情…… 人与人交往真的好累,每...
    Blackll阅读 227评论 1 0