iOS 蓝牙开发(固件升级&空中升级)

最近几个月都在做蓝牙的项目,趁现在有空,就把在蓝牙开发过程中的心得和踩过的坑给记录下来,分享给大家,避免大家在蓝牙开发过程中能避免踩相同的坑。

本文是记录的蓝牙开发系列第二篇,想看第一篇关于蓝牙开发的使用请看这里

iOS 蓝牙开发(框架集成和数据交互)

正文:

概念:蓝牙固件升级也叫空中升级,英文OTA,指在线下载好更新固件,在把固件文件发送到蓝牙外设进行升级,类似我们的手机系统更新。一般固件都是zip类型的压缩文件,解压后会有三个文件:

iOS 蓝牙开发(固件升级&空中升级)_第1张图片
image.png

工具:使用开源的第三方升级库可以很方便地为蓝牙外设进行升级,这个库有Android和iOS两个版本:

IOS-Pods-DFU-Library

Android-DFU-Library

需要注意的是iOS是swift版本,如果你的项目是OC实现的,集成会有点麻烦,这里推荐用CocoaPod导入,需要注意的是要在Podfile文件添加use_frameworks!
集成后添加swift桥接文件,为了省事,强烈建议在导入升级库之前创建一个swift文件,Xcode会在你第一次创建swift文件时自动询问你是否创建桥接文件,只要点击“是”就完事了。
这时候你会发现Xcode自动创建一个.h文件,文件名为
"你的项目名-Bridging-Header.h"

接下来在桥接文件里import你在oc类需要用到的swift文件即可


iOS 蓝牙开发(固件升级&空中升级)_第2张图片
image.png

使用方法,第一步,现在服务器下载好固件,存入本地

    NSURL *url = [NSURL URLWithString:@"https://**********"];
    AFHTTPSessionManager *mange = [AFHTTPSessionManager manager];
        [[mange downloadTaskWithRequest:[NSURLRequest requestWithURL:url] progress:^(NSProgress * _Nonnull downloadProgress) {
            NSString *progress = [NSString stringWithFormat:@"%.0f%%",1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount *100];

            dispatch_async(dispatch_get_main_queue(), ^{
                self.hud.labelText = NSStringFormat(@"下载进度...%@",progress);
                self.hud.progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
            });


        } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {

            NSURL *downloadURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
            return [downloadURL URLByAppendingPathComponent:@"zipFile.zip"];

        } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
            
            //蓝牙升级
            [self uploadFileToBlueDevice:filePath];

        }] resume];
        
    }];

第二步,执行蓝牙升级

/**
 执行升级文件发送到固件操作
 */
- (void)uploadFileToBlueDevice:(NSURL *)filePath{

    CBPeripheral *peripheral = [AYBlueHelp shareBlue].peripheral;

    DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:filePath];
 
    DFUServiceInitiator *initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.manage target:peripheral];
    [initiator withFirmware:selectedFirmware];
    initiator.delegate = self; // - to be informed about current state and errors
    initiator.logger = self;
    initiator.progressDelegate = self;
    [initiator start];
}

最后一步,遵守协议,实现代理方法,监听升级状态


#pragma mark - LoggerDelegate
- (void)logWith:(enum LogLevel)level message:(NSString *)message{
    DLog(@"logWith---------level = %d,-------message,%@",level,message);
}
#pragma mark - DFUServiceDelegate


#pragma mark - DFUProgressDelegate

- (void)dfuProgressDidChangeFor:(NSInteger)part outOf:(NSInteger)totalParts to:(NSInteger)progress currentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond{
    
    self.hud.labelText = NSStringFormat(@"升级中...%zd %%",progress);
    self.hud.progress = progress * 0.01;
 
}

- (void)dfuStateDidChangeTo:(enum DFUState)state{
    DLog(@"dfuStateDidChangeTo-----------state = %d",state);
    
    if (state == 6) {
        [self.hud hide:YES];
        [MBProgressHUD wj_showSuccess:@"升级成功!"];

        //重新连接
        CBPeripheral *peripheral = [AYBlueHelp shareBlue].peripheral;
        [self.manage cancelPeripheralConnection:peripheral];
        
        self.manage = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
        [AYBlueHelp shareBlue].manage = self.manage;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.manage scanForPeripheralsWithServices:nil options:nil];
        });

    }
}
- (void)dfuError:(enum DFUError)error didOccurWithMessage:(NSString *)message{
//    DLog(@"dfuError-----------error = %d,-------------message = %@",error,message);
    [self.hud hide:YES];
    [MBProgressHUD wj_showError:message];
}

这里三个坑非常有必要重点强调一下:
第一个坑:
服务器存放的固件名字为01&02&20180501,如果你把这个名字作为你的文件名存放在本地你会发现文件永远都是不对的,这个坑我前后弄了2个小时才发现原来文件名包含“&”特殊字符,需要进行转义,这里建议直接以zipFile.zip等自定义的名字命名即可。坑死了···**
第二个坑:
前面我们把固件下载后存在本地,本来以为下次再进行下载的时候以相同的路径和文件名存放会覆盖掉前面的文件,这样每次下载下来的固件都是最新的,然而事实上并不会,沙盒里还是你上一次的文件,需要我们手动旧文件删除再存新文件!
第三个坑:
我们进行蓝牙升级后,流程是要把固件重启后再次自动连接。因为我们的硬件工程师告诉我只要在升级前发送个协议命令,升级后设备会自动重启,我只需要做好自动连接就可以了。问题就在于貌似是重新连接成功了,设备也显示已连接状态,手机也显示已连接,但是接下来却一直收不到蓝牙数据,也无法发送蓝牙数据交互,一开始是手动先断开连接,再重新扫描,再连接。然而发现执行断开连接:[self.manage cancelPeripheralConnection:self.peripheral];并不会执行didDisconnectPeripheral代理方法,重新扫描也不会收到广播数据...
最后分析有可能是升级完毕后我们的蓝牙管理类centerManage可能已经失效或者被销毁?因此尝试重新创建,再进行扫描操作,问题解决!!!

 self.manage = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
   
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.manage scanForPeripheralsWithServices:nil options:nil];
        });

到此为止,有关蓝牙升级的操作已经写完了,下一篇,我会总结一下在蓝牙开发过程中遇到的一些坑

喜欢的点个红心,有什么问题欢迎评论区讨论~~

你可能感兴趣的:(iOS 蓝牙开发(固件升级&空中升级))