iOS中蓝牙开发

作者: ziyouzhe4 | 来源:发表于2018-04-21 00:36 被阅读668次

    在上家公司待了2年,一直在做蓝牙相关的工作,也一直没有来总结关于蓝牙的文章,昨天在交流群中的小伙伴问到蓝牙相关问题,帮他看了看指出了问题,同时也发现很多东西长时间不看也都快忘记了,今天在这里总结一下,以防以后用时候重新找资料的烦恼,同时也希望能帮到有需要的朋友!

    下面分享中 部分内容查看了BabyBluetooth作者的分享,部分根据当时公司的业务需求总结实战.希望能对有需要的人有所帮助

    讲解分四大模块

    • 基础知识了解
    • app作为主设备
    • app作为从设备
    • BabyBluetooth入门
    • 实战OTA空中升级

    一.基础知识了解

    蓝牙4.0的设备由于耗电低,so也称作为BLE(Bluetooth Low Energy)百度百科有关于蓝牙历史的介绍


    • peripheral  外设,被连接的设备为perilheral
      
    • central   发起连接的时central
      
    • service and characteristic   服务和特征 每个设备都会提供服务和特征,类似于服务端的api,但是由于结构不
      同,每个外设会有很多的服务,每个服务中又包含很多字段,这些字段的权限一般分为 读read,写write,通知
      notiy几种,就是我们连接设备后具体需要操作的内容。
      
    • Description 每个characteristic可以对应一个或多个Description用户描述characteristic的信息或属性
      
    • Characteristic 一个characteristic包括一个单一变量和0-n个用来描述
      characteristic变量的descriptor,characteristic可以被认为是一个类型,类 似于类。
      
    • Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的
       范围,或者一个characteristic变量特定的测量单位。
      
    •  Service service是characteristic的集合
      
    * BLE中的开发要使用CoreBluetooth框架
    • CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。
       对应他们分别有一组相关的API和类
      
    两种模式图.png
    • 以上这两组分别对应不同的业务场景,左边叫做中心模式,就是你的app作为中心,连接其他的外设的场景,而右边称为外设模式,使用手机作为
      外设别其他中心设备操作的场景。
      
    • 每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知
      这么几种方式 
      
    typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
        CBCharacteristicPropertyBroadcast                                                = 0x01,
        CBCharacteristicPropertyRead                                                    = 0x02,
        CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
        CBCharacteristicPropertyWrite                                                    = 0x08,
        CBCharacteristicPropertyNotify                                                    = 0x10,
        CBCharacteristicPropertyIndicate                                                = 0x20,
        CBCharacteristicPropertyAuthenticatedSignedWrites                                = 0x40,
        CBCharacteristicPropertyExtendedProperties                                        = 0x80,
        CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)        = 0x100,
        CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)    = 0x200
    };
    
    外设-服务-特征 之间关系图.png
    蓝牙设备的几种状态:
    1. 准备(standby)
    2. 广播(advertising)
    3. 监听扫描(Scanning
    4. 发起连接(Initiating)
    5. 已连接(Connected)
    作为中心模式流程:
    1. 建立中心角色
    2. 扫描外设(discover)
    3. 连接外设(connect)
    4. 扫描外设中的服务和特征(discover)
      • 4.1 获取外设的services
      • 4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
    5. 与外设做数据交互(explore and interact)
    6. 订阅Characteristic的通知
    7. 断开连接(disconnect)
    作为外设模式流程:
    1. 启动一个Peripheral管理对象
    2. 本地Peripheral设置服务,特性,描述,权限等等
    3. Peripheral发送广告
    4. 设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法

    部分知识参考 GATT Profile 简介 有兴趣的同学可以阅读

    二. app作为主设备 扫描/连接 外设 的 相关代码实现

    上面说了iOS作为外设的实现流程:

    1. 建立中心角色
    2. 扫描外设(discover)
    3. 连接外设(connect)
    4. 扫描外设中的服务和特征(discover)
      4.1 获取外设的services
      4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
    5. 与外设做数据交互(explore and interact)
    6. 订阅Characteristic的通知
    7. 断开连接(disconnect)

    下面要开始动手写代码啦(🙂)


    蓝牙需要真机调试,所以必须要有真机, 下载Xcode 一个蓝牙外设

    // 1. 导入 CoreBluetooth 框架,和头文件
        // 1.1系统蓝牙设备管理对象,可以把他理解为主设备,通过他,可以去扫描和链接外设
        CBCentralManager *manager;
       // 1.2 用于保存被发现设备
        NSMutableArray *peripherals;
    
      // 1.3 设置主设备的委托,CBCentralManagerDelegate
      // 1.4  必须实现的: //主设备状态改变的委托,在初始化CBCentralManager的适合会打开设备,只有当设备正确打开后才能使用
     - (void)centralManagerDidUpdateState:(CBCentralManager *)central;            //其他选择实现的委托中比较重要的:找到外设的委托
     - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; 
      //1.5 连接外设成功的委托
     - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
    //外设连接失败的委托
     - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
    //断开外设的委托
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
     //初始化并设置委托和线程队列,最好一个线程的参数可以为nil,默认会就main线程
      manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
    
    
    • 方法中 当设备开关蓝牙 都会走这个回调
    -(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");
                        //开始扫描周围的外设
     /*
    第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
    第二个参数可以添加一些option,来增加精确的查找范围, 如 :
            NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey,
                                     nil];
            [manager scanForPeripheralsWithServices:nil options:options];
    
     */
                        [manager scanForPeripheralsWithServices:nil options:nil];
    
                        break;
                    default:
                        break;
                }
    
            }
    
    
    • 扫描到设备会进入方法
     -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
            NSLog(@"当扫描到设备:%@",peripheral.name);
            //接下来可以连接设备
      }
    
    • 连接外设(connect)
     //扫描到设备会进入方法
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    
    //接下连接我们的测试设备,如果你没有设备,可以下载一个app叫lightbule的app去模拟一个设备
    //这里自己去设置下连接规则,我设置的是P开头的设备
        if ([peripheral.name hasPrefix:@"P"]){
    
    //  一个主设备最多能连7个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的委托
      - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
     - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
    
     //找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
             [peripherals addObject:peripheral];
                        //连接设备
             [manager connectPeripheral:peripheral options:nil];
               
       }
     }
    //连接到Peripherals-成功
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
            {
                NSLog(@"连接到名称为(%@)的设备-成功",peripheral.name);
            }
    
    //连接到Peripherals-失败
    -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
            {
                NSLog(@"连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
            }
    
    //Peripherals断开连接
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
                NSLog(@"外设连接断开连接 %@ \n", [peripheral name]);
            }
    
    

    注意 : [peripherals addObject:peripheral]; 这句代码是要手动持有外设,必须保存!

    • 获取外设的服务和特征
      <连接外设成功后,就要开始获取 设备的服务了,都有相应的方法和回调,扫描到结果后会进入delegate方法。但是这个委托已经不再是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),这个委托包含了主设备与外设交互的许多 回调方法,包括获取services,获取characteristics,获取characteristics的值,获取characteristics的Descriptor,和Descriptor的值,写数据,读rssi,用通知的方式订阅数据等等。>
    // ================== 获取 外设的service ==============//
    
     //连接到Peripherals-成功回调
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
            {
                NSLog(@"连接到名称为(%@)的设备-成功",peripheral.name);
                //设置的peripheral委托CBPeripheralDelegate
                //@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
                [peripheral setDelegate:self];
                //扫描外设Services,成功后会进入方法:
    //-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    
      // 获取service时候也可以设置option 如: 
    //    NSMutableArray *serviceUUIDs = [NSMutableArray array ];
    //    CBUUID *cbuuid = [CBUUID UUIDWithString:[NSString stringWithFormat:@"%x",Ble_Device_Service]];
    //    [serviceUUIDs addObject:cbuuid];
    //   [peripheral discoverServices:serviceUUIDs]; 
    // 添加指定条件可以 提高效率
    
                [peripheral discoverServices:nil];
    
            }
    
            //扫描到Services
            -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
                //  NSLog(@"扫描到服务:%@",peripheral.services);
                if (error)
                {
                    NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
                    return;
                }
    
                for (CBService *service in peripheral.services) {
                      NSLog(@"%@",service.UUID);
    //扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
                      [peripheral discoverCharacteristics:nil forService:service];
                  }
    
            }
    
    
    // ==========获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值============//
    
     //扫描到Characteristics
         -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
             if (error)
             {
                 NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
                 return;
             }
    
             for (CBCharacteristic *characteristic in service.characteristics)
             {
                 NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
             }
    
             //获取Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
             for (CBCharacteristic *characteristic in service.characteristics){
                 {
                     [peripheral readValueForCharacteristic:characteristic];
                 }
             }
    
             //搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
             for (CBCharacteristic *characteristic in service.characteristics){
                 [peripheral discoverDescriptorsForCharacteristic:characteristic];
             }
    
    
         }
    
        //获取的charateristic的值
        -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
            //打印出characteristic的UUID和值
            //!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
            NSLog(@"characteristic uuid:%@  value:%@",characteristic.UUID,characteristic.value);
    
        }
    
        //搜索到Characteristic的Descriptors
        -(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    
            //打印出Characteristic和他的Descriptors
             NSLog(@"characteristic uuid:%@",characteristic.UUID);
            for (CBDescriptor *d in characteristic.descriptors) {
                NSLog(@"Descriptor uuid:%@",d.UUID);
            }
    
        }
        //获取到Descriptors的值
        -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
            //打印出DescriptorsUUID 和value
            //这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析
            NSLog(@"characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
        }
    
    
    • 把数据写到 Characteristic 中
    //写数据
        -(void)writeCharacteristic:(CBPeripheral *)peripheral
                    characteristic:(CBCharacteristic *)characteristic
                             value:(NSData *)value{
    
            //打印出 characteristic 的权限,可以看到有很多种,这是一个NS_OPTIONS,就是可以同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不同的通知方式。
            /*
             typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
             CBCharacteristicPropertyBroadcast                                              = 0x01,
             CBCharacteristicPropertyRead                                                   = 0x02,
             CBCharacteristicPropertyWriteWithoutResponse                                   = 0x04,
             CBCharacteristicPropertyWrite                                                  = 0x08,
             CBCharacteristicPropertyNotify                                                 = 0x10,
             CBCharacteristicPropertyIndicate                                               = 0x20,
             CBCharacteristicPropertyAuthenticatedSignedWrites                              = 0x40,
             CBCharacteristicPropertyExtendedProperties                                     = 0x80,
             CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)        = 0x100,
             CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)  = 0x200
             };
    
             */
            NSLog(@"%lu", (unsigned long)characteristic.properties);
    
            //只有 characteristic.properties 有write的权限才可以写
            if(characteristic.properties & CBCharacteristicPropertyWrite){
                /*
                    最好一个type参数可以为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区别是是否会有反馈
                */
                [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
            }else{
                NSLog(@"该字段不能写!");
            }
        }
    
    
    • 订阅Characteristic的通知
     //设置通知
        -(void)notifyCharacteristic:(CBPeripheral *)peripheral
                    characteristic:(CBCharacteristic *)characteristic{
            //设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    
        }
    
        //取消通知
        -(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
                     characteristic:(CBCharacteristic *)characteristic{
    
             [peripheral setNotifyValue:NO forCharacteristic:characteristic];
        }
    
    
    • 断开连接
    //停止扫描并断开连接
        -(void)disconnectPeripheral:(CBCentralManager *)centralManager
                         peripheral:(CBPeripheral *)peripheral{
            //停止扫描
            [centralManager stopScan];
            //断开连接
            [centralManager cancelPeripheralConnection:peripheral];
        }
    

    三. app作为外设被主设备连接

    这里使用app作为一个peripheral,被其它的central连接

    peripheral连接流程:
    1. 打开peripheralManager,设置peripheralManager的delegate
    2. 创建characteristics,characteristics的description 创建service,把characteristics添加到service中,再把service添加到peripheralManager中
    3. 开启广播advertising
    4. 对central的操作进行响应
      • 4.1 读characteristics请求
      • 4.2 写characteristics请求
      • 4.4 订阅和取消订阅characteristics
    具体步骤核心代码:
    1. <打开peripheralManager,设置peripheralManager的委托>
    2. <初始化peripheralManager, 这里的peripheralManager和CBCentralManager 很相似>
        peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
    
    1. <创建characteristics 创建service,把characteristics添加到service 然后把service添加到peripheralManager>
     - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
      // 在这里判断蓝牙的状态, 因为蓝牙打开成功后才能配置service和characteristics
        [self config];
    }
    
    1. <配置相关service和>
    //配置蓝牙的服务和特征
     -(void)config{
    
            //特征字段描述
            CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];
    
            /*
             可以通知的Characteristic
             properties:CBCharacteristicPropertyNotify
             permissions CBAttributePermissionsReadable
             */
            CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
    
            /*
             可读写的characteristics
             properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead
             permissions CBAttributePermissionsReadable | CBAttributePermissionsWriteable
             */
            CBMutableCharacteristic *readwriteCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readwriteCharacteristicUUID] properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
            //设置description
            CBMutableDescriptor *readwriteCharacteristicDescription1 = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"name"];
            [readwriteCharacteristic setDescriptors:@[readwriteCharacteristicDescription1]];
            /*
             只读的Characteristic
             properties:CBCharacteristicPropertyRead
             permissions CBAttributePermissionsReadable
             */
            CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readCharacteristicUUID] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
    
            //service1初始化并加入两个characteristics
            CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];
            [service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];
    
            //service2初始化并加入一个characteristics
            CBMutableService *service2 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];
            [service2 setCharacteristics:@[readCharacteristic]];
    
            //添加后就会调用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
            [peripheralManager addService:service1];
            [peripheralManager addService:service2];
     }
    
    1. <开启广播>
    //perihpheral添加了service
    - (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
        if (error == nil) {
            serviceNum++;
        }
        //因为我们添加了2个服务,所以想两次都添加完成后才去发送广播
        if (serviceNum==2) {
            //添加服务后可以在此向外界发出通告 调用完这个方法后会调用代理的
            //(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
            [peripheralManager startAdvertising:@{
                                                  CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],
                                                  CBAdvertisementDataLocalNameKey : LocalNameKey
                                                 }
             ];
    
        }
    
    }
    //peripheral开始发送advertising
    - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
        NSLog(@"in peripheralManagerDidStartAdvertisiong");
    }
    
    
    1. <对central的操作进行响应>
    • 1 读characteristics请求
      2 写characteristics请求
      3 订阅和取消订阅characteristics
      
    //订阅characteristics
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
        NSLog(@"订阅了 %@的数据",characteristic.UUID);
        //每秒执行一次给主设备发送一个当前时间的秒数
        timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendData:) userInfo:characteristic  repeats:YES];
    }
    
    //取消订阅characteristics
    -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
        NSLog(@"取消订阅 %@的数据",characteristic.UUID);
        //取消回应
        [timer invalidate];
    }
    
    //发送数据,发送当前时间的秒数
    -(BOOL)sendData:(NSTimer *)t {
        CBMutableCharacteristic *characteristic = t.userInfo;
        NSDateFormatter *dft = [[NSDateFormatter alloc]init];
        [dft setDateFormat:@"ss"];
        NSLog(@"%@",[dft stringFromDate:[NSDate date]]);
    
        //执行回应Central通知数据
        return  [peripheralManager updateValue:[[dft stringFromDate:[NSDate date]] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:nil];
    
    }
    
    
    //读characteristics请求
    - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
        NSLog(@"didReceiveReadRequest");
        //判断是否有读数据的权限
        if (request.characteristic.properties & CBCharacteristicPropertyRead) {
            NSData *data = request.characteristic.value;
            [request setValue:data];
            //对请求作出成功响应
            [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
        }else{
            [peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
        }
    }
    
    
    //写characteristics请求
    - (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
        NSLog(@"didReceiveWriteRequests");
        CBATTRequest *request = requests[0];
    
        //判断是否有写数据的权限
        if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
            //需要转换成CBMutableCharacteristic对象才能进行写值
            CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
            c.value = request.value;
            [peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
        }else{
            [peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
        }
    
    }
    

    四. 开源库 BabyBluetooth介绍

    有需要的同学可以看看BabyBluetooth源码

    GitHub上BabyBluetooth源码

    BabyBluetooth的优缺点
    BabyBluetooth使用block方法,可以一定程度上节省开发时间,节约成本,毕竟直接用官方的框架写还是比较麻烦的,
    当然这还要根据自己的实际情况来,当时由于我所在公司需要自己的SDK,根据当时的业务情况,只能我自己来写了,
    而BabyBluetooth链式的语法写起来比较清爽. 如果公司有一些业务上的需求BabyBluetooth没有实现,
    等待作者更新是不太现实的,这时候自己来动手开发一个吧!
    
    使用步骤可以参考 :

    iOS蓝牙篇之BabyBluetooth详解
    iOS蓝牙开发:解析BabyBluetooth
    iOS 蓝牙库BabyBluetooth的使用笔记

    五. OTA(空中升级实战)

    当时的业务需求是 :

    • 读取地锁当前硬件的版本号,判断是否需要升级
      
    • 如果需要升级,手机连接地锁后给地锁传输脚本给地锁中的硬件进行升级
      

    <读取硬件版本号 要连接上外设后 读取的characteristic的值>

    在回调方法 : 
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
    中处理
    
    版本号.png
    • 空中升级
      这里会借助一个第三方的开源库 : Nordic GitHub
    DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:self.filePath];
     self.initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.centralManager target:self.peripheral];
     [self.initiator withFirmwareFile:selectedFirmware];
     self.initiator.delegate = self; // - to be informed about current state and errors
     self.initiator.logger = self;
     self.initiator.progressDelegate = self;
     self.dfuController = [self.initiator start];
    
    

    👆需要的代理:

    #pragma mark DFUServiceDelegate
    - (void)onUploadProgress:(NSInteger)part totalParts:(NSInteger)totalParts progress:(NSInteger)progress currentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond{
        
        NSLog(@" %s   %.2ld",__func__,(long)progress);
        
        [self.delegate onFSMOtaUpgradeProgress:progress];
    }
    
    - (void)didErrorOccur:(enum DFUError)error withMessage:(NSString *)message{
        
        switch (error) {
            case DFUErrorRemoteSuccess:
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteSuccess"]];
                break;
            case DFUErrorRemoteInvalidState :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteInvalidState"]];
                break;
            case DFUErrorRemoteNotSupported :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteNotSupported"]];
                break;
            case DFUErrorRemoteDataExceedsLimit :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteDataExceedsLimit"]];
                break;
            case DFUErrorRemoteCrcError :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteCrcError"]];
                break;
            case DFUErrorRemoteOperationFailed:
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteOperationFailed"]];
                break;
            case DFUErrorFileNotSpecified:
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileNotSpecified"]];
                break;
            case DFUErrorFileInvalid :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileInvalid"]];
                break;
            case DFUErrorExtendedInitPacketRequired :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorExtendedInitPacketRequired"]];
                break;
            case DFUErrorInitPacketRequired :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorInitPacketRequired"]];
                break;
            case DFUErrorFailedToConnect :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFailedToConnect"]];
                break;
            case DFUErrorDeviceDisconnected:
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceDisconnected"]];
                break;
            case DFUErrorServiceDiscoveryFailed:
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorServiceDiscoveryFailed"]];
                break;
            case DFUErrorDeviceNotSupported :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceNotSupported"]];
                break;
            case DFUErrorReadingVersionFailed :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReadingVersionFailed"]];
                break;
            case DFUErrorEnablingControlPointFailed :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorEnablingControlPointFailed"]];
                break;
            case DFUErrorWritingCharacteristicFailed :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorWritingCharacteristicFailed"]];
                break;
            case DFUErrorReceivingNotificationFailed :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReceivingNotificationFailed"]];
                break;
            case DFUErrorUnsupportedResponse :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorUnsupportedResponse"]];
                break;
            case DFUErrorBytesLost :
                [self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorBytesLost"]];
                break;
            default:
                break;
        }
        
        
        NSLog(@" %s 升级失败  错误的信息是: %@",__func__ ,message);
        
        NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
        
        [savePath  stringByAppendingPathComponent:@"ble_app_wechat_debug_v1.1.zip"];
    
        [self disConnectDevice:self.lockId];
        
        NSError *err;
        NSFileManager *fileMgr = [NSFileManager defaultManager];
        [fileMgr removeItemAtPath:savePath error:&err];
    
        
    }
    
    #pragma mark LoggerDelegate 代理
    - (void)logWith:(enum LogLevel)level message:(NSString * _Nonnull)message{
        
    //    NSLog(@"消息是 : %@",message);
    }
    
    - (void)didStateChangedTo:(enum DFUState)state{
        
        switch (state) {
            case DFUStateAborted:{
                [self otaCallback:@"DFUStateAborted"];
                break;
            }
            case DFUStateConnecting:{
                [self otaCallback:@"DFUStateConnecting"];
                break;
            }
            case DFUStateDisconnecting:{
                [self otaCallback:@"DFUStateDisconnecting"];
                break;
            }
            case DFUStateEnablingDfuMode:{
                [self otaCallback:@"DFUStateEnablingDfuMode"];
                break;
            }
            case DFUStateStarting:{
                [self otaCallback:@"DFUStateStarting"];
                break;
            }
            case DFUStateUploading:{
                [self otaCallback:@"DFUStateUploading"];
                break;
            }
            case DFUStateValidating:{
                [self otaCallback:@"DFUStateValidating"];
                break;
            }
            case DFUStateCompleted:{
                [self.centralManager cancelPeripheralConnection:self.peripheral];
                [self otaCallback:@"DFUStateCompleted"];
                
                NSLog(@"%d",self.dfuController.paused);
                
                break;
            }
            default:
                break;
        }
        
    }
    
    //OTA升级的回调
    - (void)otaCallback:(NSString *)state{
        
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([self.delegate respondsToSelector:@selector(onFSMOtaUpgradeResult:)])
            {
                [self.delegate onFSMOtaUpgradeResult:state];
            }
        });
    }
    
    
    参考文章:

    刘彦玮的技术博客
    Nordic
    iOS蓝牙开发

    相关文章

      网友评论

        本文标题:iOS中蓝牙开发

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