前言
这里主要参考这个项目:iOS-nRF-Toolbox(这个是Swift版的),它是Nordic公司开发的测试工程,包含一整套nRF设备的测试解决方案
OC版的可以参考这个项目:nRF-Toolbox-master 密码: w7kd
nRF-Toolbox项目包含BGM,HRM,HTM,DFU等多个模块,我们只用到了其中的DFU升级模块。打开项目,在对应的NORDFUViewController.swift中我们能够看到有三个引用库:
import UIKit
,import CoreBluetooth
,import iOSDFULibrary
,这里的iOSDFULibrary就是DFU升级的库,也是解决DFU升级最重要的组件。我们只要把这个库集成到我们的项目中,就能够完成nRF设备的DFU升级了。
下面是对于OC引用DFU升级的操作步骤和我遇到的问题(使用的是蓝牙连接升级)。
集成iOSDFULibrary
我这里使用的是直接把库导入到项目里面。(最好还是用cocoaPod导入,不过好像会出现很多问题,我这里还没试过)
编译出framework然后把库导入项目
- 这一步是最关键也是最容易出问题的,这个库也是由Swift写成的,直接打开项目,然后选择iOSDFULibrary进行编译
最后生成两个framework:
- iOSDFULibrary.framework
- Zip.framework
编译完成后,这两个文件在项目下面:pod-->products文件夹,右键在find里就可找到,直接放到你项目里面去就OK了。
导入到项目里面去了之后,更改一下项目配置:
把RunPath这里设置加一项:@executable_path/Frameworks
这时候在项目里面用头文件就可以使用库了:
#import
运行程序,如果报错如下:
dyld: Library not loaded: @rpath/libswiftCore.dylib
Referenced from: /private/var/containers/Bundle/Application/02516D79-BB30-4278-81B8-3F86BF2AE2A7/XingtelBLE.app/Frameworks/iOSDFULibrary.framework/iOSDFULibrary
Reason: image not found
[dyld: Library not loaded: @rpath/libswiftCore.dylib报错解决]
这个默认设置是NO,设置为YES就可以了!!!
还有一种错误就是运行的时候,一直崩在这个地方
//这里一直崩溃,Message from debugger: Terminated due to signal 9
//如果出现这种错误,你用他们demo跑的时候,有可能也会出现这个问题
//这里报错就是编译的库有问题,重新换库,使用carthage可以打包的库可以解决这个问题
DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:url];
那一般情况是你编译的包有问题。下面提供2种重新打包的方法,可以尝试一下:
- 1 直接是用作者提供的解决方法
1、On your mac please install carthage (instructions)
2、Create a file named cartfile anywhere on your computer
3、add the following content to the file:
github "NordicSemiconductor/IOS-Pods-DFU-Library" ~> 2.1.2
github "marmelroy/Zip" ~> 0.6
1、Open a new terminal and cd to the directory where the file is
2、Enter the command carthage update --platform iOS
3、Carthage will now take care of building your frameworks, the produced .framework files will be found in a newly created directory called Carthage/Build/iOS,copy over iOSDFULibrary.framework and Zip.framework to your project and you are good to go.
注意
a. carthage是一种和cocoapods相似的的类库管理工具,如果不会使用的话可以参照carthage的使用,将framework文件导入到自己的项目。
b. 用这个方法导出的库,在你的电脑上跑是正确的,在你同事的电脑上跑可能就不行了~~~
- 2在使用我上面的用Demo代码编译的方法的时候,做一些针对性的改变。
附加一些另外的解决方法:
- 把target -- > General -- > 下面的Linked Frameworks and Libraries下面的IOSDFULibrary和Zip两个库的右边的status改成options试试。
- 把RunPath里面的@executable_path/Frameworks删了再重新添加的试试
打包上架时报ERROR IT MS-90087等问题
ERROR ITMS-90087: "Unsupported Architectures. The executable for ***.app/Frameworks/SDK.framework contains unsupported architectures '[x86_64, i386]'."
ERROR ITMS-90362: "Invalid Info.plist value. The value for the key 'MinimumOSVersion' in bundle ***.app/Frameworks/SDK.framework is invalid. The minimum value is 8.0"
ERROR ITMS-90209: "Invalid Segment Alignment. The app binary at '***.app/Frameworks/SDK.framework/SDK' does not have proper segment alignment. Try rebuilding the app with the latest Xcode version."
ERROR ITMS-90125: "The binary is invalid. The encryption info in the LC_ENCRYPTION_INFO load command is either missing or invalid, or the binary is already encrypted. This binary does not seem to have been built with Apple's linker."
解决方法,添加Run Script Phase
Shell脚本内容填写如下内容,再次编译即可
APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"
EXTRACTED_ARCHS=()
for ARCH in $ARCHS
do
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done
echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"
echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
done
上面的是集成IOSDFULibrary时遇到的一些问题,解决这些问题后就可以正常进行固件升级了。
下面了解一下使用这个库来进行固件升级。
步骤:
- 一、连接蓝牙,发送指令对蓝牙设备进行控制
- 二、发送指令查询固件版本
- 三、收到蓝牙发回来的应答,判断是否进行版本升级
- 四、发送蓝牙升级指令(此时蓝牙会进入Dfu模式,此时蓝牙名称会改变,设备会断开蓝牙连接,需要重新连接蓝牙)
- 五、重新连接改名后的蓝牙,下载固件版本到本地
- 六、使用IOSDFULibrary发送估计到蓝牙进行升级
- 七、发送成功,固件升级成功~~~
- 八、估计升级完成之后,蓝牙会回到正常模式,名称改成之前的名称,设备会断开蓝牙,重新扫描蓝牙连接即可
- 九、大功告成
下面是对IOSDFULibrary的使用:
导入三个delegate:LoggerDelegate
、DFUServiceDelegate
、 DFUProgressDelegate
,它们的作用分别为打印状态日志,DFU升级及蓝牙连接状态,,监视DFU升级进度。
//DFU
@property (strong, nonatomic) DFUServiceController *dfuService;
@property (strong, nonatomic) DFUFirmware *selectedFirmware;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//在AppDelegate里面要设置这些默认的属性,否则文件传输会失败,直接报operation not permitted或者Sending firwmare failed
//注意这里的 [NSNumber numberWithInt:12],如果是iPhone8或以上,如果设置为12以上就会崩溃,最大为6
NSDictionary* defaults = [NSDictionary dictionaryWithObjects:@[@"2.3", [NSNumber numberWithInt:12], @NO] forKeys:@[@"key_diameter", @"dfu_number_of_packets", @"dfu_force_dfu"]];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
return YES;
}
- (void)performDFU
{
// To start the DFU operation the DFUServiceInitiator must be used
DFUServiceInitiator *initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.manager target:_val_peripheral];
//注意这里要在AppDelegate里面设置
initiator.forceDfu = [[[NSUserDefaults standardUserDefaults] valueForKey:@"dfu_force_dfu"] boolValue];
//注意这里要在AppDelegate里面设置
initiator.packetReceiptNotificationParameter = [[[NSUserDefaults standardUserDefaults] valueForKey:@"dfu_number_of_packets"] intValue];
initiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = YES;
initiator.logger = self;
initiator.delegate = self;
initiator.progressDelegate = self;
//下载网络文件升级
NSString *firmwaresPath = [[verManager getFirmwaresPath] stringByAppendingPathComponent:@"firmWareVersion.zip"];
NSURL *url = [NSURL fileURLWithPath:firmwaresPath];
_selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:url];
//本地zip文件升级
//NSURL *filePath = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"app_only.zip" ofType:nil]];
//_selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:filePath];
//开始发送文件 如果这里没有文件会报错:Firmare not specified
_dfuService = [[initiator withFirmware:_selectedFirmware] start];
}
#pragma mark - LoggerDelegate
-(void)logWith:(enum LogLevel)level message:(NSString *)message
{
NSLog(@"%ld: %@", (long) level, message);
}
#pragma mark - DFUServiceDelegate
//监听状态
- (void)dfuStateDidChangeTo:(enum DFUState)state
{
switch (state) {
case DFUStateConnecting:
NSLog(@"Connecting...");
break;
case DFUStateStarting:
NSLog(@"Starting...");
break;
case DFUStateEnablingDfuMode:
NSLog(@"EnablingDfuMode...");
break;
case DFUStateUploading:
NSLog(@"Uploading...");
break;
case DFUStateValidating:
NSLog(@"Validating...");
break;
case DFUStateDisconnecting:
NSLog(@"Disconnecting...");
break;
case DFUStateCompleted:
{
//升级成功 Upload complete
//重新连接蓝牙
self.manager = nil;
//初始化并设置委托和线程队列
//重新给蓝牙连接Manager赋值,不然有可能升级后连接不上蓝牙
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:@{CBCentralManagerOptionShowPowerAlertKey:[NSNumber numberWithBool:NO]}];
break;
}
case DFUStateAborted:
NSLog(@"Aborted...");
break;
default:
break;
}
}
- (void)dfuError:(enum DFUError)error didOccurWithMessage:(NSString * _Nonnull)message
{
NSLog(@"Error %ld: %@", (long) error, message);
}
#pragma mark - DFUProgressDelegate
//进度
- (void)dfuProgressDidChangeFor:(NSInteger)part outOf:(NSInteger)totalParts to:(NSInteger)progresscurrentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond;
{
[SVProgressHUD showProgress:(float) progress / 100.0f status:[NSString stringWithFormat:@"升级固件中%ld%%",(long)progress]];
// progress.progress = (float) percentage / 100.0f;
// progressLabel.text = [NSString stringWithFormat:@"%ld%% (%ld/%ld)", (long) percentage, (long) part, (long) totalParts];
}
补充:
后面在测试的时候发现:1. 第一次运行程序,升级的时候会出现崩溃。崩溃的位置:
解决方案:
崩溃的原因是firmwareRanges是nil,在前面添加代码进行判断就行了:
if firmwareRanges == nil {
// Split firmware into smaller object of at most maxLen bytes, if firmware is bigger than maxLen
return
}
注意
改代码的时候,不能直接改项目里面崩溃的那个位置的代码,那里改了没用,要改在编译成库的地方的代码,然后重新编译,导出库文件,把库文件重新加到项目里面。如果用的是carthage,使用流程如下:
重新导入包之后要更新一下项目配置,在Build Phases里面的Embed Frameworks里面把IOSDFULibrary库加进了。这里不需要重新更换Zip库,只用更改IOSDFULibrary库就OK~
注意:有朋友反映升级的时候会断开连接,解决方法:
在APPDelegate文件里加上:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//在AppDelegate里面要设置这些默认的属性,否则文件传输会失败,直接报operation not permitted或者Sending firwmare failed
NSDictionary* defaults = [NSDictionary dictionaryWithObjects:@[@"2.3", [NSNumber numberWithInt:12], @NO] forKeys:@[@"key_diameter", @"dfu_number_of_packets", @"dfu_force_dfu"]];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
return YES;
}
有朋友@可乐超反映,写了上面的代码,在升级的时候还是会崩溃,断开连接。
原因是设置的packetReceiptNotificationParameter
这个参数的值太大的原因。感谢@可乐超。
谢谢~
慢慢来,一步一个巴掌印。。。。。