美文网首页
iOS蓝牙开发

iOS蓝牙开发

作者: 洛洛爱吃肉 | 来源:发表于2019-10-23 16:59 被阅读0次

    CoreBluetooth详解

    CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。

    图中两组api分别对应不同的业务场景,左侧叫做中心模式,就是以你的app作为中心,连接其他的外设的场景,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景

    image

    关于蓝牙开发的一些重要的理论概念:

      1、服务(services):蓝牙外设对外广播的时候一定会有一个服务,有些时候也可以是有多个服务,服务下面包含一些特性,服务可以理解成一个模块的窗口;
      2、特征(characteristic):特征存在于服务下面的,一个服务下面可以有多个特征,特征可以理解成具体实现功能的窗口,一般的特性都会有value,也就是特征值,是特征和外界交互的最小单位;
      3、UUID:蓝牙上的唯一标示符,为了区分不同设备、服务及特征,就用UUID来表示。
    

    CBCentralMannager 中心模式

    以手机(app)作为中心,连接其他外设的场景。详细流程如下:

    步骤1.建立一个Central Manager实例进行蓝牙管理

    步骤2.搜索外围设备

    步骤3.连接外围设备

    步骤4.获得外围设备的服务

    步骤5.获得服务的特征

    步奏6.从外围设备读数据(直接读取和订阅两种方法)

    步骤7.给外围设备发送数据

    #import "ViewController.h"
    #import 
    #define kPeripheralName @"Kenshin Cui's Device" //外围设备名称
    #define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服务的UUID
    #define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特征的UUID
    
    @interface ViewController ()
    
    @property (strong,nonatomic) CBPeripheralManager *peripheralManager;//外围设备管理器
    
    @property (strong,nonatomic) NSMutableArray *centralM;//订阅此外围设备特征的中心设备
    
    @property (strong,nonatomic) CBMutableCharacteristic *characteristicM;//特征
    @property (weak, nonatomic) IBOutlet UITextView *log; //日志记录
    
    @end
    
    @implementation ViewController
    #pragma mark - 控制器视图事件
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    #pragma mark - UI事件
    - (IBAction)startClick:(UIBarButtonItem *)sender {
        //创建中心设备管理器并设置当前控制器视图为代理
        _centralManager=[[CBCentralManager alloc]initWithDelegate:self queue:nil];
    }
    
    #pragma mark - CBCentralManager代理方法
    //中心服务器状态更新后
    -(void)centralManagerDidUpdateState:(CBCentralManager *)central{
        switch (central.state) {
            case CBPeripheralManagerStatePoweredOn:
                NSLog(@"BLE已打开.");
                [self writeToLog:@"BLE已打开."];
                //扫描外围设备
    //            [central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
                [central scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
                break;
    
            default:
                NSLog(@"此设备不支持BLE或未打开蓝牙功能,无法作为外围设备.");
                [self writeToLog:@"此设备不支持BLE或未打开蓝牙功能,无法作为外围设备."];
                break;
        }
    }
    /**
     *  发现外围设备
     *
     *  @param central           中心设备
     *  @param peripheral        外围设备
     *  @param advertisementData 特征数据
     *  @param RSSI              信号质量(信号强度)
     */
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
        NSLog(@"发现外围设备...");
        [self writeToLog:@"发现外围设备..."];
        //停止扫描
        [self.centralManager stopScan];
        //连接外围设备
        if (peripheral) {
            //添加保存外围设备,注意如果这里不保存外围设备(或者说peripheral没有一个强引用,无法到达连接成功(或失败)的代理方法,因为在此方法调用完就会被销毁
            if(![self.peripherals containsObject:peripheral]){
                [self.peripherals addObject:peripheral];
            }
            NSLog(@"开始连接外围设备...");
            [self writeToLog:@"开始连接外围设备..."];
            [self.centralManager connectPeripheral:peripheral options:nil];
        }
    
    }
    //连接到外围设备
    -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
        NSLog(@"连接外围设备成功!");
        [self writeToLog:@"连接外围设备成功!"];
        //设置外围设备的代理为当前视图控制器
        peripheral.delegate=self;
        //外围设备开始寻找服务
        [peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
    }
    //连接外围设备失败
    -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
        NSLog(@"连接外围设备失败!");
        [self writeToLog:@"连接外围设备失败!"];
    }
    
    #pragma mark - CBPeripheral 代理方法
    //外围设备寻找到服务后
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
        NSLog(@"已发现可用服务...");
        [self writeToLog:@"已发现可用服务..."];
        if(error){
            NSLog(@"外围设备寻找服务过程中发生错误,错误信息:%@",error.localizedDescription);
            [self writeToLog:[NSString stringWithFormat:@"外围设备寻找服务过程中发生错误,错误信息:%@",error.localizedDescription]];
        }
        //遍历查找到的服务
        CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
        CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
        for (CBService *service in peripheral.services) {
            if([service.UUID isEqual:serviceUUID]){
                //外围设备查找指定服务中的特征
                [peripheral discoverCharacteristics:@[characteristicUUID] forService:service];
            }
        }
    }
    //外围设备寻找到特征后
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
        NSLog(@"已发现可用特征...");
        [self writeToLog:@"已发现可用特征..."];
        if (error) {
            NSLog(@"外围设备寻找特征过程中发生错误,错误信息:%@",error.localizedDescription);
            [self writeToLog:[NSString stringWithFormat:@"外围设备寻找特征过程中发生错误,错误信息:%@",error.localizedDescription]];
        }
        //遍历服务中的特征
        CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
        CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
        if ([service.UUID isEqual:serviceUUID]) {
            for (CBCharacteristic *characteristic in service.characteristics) {
                if ([characteristic.UUID isEqual:characteristicUUID]) {
                    //情景一:通知
                    /*找到特征后设置外围设备为已通知状态(订阅特征):
                     *1.调用此方法会触发代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
                     *2.调用此方法会触发外围设备的订阅代理方法
                     */
                    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                    //情景二:读取
    //                [peripheral readValueForCharacteristic:characteristic];
    //                    if(characteristic.value){
    //                    NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
    //                    NSLog(@"读取到特征值:%@",value);
    //                }
                }
            }
        }
    }
    //特征值被更新后
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        NSLog(@"收到特征更新通知...");
        [self writeToLog:@"收到特征更新通知..."];
        if (error) {
            NSLog(@"更新通知状态时发生错误,错误信息:%@",error.localizedDescription);
        }
        //给特征值设置新的值
        CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
        if ([characteristic.UUID isEqual:characteristicUUID]) {
            if (characteristic.isNotifying) {
                if (characteristic.properties==CBCharacteristicPropertyNotify) {
                    NSLog(@"已订阅特征通知.");
                    [self writeToLog:@"已订阅特征通知."];
                    return;
                }else if (characteristic.properties ==CBCharacteristicPropertyRead) {
                    //从外围设备读取新值,调用此方法会触发代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
                    [peripheral readValueForCharacteristic:characteristic];
                }
    
            }else{
                NSLog(@"停止已停止.");
                [self writeToLog:@"停止已停止."];
                //取消连接
                [self.centralManager cancelPeripheralConnection:peripheral];
            }
        }
    }
    //更新特征值后(调用readValueForCharacteristic:方法或者外围设备在订阅后更新特征值都会调用此代理方法)
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        if (error) {
            NSLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
            [self writeToLog:[NSString stringWithFormat:@"更新特征值时发生错误,错误信息:%@",error.localizedDescription]];
            return;
        }
        if (characteristic.value) {
            NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
            NSLog(@"读取到特征值:%@",value);
            [self writeToLog:[NSString stringWithFormat:@"读取到特征值:%@",value]];
        }else{
            NSLog(@"未发现特征值.");
            [self writeToLog:@"未发现特征值."];
        }
    }
    
    #pragma mark - 属性
    -(NSMutableArray *)peripherals{
       if(!_peripherals){
           _peripherals=[NSMutableArray array];
       }
       return _peripherals;
    }
    
    #pragma mark - 私有方法
    /**
     *  记录日志
     *
     *  @param info 日志信息
     */
    -(void)writeToLog:(NSString *)info{
        self.log.text=[NSString stringWithFormat:@"%@\r\n%@",self.log.text,info];
    }
    
    @end
    
    

    CBPeripheralManager 外设模式

    使用手机作为外设连接其他中心设备操作的场景。
    PS:因为苹果设备的安全性和封闭性,苹果设备不能通过与其他设备蓝牙链接进行文件传输等功能,所以在iOS与蓝牙开发的编程中是CBCentralMannager 中心模式编程居多.

    1 建立外设角色
    2 设置本地外设的服务和特征
    3 发布外设和特征
    4 广播服务
    5 响应中心的读写请求
    6 发送更新的特征值,订阅中心

    #import "ViewController.h"
    #import 
    #define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服务的UUID
    #define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特征的UUID
    
    @interface ViewController ()
    
    @property (strong,nonatomic) CBCentralManager *centralManager;//中心设备管理器
    @property (strong,nonatomic) NSMutableArray *peripherals;//连接的外围设备
    @property (weak, nonatomic) IBOutlet UITextView *log;//日志记录
    
    @end
    
    @implementation ViewController
    #pragma mark - 视图控制器方法
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    #pragma mark - UI事件
    //创建外围设备
    - (IBAction)startClick:(UIBarButtonItem *)sender {
        _peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
    }
    //更新数据
    - (IBAction)transferClick:(UIBarButtonItem *)sender {
        [self updateCharacteristicValue];
    }
    
    #pragma mark - CBPeripheralManager代理方法
    //外围设备状态发生变化后调用
    -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
        switch (peripheral.state) {
            case CBPeripheralManagerStatePoweredOn:
                NSLog(@"BLE已打开.");
                [self writeToLog:@"BLE已打开."];
                //添加服务
                [self setupService];
                break;
    
            default:
                NSLog(@"此设备不支持BLE或未打开蓝牙功能,无法作为外围设备.");
                [self writeToLog:@"此设备不支持BLE或未打开蓝牙功能,无法作为外围设备."];
                break;
        }
    }
    //外围设备添加服务后调用
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
        if (error) {
            NSLog(@"向外围设备添加服务失败,错误详情:%@",error.localizedDescription);
            [self writeToLog:[NSString stringWithFormat:@"向外围设备添加服务失败,错误详情:%@",error.localizedDescription]];
            return;
        }
    
        //添加服务后开始广播
        NSDictionary *dic=@{CBAdvertisementDataLocalNameKey:kPeripheralName};//广播设置
        [self.peripheralManager startAdvertising:dic];//开始广播
        NSLog(@"向外围设备添加了服务并开始广播...");
        [self writeToLog:@"向外围设备添加了服务并开始广播..."];
    }
    -(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
        if (error) {
            NSLog(@"启动广播过程中发生错误,错误信息:%@",error.localizedDescription);
            [self writeToLog:[NSString stringWithFormat:@"启动广播过程中发生错误,错误信息:%@",error.localizedDescription]];
            return;
        }
        NSLog(@"启动广播...");
        [self writeToLog:@"启动广播..."];
    }
    //订阅特征
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
        NSLog(@"中心设备:%@ 已订阅特征:%@.",central,characteristic);
        [self writeToLog:[NSString stringWithFormat:@"中心设备:%@ 已订阅特征:%@.",central.identifier.UUIDString,characteristic.UUID]];
        //发现中心设备并存储
        if (![self.centralM containsObject:central]) {
            [self.centralM addObject:central];
        }
        /*中心设备订阅成功后外围设备可以更新特征值发送到中心设备,一旦更新特征值将会触发中心设备的代理方法:
         -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
         */
    
    //    [self updateCharacteristicValue];
    }
    //取消订阅特征
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
        NSLog(@"didUnsubscribeFromCharacteristic");
    }
    -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(CBATTRequest *)request{
        NSLog(@"didReceiveWriteRequests");
    }
    -(void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict{
        NSLog(@"willRestoreState");
    }
    #pragma mark -属性
    -(NSMutableArray *)centralM{
        if (!_centralM) {
            _centralM=[NSMutableArray array];
        }
        return _centralM;
    }
    
    #pragma mark - 私有方法
    //创建特征、服务并添加服务到外围设备
    -(void)setupService{
        /*1.创建特征*/
        //创建特征的UUID对象
        CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
        //特征值
    //    NSString *valueStr=kPeripheralName;
    //    NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
        //创建特征
        /** 参数
         * uuid:特征标识
         * properties:特征的属性,例如:可通知、可写、可读等
         * value:特征值
         * permissions:特征的权限
         */
        CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
        self.characteristicM=characteristicM;
    //    CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
    //    characteristicM.value=value;
    
        /*创建服务并且设置特征*/
        //创建服务UUID对象
        CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
        //创建服务
        CBMutableService *serviceM=[[CBMutableService alloc]initWithType:serviceUUID primary:YES];
        //设置服务的特征
        [serviceM setCharacteristics:@[characteristicM]];
    
        /*将服务添加到外围设备*/
        [self.peripheralManager addService:serviceM];
    }
    //更新特征值
    -(void)updateCharacteristicValue{
        //特征值
        NSString *valueStr=[NSString stringWithFormat:@"%@ --%@",kPeripheralName,[NSDate   date]];
        NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
        //更新特征值
        [self.peripheralManager updateValue:value forCharacteristic:self.characteristicM onSubscribedCentrals:nil];
        [self writeToLog:[NSString stringWithFormat:@"更新特征值:%@",valueStr]];
    }
    /**
     *  记录日志
     *
     *  @param info 日志信息
     */
    -(void)writeToLog:(NSString *)info{
        self.log.text=[NSString stringWithFormat:@"%@\r\n%@",self.log.text,info];
    }
    @end
    

    蓝牙连接发送数据的流程图

    16a2962d7ce67281.png

    lightblue怎么用

    OTA(空中升级)

    将手机作为外围设备开发

    iOS浅析蓝牙设备之服务器(外围设备)

    APP被作为外设

    遇到的坑

    截屏2019-10-2315.57.54.png

    所以得知道 设备广播的的UUID
    或者用下面代码搜索蓝牙

     [self.centralManager scanForPeripheralsWithServices:nil options:nil];
    

    一般蓝牙连接失败我们会实现以下方法

    // 连接失败(但不包含超时,系统没有超时处理)
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
        if (error && self.delegate && [self.delegate respondsToSelector:@selector(centralTool:connectFailure:)]) {
            [self.delegate centralTool:self connectFailure:error];
        }
    }
    
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    {
        [self autoConnect];
    }
    

    但是当广播设备的蓝牙断开再想重连的话还要实现下面这个方法

    - (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray<CBService *> *)invalidatedServices{
    
        [self.centralManager connectPeripheral:peripheral options:nil];
          // 注意保留 Peripheral 的引用
        self.lastConnectedPeripheral = peripheral;
        [self startTimer];
    }
    

    只有这样才能实现断开蓝牙重开蓝牙秒连

    另外,有时候要提高搜索效率的话,可以过滤蓝牙的名字

        NSString *name = peripheral.name;
           if (name.length == 0) {
               return;
           }
           NSLog(@"name ===  %@",name);
           
           NSString *string = [NSString stringWithFormat:@"已发现 peripheral: %@ rssi: %@, UUID: %@ advertisementData: %@ ", peripheral, RSSI, peripheral.identifier, advertisementData];
           NSLog(@"string ==  %@",string);
        
    
    
           if ([name rangeOfString:@"Berry"].location == NSNotFound) {
               return;
           }
        
       
    //    NSString *kCBAdvDataLocalName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
    //    if ([kCBAdvDataLocalName rangeOfString:@"Berry"].location == NSNotFound) {
    //             return;
    //         }
    

    不过要注意 上文代码中的name 和 kCBAdvDataLocalName 是不同的
    一个是蓝牙设备的名字, 一个是广播的名字

    //添加服务后开始广播
    //广播设置
        NSDictionary *dic=@{CBAdvertisementDataLocalNameKey:MyDeviceName,CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:@"FFE0"]]};
    //广播设置
        [self.peripheralManager startAdvertising:dic];//开始广播
    

    第一个坑中设置UUID找不到的问题就是,需要在上述代码中设置UUID
    网上找的大部分资料都是关于如何设置中心设备的,外设的比较少,所以这里容易出错

    不过,如果这块都设置了,对于快速精准找到目标蓝牙服务是大有裨益的,也会减少耗电量,提高响应速度等等。

    相关文章

      网友评论

          本文标题:iOS蓝牙开发

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