iOS中蓝牙开发

在上家公司待了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和类
    
iOS中蓝牙开发_第1张图片
两种模式图.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
};
iOS中蓝牙开发_第2张图片
外设-服务-特征 之间关系图.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
            [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;
中处理
iOS中蓝牙开发_第3张图片
版本号.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中蓝牙开发)