前言
-
这里主要参考这个项目: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进行编译
![](https://img.haomeiwen.com/i293993/7a3da87185380f8f.png)
最后生成两个framework:
- iOSDFULibrary.framework
- Zip.framework
编译完成后,这两个文件在项目下面:pod-->products文件夹,右键在find里就可找到,直接放到你项目里面去就OK了。
导入到项目里面去了之后,更改一下项目配置:
![](https://img.haomeiwen.com/i293993/788cd4f25cf83a85.png)
![](https://img.haomeiwen.com/i293993/6e7bb738186ce25b.png)
把RunPath这里设置加一项:@executable_path/Frameworks
![](https://img.haomeiwen.com/i293993/5a51bb2f86079cf0.png)
这时候在项目里面用头文件就可以使用库了:
#import <iOSDFULibrary/iOSDFULibrary-Swift.h>
运行程序,如果报错如下:
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报错解决]
![](https://img.haomeiwen.com/i293993/3673a00309c46a46.png)
这个默认设置是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代码编译的方法的时候,做一些针对性的改变。
![](https://img.haomeiwen.com/i293993/cac892a22cccc84e.png)
附加一些另外的解决方法:
- 把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
![](https://img.haomeiwen.com/i293993/d5b1251e3eb94e7c.png)
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
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];
}
补充:
后面在测试的时候发现:第一次运行程序,升级的时候会出现崩溃。崩溃的位置:
![](https://img.haomeiwen.com/i293993/5d35a19833d6f1ed.png)
解决方案:
崩溃的原因是firmwareRanges是nil,在前面添加代码进行判断就行了:
if firmwareRanges == nil {
// Split firmware into smaller object of at most maxLen bytes, if firmware is bigger than maxLen
return
}
![](https://img.haomeiwen.com/i293993/68bf9ca672453844.png)
注意
改代码的时候,不能直接改项目里面崩溃的那个位置的代码,那里改了没用,要改在编译成库的地方的代码,然后重新编译,导出库文件,把库文件重新加到项目里面。如果用的是carthage,使用流程如下:
![](https://img.haomeiwen.com/i293993/bd069a571bf15356.png)
![](https://img.haomeiwen.com/i293993/2af9059334306c58.png)
重新导入包之后要更新一下项目配置,在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;
}
谢谢~
慢慢来,一步一个巴掌印。。。。。
网友评论