美文网首页iOS学习笔记iOS程序猿程序员
利用CoreBluetooth原生框架做蓝牙4.0开发

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

作者: 真的真心瓜子 | 来源:发表于2018-05-10 18:36 被阅读197次

    简介

    最近做了一个智能门锁利用蓝牙与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结合自己的项目穿插各种逻辑的时候,就需要谨慎思考了。

    谢谢~

    相关文章

      网友评论

      • 54ade8165821:🌻🌻🌻写的真好呢,语言又风趣幽默

      本文标题:利用CoreBluetooth原生框架做蓝牙4.0开发

      本文链接:https://www.haomeiwen.com/subject/afzarftx.html