// // 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 } struct Output { let versionTip: BehaviorRelay let versionLogTip: BehaviorRelay } 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(value: versionStr) let versionLogTip = BehaviorRelay(value: NSAttributedString(string: "")) // 获取当前固件信息 SVProgressHUD.show() if isDFU == false { ProviderRequest(.getLastAppUpgrade(packageName: "FireBoltt.\(firwareModel.adapterNumber)")) .flatMapLatest({ [weak self] (json) -> Observable 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() /// 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() } } }