前言
這里主要參考這個項目:iOS-nRF-Toolbox(這個是Swift版的),它是Nordic公司開發(fā)的測試工程铆遭,包含一整套nRF設(shè)備的測試解決方案
OC版的可以參考這個項目:nRF-Toolbox-master 密碼: w7kd
nRF-Toolbox項目包含BGM屉来,HRM走越,HTM浩淘,DFU等多個模塊痘绎,我們只用到了其中的DFU升級模塊泉哈。打開項目蛉幸,在對應(yīng)的NORDFUViewController.swift中我們能夠看到有三個引用庫:
import UIKit
,import CoreBluetooth
,import iOSDFULibrary
,這里的iOSDFULibrary就是DFU升級的庫丛晦,也是解決DFU升級最重要的組件奕纫。我們只要把這個庫集成到我們的項目中,就能夠完成nRF設(shè)備的DFU升級了烫沙。
下面是對于OC引用DFU升級的操作步驟和我遇到的問題(使用的是藍牙連接升級)匹层。
集成iOSDFULibrary
我這里使用的是直接把庫導(dǎo)入到項目里面。(最好還是用cocoaPod導(dǎo)入锌蓄,不過好像會出現(xiàn)很多問題升筏,我這里還沒試過)
編譯出framework然后把庫導(dǎo)入項目
- 這一步是最關(guān)鍵也是最容易出問題的,這個庫也是由Swift寫成的瘸爽,直接打開項目您访,然后選擇iOSDFULibrary進行編譯
最后生成兩個framework:
- iOSDFULibrary.framework
- Zip.framework
編譯完成后,這兩個文件在項目下面:pod-->products文件夾剪决,右鍵在find里就可找到灵汪,直接放到你項目里面去就OK了。
導(dǎo)入到項目里面去了之后柑潦,更改一下項目配置:
把RunPath這里設(shè)置加一項:@executable_path/Frameworks
這時候在項目里面用頭文件就可以使用庫了:
#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報錯解決]
這個默認設(shè)置是NO,設(shè)置為YES就可以了!!!
還有一種錯誤就是運行的時候,一直崩在這個地方
//這里一直崩潰妒茬,Message from debugger: Terminated due to signal 9
//如果出現(xiàn)這種錯誤担锤,你用他們demo跑的時候,有可能也會出現(xiàn)這個問題
//這里報錯就是編譯的庫有問題乍钻,重新?lián)Q庫肛循,使用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文件導(dǎo)入到自己的項目。
b. 用這個方法導(dǎo)出的庫怜俐,在你的電腦上跑是正確的身堡,在你同事的電腦上跑可能就不行了~~~
- 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腳本內(nèi)容填寫如下內(nèi)容季稳,再次編譯即可
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時遇到的一些問題擅这,解決這些問題后就可以正常進行固件升級了。
下面了解一下使用這個庫來進行固件升級景鼠。
步驟:
- 一仲翎、連接藍牙,發(fā)送指令對藍牙設(shè)備進行控制
- 二铛漓、發(fā)送指令查詢固件版本
- 三谭确、收到藍牙發(fā)回來的應(yīng)答,判斷是否進行版本升級
- 四票渠、發(fā)送藍牙升級指令(此時藍牙會進入Dfu模式,此時藍牙名稱會改變芬迄,設(shè)備會斷開藍牙連接问顷,需要重新連接藍牙)
- 五、重新連接改名后的藍牙禀梳,下載固件版本到本地
- 六杜窄、使用IOSDFULibrary發(fā)送估計到藍牙進行升級
- 七、發(fā)送成功算途,固件升級成功~~~
- 八塞耕、估計升級完成之后,藍牙會回到正常模式嘴瓤,名稱改成之前的名稱扫外,設(shè)備會斷開藍牙,重新掃描藍牙連接即可
- 九廓脆、大功告成
下面是對IOSDFULibrary的使用:
導(dǎo)入三個delegate:LoggerDelegate
筛谚、DFUServiceDelegate
、 DFUProgressDelegate
,它們的作用分別為打印狀態(tài)日志停忿,DFU升級及藍牙連接狀態(tài),驾讲,監(jiān)視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里面要設(shè)置這些默認的屬性,否則文件傳輸會失敗吮铭,直接報operation not permitted或者Sending firwmare failed
//注意這里的 [NSNumber numberWithInt:12]时迫,如果是iPhone8或以上,如果設(shè)置為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里面設(shè)置
initiator.forceDfu = [[[NSUserDefaults standardUserDefaults] valueForKey:@"dfu_force_dfu"] boolValue];
//注意這里要在AppDelegate里面設(shè)置
initiator.packetReceiptNotificationParameter = [[[NSUserDefaults standardUserDefaults] valueForKey:@"dfu_number_of_packets"] intValue];
initiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = YES;
initiator.logger = self;
initiator.delegate = self;
initiator.progressDelegate = self;
//下載網(wǎng)絡(luò)文件升級
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];
//開始發(fā)送文件 如果這里沒有文件會報錯: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
//監(jiān)聽狀態(tài)
- (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;
//初始化并設(shè)置委托和線程隊列
//重新給藍牙連接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];
}
補充:
后面在測試的時候發(fā)現(xiàn):1. 第一次運行程序,升級的時候會出現(xiàn)崩潰扎谎。崩潰的位置:
解決方案:
崩潰的原因是firmwareRanges是nil碳想,在前面添加代碼進行判斷就行了:
if firmwareRanges == nil {
// Split firmware into smaller object of at most maxLen bytes, if firmware is bigger than maxLen
return
}
注意
改代碼的時候,不能直接改項目里面崩潰的那個位置的代碼毁靶,那里改了沒用胧奔,要改在編譯成庫的地方的代碼,然后重新編譯预吆,導(dǎo)出庫文件龙填,把庫文件重新加到項目里面。如果用的是carthage拐叉,使用流程如下:
重新導(dǎo)入包之后要更新一下項目配置岩遗,在Build Phases里面的Embed Frameworks里面把IOSDFULibrary庫加進了。這里不需要重新更換Zip庫凤瘦,只用更改IOSDFULibrary庫就OK~
注意:有朋友反映升級的時候會斷開連接宿礁,解決方法:
在APPDelegate文件里加上:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//在AppDelegate里面要設(shè)置這些默認的屬性,否則文件傳輸會失敗蔬芥,直接報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;
}
有朋友@可樂超反映梆靖,寫了上面的代碼,在升級的時候還是會崩潰笔诵,斷開連接返吻。
原因是設(shè)置的packetReceiptNotificationParameter
這個參數(shù)的值太大的原因。感謝@可樂超乎婿。
謝謝~
慢慢來测僵,一步一個巴掌印。谢翎。捍靠。。岳服。