You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
19 KiB
461 lines
19 KiB
// |
|
// FirmwareUpdateViewModels.swift |
|
// FireBoltt |
|
// |
|
// Created by lemo. on 2020/4/17. |
|
// Copyright © 2020 Sheldon. All rights reserved. |
|
// |
|
|
|
import UIKit |
|
import RxSwift |
|
import iOSDFULibrary |
|
import RTKOTASDK |
|
|
|
|
|
|
|
enum FirmwareOTAPlan { |
|
case nordic |
|
/// 富芮坤 |
|
case frq |
|
/// 瑞昱 |
|
case realtek |
|
/// 杰里 |
|
case jerry |
|
|
|
var fileType: String { |
|
switch self { |
|
case .nordic, .realtek: return "zip" |
|
case .frq: return "bin" |
|
case .jerry: return "ufw" |
|
} |
|
} |
|
} |
|
|
|
fileprivate let rtkEncryptKey: [UInt8] = [0x1B, 0x62, 0x8E, 0x11, 0xD3, 0x4C, 0x0D, 0x96, 0x70, 0x7E, 0x7A, 0x80, 0xDF, 0x63, 0x67, 0x1F, 0xFB, 0x10, 0xF6, 0xD1, 0x0C, 0x1A, 0x97, 0x9A, 0xE2, 0x29, 0x2B, 0x09, 0xC5, 0xF8, 0x46, 0x4E].reversed() |
|
fileprivate let rtkEncryptKeyData = Data(rtkEncryptKey) |
|
|
|
class FirmwareUpdateViewModels: ViewModel, ViewModelType, RTKLEPeripheralDelegate{ |
|
|
|
|
|
// override init() { |
|
// super.init() |
|
// jerryOTASuccess() |
|
// } |
|
|
|
|
|
struct Input { |
|
let startUpdate: Observable<Void> |
|
} |
|
|
|
struct Output { |
|
let versionTip: BehaviorRelay<String> |
|
let versionLogTip: BehaviorRelay<NSAttributedString> |
|
} |
|
|
|
func transform(input: FirmwareUpdateViewModels.Input) -> FirmwareUpdateViewModels.Output { |
|
input.startUpdate |
|
.subscribe(onNext: { [weak self] _ in |
|
// 二次确认 |
|
showAlert(currentViewController()!, MultiLanguageKey_FB.updateConfirmFB.localized, cancelText: "NO", confirmText: "YES") { (result) in |
|
if result { |
|
self?.startUpdateFirmware() |
|
} |
|
} |
|
}) |
|
.disposed(by: rx.disposeBag) |
|
|
|
let firwareModel = GlobalDeviceProfileModel.shareInstance.firwareModel ?? FirmwareModel() |
|
currenVersion = firwareModel.firmwareVersion; |
|
var versionStr = MultiLanguageKey_FB.currenVerisonFB.localized + firwareModel.firmwareVersion |
|
let versionTip = BehaviorRelay<String>(value: versionStr) |
|
let versionLogTip = BehaviorRelay<NSAttributedString>(value: NSAttributedString(string: "")) |
|
// 获取当前固件信息 |
|
SVProgressHUD.show() |
|
if isDFU == false { |
|
ProviderRequest(.getLastAppUpgrade(packageName: "FireBoltt.\(firwareModel.adapterNumber)")) |
|
.flatMapLatest({ [weak self] (json) -> Observable<String> in |
|
guard let `self` = self else { return .just("") } |
|
SVProgressHUD.dismiss() |
|
var url = json["data"]["url"].stringValue |
|
url += json["data"]["apkName"].stringValue |
|
let version = json["data"]["version"].intValue |
|
let newVersion = String(format: "%d.%d.%d", version / 1000, version / 100 % 10, version % 100) |
|
self.firmwareUrl = url |
|
self.newVersion = json["data"]["apkName"].stringValue |
|
// 更新 |
|
versionStr += ", \(MultiLanguageKey_FB.newVerisonFB.localized)\(newVersion)" |
|
let remarkStr = json["data"]["remark"].string ?? "" |
|
var remarkStrAtt = NSMutableAttributedString(string: "") |
|
do { |
|
if let data = remarkStr.data(using: String.Encoding.unicode, allowLossyConversion: true) { |
|
let attStr = try NSMutableAttributedString.init(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html], documentAttributes: nil) |
|
attStr.addAttribute(NSAttributedString.Key.font, value: SystemRegularFont(16), range: NSMakeRange(0, attStr.length)) |
|
remarkStrAtt = attStr |
|
} |
|
} |
|
versionLogTip.accept(remarkStrAtt) |
|
// 强制升级 |
|
let isMandatory = json["data"]["imposed"].boolValue |
|
if isMandatory { |
|
self.startUpdateFirmware(isMandatory: isMandatory) |
|
} |
|
return Observable.just(versionStr) |
|
}) |
|
.bind(to: versionTip) |
|
.disposed(by: rx.disposeBag) |
|
}else { |
|
// DFU设备直接获取本地数据升级 |
|
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! |
|
let filePath = "\(paths)/firmware.zip" |
|
// 启动升级 |
|
nordicFirmWare(fileName: filePath) |
|
} |
|
return Output(versionTip: versionTip, |
|
versionLogTip: versionLogTip) |
|
} |
|
|
|
/// 固件升级状态 |
|
private var isOtaUpdate: Bool = false |
|
/// 固件下载地址 |
|
private var firmwareUrl: String? |
|
/// 当前版本号 |
|
private var currenVersion: String = "" |
|
/// 最新版本号 |
|
private var newVersion: String = "" |
|
/// 是否进入就是DFU设备 |
|
private var isDFU: Bool = false |
|
/// 升级完成 |
|
let updateComplete = PublishSubject<Void>() |
|
/// OTA升级类型 |
|
var otaPlan: FirmwareOTAPlan = .nordic |
|
|
|
/// FRQOTA |
|
lazy var frqOTAManager: FRIUpdateOTAManager = { |
|
return FRIUpdateOTAManager() |
|
}() |
|
/// Realtek |
|
lazy var rtkOTAProfile: RTKOTAProfile = { |
|
return RTKOTAProfile() |
|
}() |
|
|
|
|
|
var rtkOTAPeripheral: RTKOTAPeripheral? |
|
var rtkDFUPeripheral: RTKMultiDFUPeripheral? |
|
var rtkOTAUpgradeBins: [RTKOTAUpgradeBin] = [] |
|
//var jlOTARunSDK: JL_RunSDK? |
|
|
|
init(isDFU: Bool = false) { |
|
super.init() |
|
self.isDFU = isDFU |
|
/// OTA升级方案 |
|
if let deviceInfo = UserDefaultsManagerFrieBoltt.getDeviceInfo() { |
|
otaPlan = deviceInfo.otaPlan |
|
switch otaPlan { |
|
case .frq: |
|
frqOTAManager.delegate = self |
|
case .realtek: |
|
rtkOTAProfile.delegate = self |
|
case .jerry: |
|
//JL_RunSDK.sharedInstance().delegate = self |
|
BluetoothFireBoltt.shareInstance()?.jl_delegate = self |
|
default: |
|
break |
|
} |
|
} |
|
// nordic DFU设备自动进入升级 |
|
kNotificationCenter.rx.notification(Notification.Name(rawValue: BluetoothNotificationAtDFUConnectSuccess)) |
|
.subscribe(onNext: { [weak self] _ in |
|
guard let `self` = self else { return } |
|
if self.isOtaUpdate == false { |
|
// DFU设备直接获取本地数据升级 |
|
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! |
|
let filePath = "\(paths)/firmware.zip" |
|
// 启动升级 |
|
self.nordicFirmWare(fileName: filePath) |
|
} |
|
}) |
|
.disposed(by: rx.disposeBag) |
|
} |
|
|
|
// private func jerryOTASuccess() { |
|
// /// 杰里OTA升级成功失败监听 |
|
// Observable.of(kNotificationCenter.rx.notification(Notification.Name(rawValue: "JerryOTASuccess"))) |
|
// .merge() |
|
// .subscribe(onNext: { [weak self] notification in |
|
// self!.isOtaUpdate = false |
|
// //SVProgressHUD.showSuccess(withStatus: MultiLanguageKey_FB.firmwareCompleteFB.localized) |
|
// self!.updateComplete.onNext(()) |
|
// |
|
// }) |
|
// .disposed(by: rx.disposeBag) |
|
// } |
|
|
|
} |
|
|
|
extension FirmwareUpdateViewModels { |
|
|
|
/// 启动固件升级 |
|
private func startUpdateFirmware(isMandatory: Bool = false) { |
|
if isMandatory { |
|
// 强制升级仅判断连接状态 |
|
let isConnect = BluetoothFireBoltt.shareInstance()?.isConnected ?? false |
|
if !isConnect { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.bleTipFB.localized) |
|
return |
|
} |
|
}else { |
|
// 是否已连接 && 是否正在同步数据 |
|
if !BluetoothService.shared.checkBleCmdEnable(isShow: true) { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.bleTipFB.localized) |
|
return |
|
} |
|
// 电量判断 |
|
if BluetoothService.shared.power.value < 30 { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.lowPowerFB.localized) |
|
return |
|
} |
|
} |
|
// 是否获取下载地址 |
|
guard let firmwareUrl = firmwareUrl else { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.networkErrorFB.localized) |
|
return |
|
} |
|
// 版本是否一致 |
|
if currenVersion == newVersion { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.latestVerisonFB.localized) |
|
return |
|
} |
|
// 下载固件 & 启动升级 |
|
SVProgressHUD.setDefaultMaskType(.black) |
|
ProviderRequestDownloadWithProgress(APIManagerFireBoltt.downLoad(url: firmwareUrl)) |
|
.subscribe(onNext: { |
|
[weak self] (result) in |
|
guard let `self` = self else {return} |
|
let progress = String(format: "%.0f%%", result.progress * 100) |
|
SVProgressHUD.show(withStatus: "下载中".localized + progress) |
|
if result.completed { |
|
// 写入OTA文件 |
|
let data = (result.response?.data ?? Data()) as NSData |
|
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! |
|
let filePath = self.otaPlan == .jerry ? String(format: "%@/%@", paths,self.newVersion) : "\(paths)/firmware.\(self.otaPlan.fileType)" |
|
if !data.write(toFile: filePath, atomically: true) { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.networkErrorFB.localized) |
|
return |
|
} |
|
switch self.otaPlan { |
|
case .nordic: |
|
self.nordicFirmWare(fileName: filePath) |
|
case .frq: |
|
self.frqOTAFirmWare(data: data) |
|
case .realtek: |
|
self.realtekOTAFirmWare(filePath: filePath) |
|
case .jerry: |
|
self.JLUpdateFailure(filePath: filePath, fileData: data) |
|
print("杰里SDK升级") |
|
} |
|
} |
|
}).disposed(by: rx.disposeBag) |
|
// // 测试固件地址 |
|
// guard let filePath = Bundle.main.path(forResource: "v1014", ofType: "bin"), let data = NSData(contentsOfFile: filePath) else { return } |
|
// switch otaPlan { |
|
// case .nordic: |
|
// nordicFirmWare(fileName: filePath) |
|
// case .frq: |
|
// frqOTAFirmWare(data: data) |
|
// case .realtek: |
|
// realtekOTAFirmWare(filePath: filePath) |
|
// } |
|
} |
|
|
|
/// 开始固件升级 |
|
private func nordicFirmWare(fileName: String) { |
|
guard let selectedFirmware = DFUFirmware.init(urlToZipFile: URL(fileURLWithPath: fileName)) else { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.updateFirmwareFailFB.localized) |
|
return |
|
} |
|
guard let peripheral = BluetoothFireBoltt.shareInstance()!.currenModel.peripheral else { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.updateFirmwareFailFB.localized) |
|
return |
|
} |
|
isOtaUpdate = true |
|
let centralManager = CBCentralManager() |
|
let initiator = DFUServiceInitiator.init(centralManager: centralManager, target: peripheral) |
|
_ = initiator.with(firmware: selectedFirmware) |
|
initiator.alternativeAdvertisingNameEnabled = false |
|
initiator.logger = self |
|
initiator.delegate = self |
|
initiator.progressDelegate = self |
|
_ = initiator.start() |
|
} |
|
|
|
/// 开始FRQ固件升级 |
|
private func frqOTAFirmWare(data: NSData) { |
|
guard let write = BluetoothFireBoltt.shareInstance()?.frqOTAwritecharacteristic, let blemodel = BluetoothFireBoltt.shareInstance()!.currenModel, let peripheral = blemodel.peripheral else { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.updateFirmwareFailFB.localized) |
|
return |
|
} |
|
isOtaUpdate = true |
|
frqOTAManager.binData = data as Data |
|
frqOTAManager.updateVersion = newVersion |
|
frqOTAManager.startUpdateOTA(peripheral, write: write) |
|
} |
|
|
|
/// 开始Realtek固件升级 |
|
private func realtekOTAFirmWare(filePath: String) { |
|
guard let blemodel = BluetoothFireBoltt.shareInstance()!.currenModel, let peripheral = blemodel.peripheral, let otaPeripheral = self.rtkOTAProfile.instantiatePeripheral(with: peripheral) as? RTKOTAPeripheral, let rtkOTAUpgradeBins = try? RTKOTAUpgradeBin.imagesExtracted(fromMPPackFilePath: filePath) else { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.updateFirmwareFailFB.localized) |
|
return |
|
} |
|
isOtaUpdate = true |
|
self.rtkOTAPeripheral = otaPeripheral |
|
self.rtkOTAProfile.connect(to: otaPeripheral) |
|
self.rtkOTAUpgradeBins = rtkOTAUpgradeBins |
|
} |
|
|
|
private func handleUpdateFailure() { |
|
if self.isOtaUpdate { |
|
SVProgressHUD.showError(withStatus: MultiLanguageKey_FB.updateFirmwareFailFB.localized) |
|
isOtaUpdate = false |
|
} |
|
} |
|
|
|
/// 杰里固件升级 |
|
private func JLUpdateFailure(filePath: String, fileData:NSData){ |
|
// Bluetooth.shareInstance()! .jerryOTAUpgrade(filePath,fileData: fileData as Data) |
|
BluetoothFireBoltt.shareInstance()?.otaFunc(withFilePaths: filePath); |
|
SVProgressHUD .dismiss() |
|
} |
|
|
|
} |
|
|
|
|
|
// MARK:- DFUProgressDelegate, DFUServiceDelegate, LoggerDelegate |
|
extension FirmwareUpdateViewModels: DFUProgressDelegate, DFUServiceDelegate, LoggerDelegate { |
|
func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) { |
|
SVProgressHUD.show(withStatus: "固件升级中".localized + " \(progress)%") |
|
LFLogs("固件升级进度 part: \(part) totalProgress: \(totalParts) progress: \(progress) currentSpeedBytesPerSecond: \(currentSpeedBytesPerSecond) avgSpeedBytesPerSecond: \(avgSpeedBytesPerSecond)") |
|
} |
|
|
|
func dfuStateDidChange(to state: DFUState) { |
|
LFLogs("固件升级状态: \(state)") |
|
if (state == .completed) { |
|
isOtaUpdate = false |
|
SVProgressHUD.showSuccess(withStatus: MultiLanguageKey_FB.firmwareCompleteFB.localized) |
|
updateComplete.onNext(()) |
|
} |
|
} |
|
|
|
func dfuError(_ error: DFUError, didOccurWithMessage message: String) { |
|
if error.rawValue != 0 { |
|
handleUpdateFailure() |
|
} |
|
} |
|
|
|
func logWith(_ level: LogLevel, message: String) { |
|
LFLogs("logWith level: \(level) message: \(message)") |
|
} |
|
} |
|
|
|
// MARK:- FRQUpdateOTAManagerDelegate |
|
extension FirmwareUpdateViewModels: FRIUpdateOTAManagerDelegate { |
|
|
|
func onOTAUpdateStatusDidChange(_ ota: FRIUpdateOTAManager, withProgress aProgress: Float) { |
|
LFLogs("[FRQOTA] onOTAUpdateStatusDidChange aProgress:\(aProgress)") |
|
SVProgressHUD.show(withStatus: "固件升级中".localized + " \(Int(aProgress))%") |
|
} |
|
|
|
func onOTAUpdateStatusCompletion(_ ota: FRIUpdateOTAManager) { |
|
LFLogs("[FRQOTA] onOTAUpdateStatusCompletion") |
|
isOtaUpdate = false |
|
SVProgressHUD.showSuccess(withStatus: MultiLanguageKey_FB.firmwareCompleteFB.localized) |
|
updateComplete.onNext(()) |
|
} |
|
|
|
func onOTAUpdateStart(_ ota: FRIUpdateOTAManager) { |
|
LFLogs("[FRQOTA] onOTAUpdateStart") |
|
} |
|
|
|
func onOTAUpdateStatusFailure(_ ota: FRIUpdateOTAManager, error err: Error) { |
|
LFLogs("[FRQOTA] onOTAUpdateStatusFailure") |
|
handleUpdateFailure() |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// MARK:- RTKLEProfileDelegate, RTKMultiDFUPeripheralDelegate |
|
extension FirmwareUpdateViewModels: RTKLEProfileDelegate, RTKMultiDFUPeripheralDelegate { |
|
|
|
func profileManagerDidUpdateState(_ profile: RTKLEProfile) { |
|
} |
|
|
|
func profile(_ profile: RTKLEProfile, didConnect peripheral: RTKLEPeripheral) { |
|
if peripheral == rtkOTAPeripheral, let rtkOTAPeripheral = rtkOTAPeripheral { |
|
// 转换DFU外设 |
|
if rtkOTAUpgradeBins.count == 1 && !(rtkOTAUpgradeBins.last?.icDetermined ?? false) { |
|
rtkOTAUpgradeBins.last?.assertAvailable(for: rtkOTAPeripheral) |
|
} |
|
// 判断升级类型 |
|
if rtkOTAPeripheral.canEnterOTAMode { |
|
rtkOTAProfile.translate(rtkOTAPeripheral) { [weak self] success, error, dfuPeripheral in |
|
if success, let dfuPeripheral = dfuPeripheral { |
|
self?.rtkDFUPeripheral = dfuPeripheral as? RTKMultiDFUPeripheral |
|
self?.rtkDFUPeripheral?.delegate = self |
|
self?.rtkOTAProfile.connect(to: dfuPeripheral) |
|
}else { |
|
self?.handleUpdateFailure() |
|
} |
|
} |
|
return |
|
} |
|
handleUpdateFailure() |
|
}else if peripheral == rtkDFUPeripheral { |
|
// 启动升级 |
|
rtkDFUPeripheral?.setEncryptKey(rtkEncryptKeyData) |
|
rtkDFUPeripheral?.upgradeImages(rtkOTAUpgradeBins, inOTAMode: true) |
|
} |
|
} |
|
|
|
func dfuPeripheral(_ peripheral: RTKDFUPeripheral, didSend length: UInt, totalToSend totalLength: UInt) { |
|
let progress = CGFloat(length) / CGFloat(totalLength) * 100 |
|
LFLogs("[RTKOTA] dfuPeripheral progress:\(progress)") |
|
SVProgressHUD.show(withStatus: "固件升级中".localized + " \(Int(progress))%") |
|
} |
|
|
|
func dfuPeripheral(_ peripheral: RTKDFUPeripheral, didFinishWithError err: Error?) { |
|
if err == nil { |
|
LFLogs("[RTKOTA] dfuPeripheral Upgrade complete successfully") |
|
isOtaUpdate = false |
|
SVProgressHUD.showSuccess(withStatus: MultiLanguageKey_FB.firmwareCompleteFB.localized) |
|
updateComplete.onNext(()) |
|
}else { |
|
LFLogs("[RTKOTA] dfuPeripheral Upgrade Fail") |
|
handleUpdateFailure() |
|
} |
|
} |
|
|
|
} |
|
|
|
extension FirmwareUpdateViewModels:JL_SDKOtaDelegate{ |
|
func otaProgress(with result: JL_OTAResult, withProgress progress: Float) { |
|
isOtaUpdate = true |
|
if result == .success{ |
|
isOtaUpdate = false |
|
SVProgressHUD.showSuccess(withStatus: MultiLanguageKey_FB.firmwareCompleteFB.localized) |
|
updateComplete.onNext(()) |
|
BluetoothFireBoltt.shareInstance()?.otaTimeClose() |
|
} |
|
|
|
if result == .fail { |
|
SVProgressHUD.dismiss() |
|
BluetoothFireBoltt.shareInstance()?.otaTimeClose() |
|
print("---> 固件升级失败") |
|
// Bluetooth.shareInstance()?.startAndStopReconnect(true) |
|
handleUpdateFailure() |
|
} |
|
|
|
|
|
} |
|
|
|
}
|
|
|