美文网首页iOS面试准备蓝牙
iOS蓝牙之扫描、链接、读写数据(二)

iOS蓝牙之扫描、链接、读写数据(二)

作者: 海耐射手 | 来源:发表于2018-11-30 16:31 被阅读210次

    接上篇iOS蓝牙之扫描、链接、读写数据(一)

    一、关于蓝牙从连接到读发数据

    在这篇文章中,我会按照上篇文章中介绍的流程,来代码实现:(由于自己也是第一次做蓝牙模块内容,有不对的地方还希望大神指正,小弟不胜感激......)

    #import "BLEController.h"
    //需要遵守蓝牙协议
    #import <CoreBluetooth/CoreBluetooth.h>
    
    #define kPeripheralName            @"B10001"//硬件设备蓝牙名称
    #define kServiceUUID               @"0003CDD0-0000-1000-8000-00805F9B0131" //服务的UUID
    #define kWriteCharacteristicUUID   @"0003CDD2-0000-1000-8000-00805F9B0131" //特征的UUID
    #define kNotifyCharacteristicUUID  @"0003CDD1-0000-1000-8000-00805F9B0131" //特征的UUID
    
    @interface BLEController ()<CBCentralManagerDelegate,CBPeripheralDelegate>
    @property (nonatomic, strong) CBCentralManager *manager;
    @property (nonatomic, strong) CBPeripheral *peripheral;
    @property (nonatomic, strong) CBCharacteristic *character;
    @end
    

    1、创建蓝牙管理者对象

    //第二个参数:nil默认为主线程
    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    

    2、该方法当蓝牙状态改变(打开或者关闭)的时候就会调用

    - (void)centralManagerDidUpdateState:(CBCentralManager *)central{
        switch (central.state) {
            case CBManagerStateUnknown:{
                NSLog(@"未知蓝牙状态");
            }
                break;
            case CBManagerStateResetting:{
                NSLog(@"系统服务连接暂时丢失");
            }
                break;
            case CBManagerStateUnsupported:{
                NSLog(@"该设备不支持蓝牙");
            }
                break;
            case CBManagerStateUnauthorized:{
                NSLog(@"该设备蓝牙为被授权");
            }
                break;
            case CBManagerStatePoweredOn:{
                NSLog(@"该设备蓝牙已打开");
                //打开后,开始扫描
                [self scanBlueTooth];
            }
                break;
            case CBManagerStatePoweredOff:{
                NSLog(@"该设备蓝牙没有打开");
            }
                break;
        }
    }
    

    3、发现外设后调用的方法

    //查到外设后,连接设备,停止扫描
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
        //可在该方法内部区分扫描到的蓝牙设备
        NSLog(@"发现外设peripheral:%@ =====RSSI:%@ =====UUID:%@", peripheral, RSSI, peripheral.identifier);
        
        //判断是否为你要连接的设备(我这里用设备名称判断的,扫描到我要的外设蓝牙名称后,停止扫描、连接外设)
        if ([peripheral.name isEqualToString:kPeripheralName]) {
            //扫描到设备之后停止扫描
            [_manager stopScan];
             //开始连接外设
            [_manager connectPeripheral:peripheral options:nil];
            _peripheral = peripheral;
        }
    }
    

    3.1、连接成功会被调用

    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
        NSLog(@"=====>%@连接成功 =====>UUID: %@",peripheral.name,peripheral.identifier);
        //连接设备之后设置蓝牙对象的代理,扫描服务
        [self.peripheral setDelegate:self];
    
        // 外设发现服务,传nil代表不过滤
        // 这里会触发外设的代理 didDiscoverServices 方法
        [self.peripheral discoverServices:nil];
    
        //这是我们业务需求:0.2s给我们的采集卡设备发一次命令来读数
        [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(timered:) userInfo:nil repeats:YES];
    }
    //返回的蓝牙服务通知通过代理实现(已经发现服务)
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
        NSLog(@"=====>发现服务");
        //一个设备可能为多个服务,所以要取你需要读写的那个服务(我们的外设就一个服务)
        for (CBService *service in peripheral.services) {
            if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
                
                //根据你要的那个服务去发现特性
                [self.peripheral discoverCharacteristics:nil forService:service];
            }
        }
    }
    

    3.2、连接失败会被调用

    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
        NSLog(@"=====>连接失败");
    }
    

    3.3、断开连接会被调用

    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
        NSLog(@"=====>断开连接");
        //断开连接后,可能是因为信号不好,所以我们的需求是继续连接我们的设备
        [self scanBlueTooth];
    }
    

    4、获得外围设备的服务、获得服务的特征

    //已经发现特性
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
        //首先读到外设特征服务,写到宏定义
        NSLog(@"发现特征服务:%@",service.UUID);
        
        for (CBCharacteristic *c in service.characteristics) {
            //其次读到两个特征值,写到宏定义
            NSLog(@"特征UUID: %@",c.UUID);
            
            //哪个特征值是读的,哪个特征值是写的,一般硬件兄弟都会告诉你的
            //(即使没有告诉你,也可以自己在AppStore下载LightBlue或UsrBleAssistent蓝牙调试工具看到你需要读写的特征值)
            if ([c.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID]) {
                //找到可写特征值D2
                _character = c;
                
            }else if ([c.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]){
                //设置通知
                [_peripheral setNotifyValue:YES forCharacteristic:c];
            }
        }
    }
    

    5、从外围设备读数据

    //获取外设发来的数据,不论是read和notify,获取数据都是从这个方法中读取
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
        if (!error) {
            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kNotifyCharacteristicUUID]]) {
                // 此处的byte数组就是接收到的数据
                NSString *str = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
                NSLog(@"原始数据:%@===>字符串:%@",characteristic.value,str);
            }
        }else{
            NSLog(@"error:%@", error);
        }
    }
    

    6、给蓝牙发送数据(及前边定时器的实现)

    #pragma mark - 定时器时间事件
    - (void)Timered:(NSTimer*)timer {
        //读信号强度
        [_peripheral readRSSI];
        //实时读数
        [self writeDataToBle:@"*APP.VALUE#$"];
    }
    
    //向蓝牙写入数
    - (void)writeDataToBle:(NSString *)aStr{
        if (_character.properties & CBCharacteristicPropertyWrite) {
            //BLE设备接收的数据需要转为NSData类型
            NSData *data = [aStr dataUsingEncoding:NSUTF8StringEncoding];
            //第一个参数:你要写入的数据,为data类型。
            //第二个参数:你要向哪个特征写入,一般就是属性为写入的那个特征。
            //第三个参数:你写入的方式,这个参数有两个枚举分别是:
            //CBCharacteristicWriteWithResponse//写入之后有回应
            //CBCharacteristicWriteWithoutResponse//写入之后不需要回应
            //使用WithResponse的成功后就会调用:-(void)peripheral:(CBPeripheral )peripheral didWriteValueForCharacteristic:(CBCharacteristic )characteristic error:(NSError )error;
            [_peripheral writeValue:data forCharacteristic:_character type:CBCharacteristicWriteWithResponse];
        }else{
            NSLog(@"该字段不可写!");
        }
    }
    

    到此BLE4.0从扫描到读数,已经实现

    二、关于蓝牙你需要知道

    1、一般蓝牙的读写通道(Characteristics)都是分开的,少数蓝牙会读写用同一个通道,在发现目标通道后,我们要利用写通道来向蓝牙写入数据,用读通道来读取蓝牙发来的包。

    2、对于外设的BLE的唯一标识,亲测同一设备的 UUID 对于每台iOS设备都不一样,只能尽量保证设备的唯一性;特别是自动重连的过程,让用户没有感知
    (在此工程中,我是根据外设的名字来判断链接的!因为我们的BLE外设名称都是以公司名称字母开头的!)

    3、关于BLE的Mac:在开发过程中发现CBCentralManager 和 CBPeripheral 里边都找不到和Mac地址相关的字段,所以如果领导要外设的Mac,你大胆的告诉他:你无能为力!
    (但有个解决办法:你可以与硬件工程师商量,能否将BLE的Mac地址写在服务里,这样可以为自动连接的唯一性做准备)

    4、关于Read还是Notify的区别:
    比如说我现在连接的是一个骑行设备上的蓝牙,此设备上有一个照明灯,现在APP可以控制这个灯,我发送开启的命令之后,如果开启成功那么设备会返回给我一个开启成功的信息,此时就是读取。
    而通知呢就是:APP需要实时显示该设备的速度,我不需要向设备发送任何的指令,只要它的速度发生了变化,就会向APP发送此时的速度信息(关于区别这块内容是百度其他作者的,不知对不对)

    当然关于BLE的实现也有好多优秀的三方库实现:比如 BabyBluetooth
    BabyBluetooth是一个最简单易用的蓝牙库,基于CoreBluetooth的封装,并兼容iOS和Mac OS X。
    BabyBluetooth的优点:
    1、基于原生CoreBluetooth框架封装的轻量级的开源库,可以帮你更简单地使用CoreBluetooth API。
    2、CoreBluetooth所有方法都是通过委托完成,代码冗余且顺序凌乱。BabyBluetooth使用block方法,可以重新按照功能和顺序组织代码,并提供许多方法减少蓝牙开发过程中的代码量。
    3、链式方法体,代码更简洁、优雅。
    4、通过channel切换区分委托调用,并方便切换
    5、完善的文档,且项目处于活跃状态,不断的更新中
    6、github上star最多的纯Bluetooch类库(非PhoneGap和SensorTag项目)

    相关文章

      网友评论

        本文标题:iOS蓝牙之扫描、链接、读写数据(二)

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