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.

462 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()
}
}
}