SwiftNordicDfuPlugin.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import Flutter
  2. import UIKit
  3. import iOSDFULibrary
  4. import CoreBluetooth
  5. public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, DFUServiceDelegate, DFUProgressDelegate, LoggerDelegate {
  6. let registrar: FlutterPluginRegistrar
  7. var sink: FlutterEventSink!
  8. var pendingResult: FlutterResult?
  9. var deviceAddress: String?
  10. private var dfuController : DFUServiceController!
  11. public static func register(with registrar: FlutterPluginRegistrar) {
  12. let instance = SwiftNordicDfuPlugin(registrar)
  13. let method = FlutterMethodChannel(name: "dev.steenbakker.nordic_dfu/method", binaryMessenger: registrar.messenger())
  14. let event = FlutterEventChannel(name:
  15. "dev.steenbakker.nordic_dfu/event", binaryMessenger: registrar.messenger())
  16. registrar.addMethodCallDelegate(instance, channel: method)
  17. event.setStreamHandler(instance)
  18. }
  19. init(_ registrar: FlutterPluginRegistrar) {
  20. self.registrar = registrar
  21. super.init()
  22. }
  23. public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
  24. switch call.method {
  25. case "startDfu": initializeDfu(call, result)
  26. case "abortDfu" : abortDfu()
  27. default: result(FlutterMethodNotImplemented)
  28. }
  29. }
  30. public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
  31. sink = events
  32. return nil
  33. }
  34. public func onCancel(withArguments arguments: Any?) -> FlutterError? {
  35. sink = nil
  36. return nil
  37. }
  38. private func abortDfu() {
  39. _ = dfuController?.abort()
  40. dfuController = nil
  41. }
  42. private func initializeDfu(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
  43. guard let arguments = call.arguments as? Dictionary<String, AnyObject> else {
  44. result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil))
  45. return
  46. }
  47. let name = arguments["name"] as? String
  48. guard let address = arguments["address"] as? String,
  49. var filePath = arguments["filePath"] as? String else {
  50. result(FlutterError(code: "ABNORMAL_PARAMETER", message: "address and filePath are required", details: nil))
  51. return
  52. }
  53. let forceDfu = arguments["forceDfu"] as? Bool
  54. let enableUnsafeExperimentalButtonlessServiceInSecureDfu = arguments["enableUnsafeExperimentalButtonlessServiceInSecureDfu"] as? Bool
  55. let fileInAsset = (arguments["fileInAsset"] as? Bool) ?? false
  56. if (fileInAsset) {
  57. let key = registrar.lookupKey(forAsset: filePath)
  58. guard let pathInAsset = Bundle.main.path(forResource: key, ofType: nil) else {
  59. result(FlutterError(code: "ABNORMAL_PARAMETER", message: "file in asset not found \(filePath)", details: nil))
  60. return
  61. }
  62. filePath = pathInAsset
  63. }
  64. let alternativeAdvertisingNameEnabled = arguments["alternativeAdvertisingNameEnabled"] as? Bool
  65. startDfu(address,
  66. name: name,
  67. filePath: filePath,
  68. forceDfu: forceDfu,
  69. enableUnsafeExperimentalButtonlessServiceInSecureDfu: enableUnsafeExperimentalButtonlessServiceInSecureDfu,
  70. alternativeAdvertisingNameEnabled: alternativeAdvertisingNameEnabled,
  71. result: result)
  72. }
  73. private func startDfu(
  74. _ address: String,
  75. name: String?,
  76. filePath: String,
  77. forceDfu: Bool?,
  78. enableUnsafeExperimentalButtonlessServiceInSecureDfu: Bool?,
  79. alternativeAdvertisingNameEnabled: Bool?,
  80. result: @escaping FlutterResult) {
  81. guard let uuid = UUID(uuidString: address) else {
  82. result(FlutterError(code: "DEVICE_ADDRESS_ERROR", message: "Device address conver to uuid failed", details: "Device uuid \(address) convert to uuid failed"))
  83. return
  84. }
  85. guard let firmware = DFUFirmware(urlToZipFile: URL(fileURLWithPath: filePath)) else {
  86. result(FlutterError(code: "DFU_FIRMWARE_NOT_FOUND", message: "Could not dfu zip file", details: nil))
  87. return
  88. }
  89. let dfuInitiator = DFUServiceInitiator(queue: nil)
  90. .with(firmware: firmware);
  91. dfuInitiator.delegate = self
  92. dfuInitiator.progressDelegate = self
  93. dfuInitiator.logger = self
  94. if let enableUnsafeExperimentalButtonlessServiceInSecureDfu = enableUnsafeExperimentalButtonlessServiceInSecureDfu {
  95. dfuInitiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = enableUnsafeExperimentalButtonlessServiceInSecureDfu
  96. }
  97. if let forceDfu = forceDfu {
  98. dfuInitiator.forceDfu = forceDfu
  99. }
  100. if let alternativeAdvertisingNameEnabled = alternativeAdvertisingNameEnabled {
  101. dfuInitiator.alternativeAdvertisingNameEnabled = alternativeAdvertisingNameEnabled
  102. }
  103. pendingResult = result
  104. deviceAddress = address
  105. dfuController = dfuInitiator.start(targetWithIdentifier: uuid)
  106. }
  107. // MARK: DFUServiceDelegate
  108. public func dfuStateDidChange(to state: DFUState) {
  109. switch state {
  110. case .completed:
  111. sink?(["onDfuCompleted":deviceAddress])
  112. pendingResult?(deviceAddress)
  113. pendingResult = nil
  114. dfuController = nil
  115. case .disconnecting:
  116. sink?(["onDeviceDisconnecting":deviceAddress])
  117. case .aborted:
  118. sink?(["onDfuAborted": deviceAddress])
  119. pendingResult?(FlutterError(code: "DFU_ABORTED", message: "DFU ABORTED by user", details: "device address: \(deviceAddress!)"))
  120. pendingResult = nil
  121. case .connecting:
  122. sink?(["onDeviceConnecting":deviceAddress])
  123. case .starting:
  124. sink?(["onDfuProcessStarting":deviceAddress])
  125. case .enablingDfuMode:
  126. sink?(["onEnablingDfuMode":deviceAddress])
  127. case .validating:
  128. sink?(["onFirmwareValidating":deviceAddress])
  129. case .uploading:
  130. sink?(["onFirmwareUploading":deviceAddress])
  131. }
  132. }
  133. public func dfuError(_ error: DFUError, didOccurWithMessage message: String) {
  134. sink?(["onError":["deviceAddress": deviceAddress!, "error": error.rawValue, "errorType":error.rawValue, "message": message]])
  135. pendingResult?(FlutterError(code: "\(error.rawValue)", message: "DFU FAILED: \(message)", details: "Address: \(deviceAddress!), Error type \(error.rawValue)"))
  136. pendingResult = nil
  137. }
  138. //MARK: DFUProgressDelegate
  139. public func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) {
  140. sink?(["onProgressChanged":["deviceAddress": deviceAddress!, "percent": progress, "speed":currentSpeedBytesPerSecond, "avgSpeed": avgSpeedBytesPerSecond, "currentPart": part, "partsTotal": totalParts]])
  141. }
  142. //MARK: - LoggerDelegate
  143. public func logWith(_ level: LogLevel, message: String) {
  144. //print("\(level.name()): \(message)")
  145. }
  146. }