美文网首页
CoreBlutooth使用

CoreBlutooth使用

作者: DoflaKaiGo | 来源:发表于2019-05-13 16:55 被阅读0次

    CoreBluetooth是基于蓝牙ble技术实现的,现在的智能手环手表都使用了低功耗蓝牙技术
    CoreBluetooth场景中蓝牙对象分为两种:

    1.中心设备(CBCentralManager * cbcManager):中心设备负责扫描接收广播,连接外设设备,向外设设备进行读写操作
    1. 外设设备(CBPeripheral * peripheral):向外发送广播,向中心设备读写操作
    注意:一个设备既可以是中心设备,又可以是外设设备,但是不能同时是中心设备和外设设备,同一时间,只能扮演一种角色


    基础知识介绍:

    CBPeripheral

    //CBPeripheral 属性
    //外设状态
    typedef NS_ENUM(NSInteger, CBPeripheralState) {
        CBPeripheralStateDisconnected = 0, // 没有连接
        CBPeripheralStateConnecting,//正在连接
        CBPeripheralStateConnected,//已经连接
        CBPeripheralStateDisconnecting NS_AVAILABLE(10_13, 9_0),
    } NS_AVAILABLE(10_9, 7_0);
    @interface CBPeripheral : CBPeer
    @property(weak, nonatomic, nullable) id<CBPeripheralDelegate> delegate;
    @property(retain, readonly, nullable) NSString *name;//外设名字
    @property(retain, readonly, nullable) NSNumber *RSSI;//信号强度
    @property(readonly) CBPeripheralState state;//外设状态
    @property(retain, readonly, nullable) NSArray<CBService *> *services;//外设包含的服务(每个服务里面可能包含多个特征)
    @property(readonly) BOOL canSendWriteWithoutResponse;//判断读写性
    
    // CBPeripheral 常用方法
    //查找外设包含的服务 serviceUUIDs 为nil 则查找该外设包含的所有服务 执行该方法会出发代理方法
    - (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
    // 扫描 特征的描述,执行该方法会出发代理方法
    - (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic;
    //扫描服务里包含的服务,执行该方法会出发代理方法
    - (void)discoverIncludedServices:(nullable NSArray<CBUUID *> *)includedServiceUUIDs forService:(CBService *)service;
    //扫描服务包含的特征,执行该方法会出发代理方法
    - (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;
    //读取 某特征的数据 
    - (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
    //读取描述的值
    - (void)readValueForDescriptor:(CBDescriptor *)descriptor;(CBCharacteristicWriteType)type;
    //通过特征写入数据
    - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:
    //通过描述写入数据
    - (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor;
    //设置通知
    - (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;
    
    //代理方法
    //扫描外设服务触发该代理,如果找打服务 可以在这里调用discoverCharacteristics 方法查找该服务的特征
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;
    //扫描服务包含的服务(服务里面可能会包含服务)
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error;
    //查找服务的特征 ,一个服务可能有多个特征,一个特征可能有多个属性,CBCharacteristic是一个枚举值
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error;
    //接收通知代理
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
    //读取Characteristic 中的值,调用readValueForCharacteristic 方法会触发该代理,需要在该代理方法里面获取具体的数据
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
    //向 CBCharacteristic 写入数据的回调
    -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
    

    CBCentralManager

    //中心设备的状态 创建中心设备的时候 会触发代理,在代理里面判断中心设备状态,在状态为CBManagerStatePoweredOn的时候可以开始扫描广播
    typedef NS_ENUM(NSInteger, CBCentralManagerState) {
       CBCentralManagerStateUnknown = CBManagerStateUnknown,//未知
       CBCentralManagerStateResetting = CBManagerStateResetting,//正在重置
       CBCentralManagerStateUnsupported = CBManagerStateUnsupported,//不支持
       CBCentralManagerStateUnauthorized = CBManagerStateUnauthorized,
       CBCentralManagerStatePoweredOff = CBManagerStatePoweredOff,//没有打开蓝牙
       CBCentralManagerStatePoweredOn = CBManagerStatePoweredOn,//蓝牙以打开,可以扫描广播
    } NS_DEPRECATED(10_7, 10_13, 5_0, 10_0, "Use CBManagerState instead");
    @interface CBCentralManager : CBManager
    @property(nonatomic, weak, nullable) id<CBCentralManagerDelegate> delegate;
    @property(nonatomic, assign, readonly) BOOL isScanning ;//判断蓝牙是否正在扫描广播
    //蓝牙中心设备初始化方法,需要指定 delegate 并实现代理方法
    - (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate
                               queue:(nullable dispatch_queue_t)queue;
    // 常用方法
    //通过服务 ID 来扫描外设
    - (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
    //连接外设
    - (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
    //取消连接
    - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
    //停止扫描
    - (void)stopScan;
    //通过 ID 数组查找外设
    - (NSArray<CBPeripheral *> *)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> *)identifiers NS_AVAILABLE(10_9, 7_0);
    //通过 服务的UUID 数组查找外设
    - (NSArray<CBPeripheral *>*)retrieveConnectedPeripheralsWithServices:(NSArray<CBUUID *> *)serviceUUIDs NS_AVAILABLE(10_9, 7_0);
    
    // @protocol CBCentralManagerDelegate <NSObject> 代理方法
    //中心设备开始扫描之后,每发现一个外设就会调用一次该方法,同一设备可能会被发现多次,扫描频率基本固定,外设发送的广播不一定每一个都能接收到,如果使用该方法获取广播数据的时候需要注意
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
    //中心设备连接上外设之后调用
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
    //连接失败调用
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
    //断开连接
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
    // 中心设备 状态改变的时候调用
    - (void)centralManagerDidUpdateState:(CBCentralManager *)central;
    //中心设备状态重置的时候调用
    - (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;
    

    连接过程: 外设设备向外广播,中心设备接收广播,广播包含了广播内容和外设的名字,信号强度等属性(IiOS 的CoreBluetooth框架不能从广播里面获取到 外设 MAC地址,所以不能使用MAC地址来区分外设). 扫描 -> 连接 ->扫描外设所包含的服务Services -> 扫描每一个服务所包含的特征Characteristics ->判断特征的属性CBCharacteristicProperties,根据需求保存对应属性的Characteristics,后面读/写的时候只能往可读/可写属性的特征里面写
    CBCharacteristicProperties 是一个枚举
    注意:一个特征可能同时拥有多个属性 :比如属性值为 0x02 & 0x08则该特性可读可写

    typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
        CBCharacteristicPropertyBroadcast= 0x01,//广播属性
        CBCharacteristicPropertyRead= 0x02,//可读属性
        CBCharacteristicPropertyWriteWithoutResponse= 0x04,//无响应的写
        CBCharacteristicPropertyWrite= 0x08,//写
        CBCharacteristicPropertyNotify= 0x10,//通知
        CBCharacteristicPropertyIndicate= 0x20,
        CBCharacteristicPropertyAuthenticatedSignedWrites= 0x40,
        CBCharacteristicPropertyExtendedProperties= 0x80,
        CBCharacteristicPropertyNotifyEncryptionRequired= 0x100,
        CBCharacteristicPropertyIndicateEncryptionRequired= 0x200
    };
    

    实际使用

    1. CBCentralManager 中心设备方法使用

    @interface XIXCBCenterManager : CBCentralManagerDelegate
    @property (strong, nonatomic) CBCentralManager * cbcManager;
    ...
    -(void)propertyInit{
    ...
    //创建 cbcManager 对象,创建之后会调用 代理 返回 中心设备状态
     self.cbcManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
    }
    
    #pragma mark - CoreBlueToothDelegate
    //蓝牙状态代理 蓝牙创建之后会调用一次
    -(void)centralManagerDidUpdateState:(CBCentralManager *)central{
        switch (central.state) {
            case CBManagerStatePoweredOn:  //蓝牙状态可用
                NSLog(@"蓝牙已打开,可用,开始扫描");
                //蓝牙状态可用的时候,开启扫描可用的蓝牙外设
                    [self.cbcManager scanForPeripheralsWithServices:nil options:nil];
                    [MBProgressHUD ShowMBToView:self.view withMessage:@"正在扫描设备..."];
                break;
            case CBManagerStateUnsupported:
                [MBProgressHUD showError:@"蓝牙不可用"];
                break;
            default:{
                [MBProgressHUD hideHUDForView:self.view];
                UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:nil message:@"请先开启蓝牙功能" preferredStyle:UIAlertControllerStyleAlert];
                UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
                UIAlertAction * settingAction = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action){
                [[UIApplication sharedApplication]openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
                }];
                [alertVC addAction:cancelAction];
                [alertVC addAction:settingAction];
                [self presentViewController:alertVC animated:YES completion:nil];
            }
                break;
        }
    }
    
    /**
     扫描到蓝牙外设后调用 ,每扫描到一个设备信息返回一次,判断返回的该蓝牙是否是已经被添加过的,如果被添加过,则替换数组中的该设备,如果没有添加过则添加到数组
     @param central central
     @param peripheral 扫描到的蓝牙外设
     @param advertisementData 蓝牙外设的额外数据
     @param RSSI 信号强度
     */
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData RSSI:(nonnull NSNumber *)RSSI{
        if (peripheral.name.length <= 0) return;
    //获取外设广播包含的数据
        NSData * adverData =  advertisementData[@"kCBAdvDataManufacturerData"] ;
       if(adverData != nil){
        AdvertiseMentModel * advModel = [self.connect getAdvertisementData:adverData];
    // 若果扫描到的外设已存在,则替换,如果不存在则添加,刷新列表
        if (advModel) {
            PeripheralModel * peripheralModel;
            if (![self.perArray containsObject:peripheral]) {
                [self.perArray addObject:peripheral];
                peripheralModel = [[PeripheralModel alloc]initWithPeripheral:peripheral];
                [self.deviceArray addObject:peripheralModel];
            }
            NSInteger index = [self.perArray indexOfObject:peripheral];
            peripheralModel = self.deviceArray[index];
            peripheralModel.advertisementModel = advModel;
            [self.tableView reloadData];
        }
      }
    }
    // 使用列表展示外设,点击Cell 的时候,获取外设对象并连接
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
            self.selectedPeriphalModel = self.resultList[indexPath.row];
             NSDictionary * dic = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey];
            [self.cbcManager connectPeripheral:self.selectedPeriphalModel.periphal options:dic];
            [MBProgressHUD ShowMBToView:self.view withMessage:@"正在连接设备..."];
    //设置10s 的连接超时时间,连接超时则调用 
    // [self.cbcManager cancelPeripheralConnection:self.selectedPeriphalModel.periphal];取消连接
            [self performSelector:@selector(connectTimeOut) withObject:nil afterDelay:10];
    }
    //连接成功调用代理
    /**
     连接外设成功后调用
     @param central central
     @param peripheral 连接成功的设备
     */
    -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //没有超时则需要取消延迟 调用方法 同时停止扫描
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(connectTimeOut) object:nil];
        [self.cbcManager stopScan];//停止扫描
        [MBProgressHUD hideHUDForView:self.view animated:YES];
    }
    /**
     连接失败调用
     @param central central
     @param peripheral 连接的设备
     @param error 错误原因
     */
    -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
        [MBProgressHUD hideHUDForView:self.view animated:YES];
        [MBProgressHUD showError:@"连接失败"];
    }
    //断开连接
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
        [MBProgressHUD hideHUDForView:self.view animated:NO];
    }
    

    2. CBPeripheral 外设设备方法使用

    #define CHARACTER_UUID_WRITE @"5E9BF2A8-F93F-4481-A67E-3B2F4A07891A"
    #define CHARACTER_UUID_NOTI @"8AC32D3F-5CB9-4D44-BEC2-EE689169F626"
    @property (strong, nonatomic) CBPeripheral * peripheral;
    //保存可写的特征,写数据的时候用这个特征写入数据
    @property (strong, nonatomic) CBCharacteristic * writeCharacter;
    //连接上外设之后 调用 discoverServices 方法,查找该外设所包含的服务
     [self.peripheral discoverServices:nil];
    //写入数据
      [self.peripheral writeValue:data  forCharacteristic:self.writeCharacter type:CBCharacteristicWriteWithResponse];
    
    #pragma mark - CBPeripheralDelegate
    /**
     查找蓝牙代理的服务 一个蓝牙 外设可能有多个服务
     @param peripheral peripheral
     @param error 错误信息
     */
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    //    NSString * UUID_str = [peripheral.identifier UUIDString];
    //    NSLog(@"UUID:%@",UUID_str);
        if (error) {
            NSLog(@"查找蓝牙服务出错");
            return;
        }
        //开始遍历查找服务 查找每个服务的特征
        for (CBService * service in peripheral.services) {
            //如果知道特征的 UUID 可以在第一个参数传入 UUID数组
            NSLog(@"服务CBUUID:%@",service.UUID.UUIDString);
            [self.peripheral discoverCharacteristics:nil forService:service];
        }
    }
    //扫描服务包含的服务
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error{
        if (error) {
            NSLog(@"扫描服务内部的服务失败");
            return;
        }
        NSArray * services = service.includedServices;
        for (CBService * service  in services) {
            //如果知道特征的 UUID 可以在第一个参数传入 UUID数组
            [peripheral discoverCharacteristics:nil forService:service];
        }
    }
    //查找服务的特征 ,一个服务可能有多个特征,一个特征可能有多个属性,CBCharacteristic是一个枚举值
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error{
        if(error){
            NSLog(@"查找特征错误");
            return;
        }
        for (CBCharacteristic * characteristic in service.characteristics) {
    //这里开发的时候和硬件沟通 获取到了具体的 特征的 UUID
            if([characteristic.UUID.UUIDString isEqualToString: CHARACTER_UUID_WRITE]){
                self.writeCharacter = characteristic; // 这是可写属性,保存起来后面需要使用它来写数据
            }else if([characteristic.UUID.UUIDString isEqualToString: CHARACTER_UUID_NOTI]){
    //监听该特征,外设发送通知的时候才能收到数据
                 [peripheral setNotifyValue:YES forCharacteristic:characteristic];
             }
        }
    }
    //接收通知代理
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        if (error) {
            NSLog(@"错误:%@",error.localizedDescription);
            return;
        }
        CBCharacteristicProperties  properties = characteristic.properties;
        if (properties & CBCharacteristicPropertyRead) {
            //如果具备读特性,即可读取特性的 Value 值
            [peripheral readValueForCharacteristic:characteristic];
        }
    }
    //读取Characteristic 中的值
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
        if (error) {
            NSLog(@"读取数据错误:%@",error.localizedDescription);
            return;
        }
        NSData * dada = characteristic.value;
        if (dada.length <= 0) {
            return;
        }
        Byte * byteData = (Byte*)[dada bytes];
        if (!(byteData[0] == 0x48 && byteData[1] == 0x65 &&byteData[2] == 0x6c &&byteData[3] == 0x6c)) {
             NSLog(@"通知数据:%@",dada);
        ....
        }
    }
    //写入数据的回调,返回写入成功失败结果
    -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
        if (error) {
            NSLog(@"写入数据失败");
        }
    }
    

    中心设备就是主动去连接别的设备的设备,一般就是手机,外设就是发送广播,被连接的设备,一般就是手环或者包含蓝牙模块的硬件设备
    CoreBlutooth已经把ble封装的很好,只需要顺着代理方法一步一步从扫描到连接到数据读写都可以完成
    另外: 蓝牙广播的数据长度大小有限制,不能通过广播获取 外设MAC地址来区分,所以区分多个外设的话就需要在广播的kCBAdvDataManufacturerData字段的数据里面包含, advertisementData数据包含多个字段,大家可以在使用的时候打印看看,具体的字段对数据的类型也有限制,服务,特征,描述都是对应的uuid来唯一标识,如何知道UUID的话直接使用UUID能免去多层遍历

    相关文章

      网友评论

          本文标题:CoreBlutooth使用

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