From fa5d0374caaa7d3e6881690dc91976be89de617e Mon Sep 17 00:00:00 2001 From: Julian Steenbakker Date: Thu, 5 May 2022 12:19:45 +0200 Subject: [PATCH] imp: updated event channels and callbacks --- CHANGELOG.md | 7 + .../steenbakker/nordicdfu/NordicDfuPlugin.kt | 225 +++++---- example/lib/main.dart | 70 +-- example/pubspec.yaml | 4 +- ios/Classes/SwiftNordicDfuPlugin.swift | 163 +++---- lib/nordic_dfu.dart | 430 +----------------- lib/src/android_special_paramter.dart | 51 +++ lib/src/ios_special_parameter.dart | 11 + lib/src/nordic_dfu.dart | 190 ++++++++ pubspec.yaml | 2 +- 10 files changed, 498 insertions(+), 655 deletions(-) create mode 100644 lib/src/android_special_paramter.dart create mode 100644 lib/src/ios_special_parameter.dart create mode 100644 lib/src/nordic_dfu.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b299c37..dabba95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 5.0.0 +BREAKING CHANGES: +Callback is now handled through functions in the StartDfu() method. Please see the example app for an example. + +Bugs fixed: +Fixed callback not being called on both Android and iOS. + ## 4.0.0 BREAKING CHANGES: NordiDfu now uses a Singelton! The notation changes from NordicDfu.startDfu() to NordicDfu().startDfu() diff --git a/android/src/main/kotlin/dev/steenbakker/nordicdfu/NordicDfuPlugin.kt b/android/src/main/kotlin/dev/steenbakker/nordicdfu/NordicDfuPlugin.kt index 0e847cf..f409e62 100644 --- a/android/src/main/kotlin/dev/steenbakker/nordicdfu/NordicDfuPlugin.kt +++ b/android/src/main/kotlin/dev/steenbakker/nordicdfu/NordicDfuPlugin.kt @@ -8,6 +8,7 @@ import android.os.Looper import io.flutter.FlutterInjector import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler @@ -15,86 +16,95 @@ import no.nordicsemi.android.dfu.* import no.nordicsemi.android.dfu.DfuBaseService.NOTIFICATION_ID import java.util.* -class NordicDfuPlugin : FlutterPlugin, MethodCallHandler { - /** - * hold context - */ +class NordicDfuPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler { + private var mContext: Context? = null - /** - * hold result - */ private var pendingResult: MethodChannel.Result? = null - - /** - * Method Channel - */ - private var channel: MethodChannel? = null + private var methodChannel: MethodChannel? = null + private var eventChannel: EventChannel? = null + private var sink: EventChannel.EventSink? = null private var controller: DfuServiceController? = null private var hasCreateNotification = false + override fun onAttachedToEngine(binding: FlutterPluginBinding) { mContext = binding.applicationContext - channel = MethodChannel(binding.binaryMessenger, "dev.steenbakker.nordic_dfu/method") - channel!!.setMethodCallHandler(this) + + methodChannel = MethodChannel(binding.binaryMessenger, "dev.steenbakker.nordic_dfu/method") + methodChannel!!.setMethodCallHandler(this) + + eventChannel = EventChannel(binding.binaryMessenger, "dev.steenbakker.nordic_dfu/event") + eventChannel!!.setStreamHandler(this) + DfuServiceListenerHelper.registerProgressListener(binding.applicationContext, mDfuProgressListener) } override fun onDetachedFromEngine(binding: FlutterPluginBinding) { mContext = null - channel = null + methodChannel = null + eventChannel = null } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - if (call.method == "startDfu") { - val address = call.argument("address") - val name = call.argument("name") - var filePath = call.argument("filePath") - var fileInAsset = call.argument("fileInAsset") - val forceDfu = call.argument("forceDfu") - val enableUnsafeExperimentalButtonlessServiceInSecureDfu = call.argument("enableUnsafeExperimentalButtonlessServiceInSecureDfu") - val disableNotification = call.argument("disableNotification") - val keepBond = call.argument("keepBond") - val packetReceiptNotificationsEnabled = call.argument("packetReceiptNotificationsEnabled") - val restoreBond = call.argument("restoreBond") - val startAsForegroundService = call.argument("startAsForegroundService") - val numberOfPackets = call.argument("numberOfPackets") - val enablePRNs = call.argument("enablePRNs") - if (fileInAsset == null) { - fileInAsset = false - } - if (address == null || filePath == null) { - result.error("Abnormal parameter", "address and filePath are required", null) + when (call.method) { + "startDfu" -> initiateDfu(call, result) + "abortDfu" -> abortDfu() + else -> result.notImplemented() + } + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + this.sink = events + } + + override fun onCancel(arguments: Any?) { + sink = null + } + + private fun initiateDfu(call: MethodCall, result: MethodChannel.Result) { + val address = call.argument("address") + val name = call.argument("name") + var filePath = call.argument("filePath") + var fileInAsset = call.argument("fileInAsset") + val forceDfu = call.argument("forceDfu") + val enableUnsafeExperimentalButtonlessServiceInSecureDfu = call.argument("enableUnsafeExperimentalButtonlessServiceInSecureDfu") + val disableNotification = call.argument("disableNotification") + val keepBond = call.argument("keepBond") + val packetReceiptNotificationsEnabled = call.argument("packetReceiptNotificationsEnabled") + val restoreBond = call.argument("restoreBond") + val startAsForegroundService = call.argument("startAsForegroundService") + val numberOfPackets = call.argument("numberOfPackets") + val enablePRNs = call.argument("enablePRNs") + if (fileInAsset == null) fileInAsset = false + if (address == null || filePath == null) { + result.error("Abnormal parameter", "address and filePath are required", null) + return + } + if (fileInAsset) { + val loader = FlutterInjector.instance().flutterLoader() + filePath = loader.getLookupKeyForAsset(filePath) + val tempFileName = (PathUtils.getExternalAppCachePath(mContext!!) + + UUID.randomUUID().toString()) + // copy asset file to temp path + if (!ResourceUtils.copyFileFromAssets(filePath, tempFileName, mContext!!)) { + result.error("File Error", "File not found!", "$filePath") return } - if (fileInAsset) { - val loader = FlutterInjector.instance().flutterLoader() - filePath = loader.getLookupKeyForAsset(filePath) - val tempFileName = (PathUtils.getExternalAppCachePath(mContext!!) - + UUID.randomUUID().toString()) - // copy asset file to temp path - if (!ResourceUtils.copyFileFromAssets(filePath, tempFileName, mContext!!)) { - result.error("File Error", "File not found!", "$filePath") - return - } - - // now, the path is an absolute path, and can pass it to nordic dfu libarary - filePath = tempFileName - } - pendingResult = result - startDfu(address, name, filePath, forceDfu, enableUnsafeExperimentalButtonlessServiceInSecureDfu, disableNotification, keepBond, packetReceiptNotificationsEnabled, restoreBond, startAsForegroundService, result, numberOfPackets, enablePRNs) - } else if (call.method == "abortDfu") { - if (controller != null) { - controller!!.abort() - } - } else { - result.notImplemented() + + // now, the path is an absolute path, and can pass it to nordic dfu libarary + filePath = tempFileName + } + pendingResult = result + startDfu(address, name, filePath, forceDfu, enableUnsafeExperimentalButtonlessServiceInSecureDfu, disableNotification, keepBond, packetReceiptNotificationsEnabled, restoreBond, startAsForegroundService, result, numberOfPackets, enablePRNs) + } + + private fun abortDfu() { + if (controller != null) { + controller!!.abort() } } - /** - * Start Dfu - */ private fun startDfu(address: String, name: String?, filePath: String?, forceDfu: Boolean?, enableUnsafeExperimentalButtonlessServiceInSecureDfu: Boolean?, disableNotification: Boolean?, keepBond: Boolean?, packetReceiptNotificationsEnabled: Boolean?, restoreBond: Boolean?, startAsForegroundService: Boolean?, result: MethodChannel.Result, numberOfPackets: Int?, enablePRNs: Boolean?) { val starter = DfuServiceInitiator(address) .setZip(filePath!!) @@ -113,21 +123,11 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler { if (enableUnsafeExperimentalButtonlessServiceInSecureDfu != null) { starter.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(enableUnsafeExperimentalButtonlessServiceInSecureDfu) } - if (forceDfu != null) { - starter.setForceDfu(forceDfu) - } - if (disableNotification != null) { - starter.setDisableNotification(disableNotification) - } - if (startAsForegroundService != null) { - starter.setForeground(startAsForegroundService) - } - if (keepBond != null) { - starter.setKeepBond(keepBond) - } - if (restoreBond != null) { - starter.setRestoreBond(restoreBond) - } + if (forceDfu != null) starter.setForceDfu(forceDfu) + if (disableNotification != null) starter.setDisableNotification(disableNotification) + if (startAsForegroundService != null) starter.setForeground(startAsForegroundService) + if (keepBond != null) starter.setKeepBond(keepBond) + if (restoreBond != null) starter.setRestoreBond(restoreBond) if (packetReceiptNotificationsEnabled != null) { starter.setPacketsReceiptNotificationsEnabled(packetReceiptNotificationsEnabled) } @@ -142,99 +142,98 @@ class NordicDfuPlugin : FlutterPlugin, MethodCallHandler { controller = starter.start(mContext!!, DfuService::class.java) } + private fun cancelNotification() { + // let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again. + Handler(Looper.getMainLooper()).postDelayed({ + val manager = mContext!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + manager.cancel(NOTIFICATION_ID) + }, 200) + } + private val mDfuProgressListener: DfuProgressListenerAdapter = object : DfuProgressListenerAdapter() { override fun onDeviceConnected(deviceAddress: String) { super.onDeviceConnected(deviceAddress) - channel!!.invokeMethod("onDeviceConnected", deviceAddress) + sink?.success(mapOf("onDeviceConnected" to deviceAddress)) } override fun onError(deviceAddress: String, error: Int, errorType: Int, message: String) { super.onError(deviceAddress, error, errorType, message) cancelNotification() - channel!!.invokeMethod("onError", deviceAddress) + val parameters = mutableMapOf() + parameters["deviceAddress"] = deviceAddress + parameters["error"] = error + parameters["errorType"] = errorType + parameters["message"] = message + sink?.success(mapOf("onError" to parameters)) if (pendingResult != null) { - pendingResult!!.error("2", "DFU FAILED", "device address: $deviceAddress") + pendingResult!!.error("$error", "DFU FAILED: $message", "Address: $deviceAddress, Error Type: $errorType") pendingResult = null } } override fun onDeviceConnecting(deviceAddress: String) { super.onDeviceConnecting(deviceAddress) - channel!!.invokeMethod("onDeviceConnecting", deviceAddress) + sink?.success(mapOf("onDeviceConnecting" to deviceAddress)) } override fun onDeviceDisconnected(deviceAddress: String) { super.onDeviceDisconnected(deviceAddress) - channel!!.invokeMethod("onDeviceDisconnected", deviceAddress) + sink?.success(mapOf("onDeviceDisconnected" to deviceAddress)) } override fun onDeviceDisconnecting(deviceAddress: String) { super.onDeviceDisconnecting(deviceAddress) - channel!!.invokeMethod("onDeviceDisconnecting", deviceAddress) + sink?.success(mapOf("onDeviceDisconnecting" to deviceAddress)) } override fun onDfuAborted(deviceAddress: String) { super.onDfuAborted(deviceAddress) cancelNotification() - if (pendingResult != null) { - pendingResult!!.error("2", "DFU ABORTED", "device address: $deviceAddress") - pendingResult = null - } - channel!!.invokeMethod("onDfuAborted", deviceAddress) + sink?.success(mapOf("onDfuAborted" to deviceAddress)) + pendingResult?.error("DFU_ABORTED", "DFU ABORTED by user", "device address: $deviceAddress") + pendingResult = null } override fun onDfuCompleted(deviceAddress: String) { super.onDfuCompleted(deviceAddress) cancelNotification() - if (pendingResult != null) { - pendingResult!!.success(deviceAddress) - pendingResult = null - } - channel!!.invokeMethod("onDfuCompleted", deviceAddress) + sink?.success(mapOf("onDfuCompleted" to deviceAddress)) + pendingResult?.success(deviceAddress) + pendingResult = null } override fun onDfuProcessStarted(deviceAddress: String) { super.onDfuProcessStarted(deviceAddress) - channel!!.invokeMethod("onDfuProcessStarted", deviceAddress) + sink?.success(mapOf("onDfuProcessStarted" to deviceAddress)) } override fun onDfuProcessStarting(deviceAddress: String) { super.onDfuProcessStarting(deviceAddress) - channel!!.invokeMethod("onDfuProcessStarting", deviceAddress) + sink?.success(mapOf("onDfuProcessStarting" to deviceAddress)) } override fun onEnablingDfuMode(deviceAddress: String) { super.onEnablingDfuMode(deviceAddress) - channel!!.invokeMethod("onEnablingDfuMode", deviceAddress) + sink?.success(mapOf("onEnablingDfuMode" to deviceAddress)) } override fun onFirmwareValidating(deviceAddress: String) { super.onFirmwareValidating(deviceAddress) - channel!!.invokeMethod("onFirmwareValidating", deviceAddress) + sink?.success(mapOf("onFirmwareValidating" to deviceAddress)) } override fun onProgressChanged(deviceAddress: String, percent: Int, speed: Float, avgSpeed: Float, currentPart: Int, partsTotal: Int) { super.onProgressChanged(deviceAddress, percent, speed, avgSpeed, currentPart, partsTotal) - val paras: HashMap = object : HashMap() { - init { - put("percent", percent) - put("speed", speed) - put("avgSpeed", avgSpeed) - put("currentPart", currentPart) - put("partsTotal", partsTotal) - put("deviceAddress", deviceAddress) - } - } - channel!!.invokeMethod("onProgressChanged", paras) - } - } + val parameters = mutableMapOf() + parameters["deviceAddress"] = deviceAddress + parameters["percent"] = percent + parameters["speed"] = speed + parameters["avgSpeed"] = avgSpeed + parameters["currentPart"] = currentPart + parameters["partsTotal"] = partsTotal - private fun cancelNotification() { - // let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again. - Handler(Looper.getMainLooper()).postDelayed({ - val manager = mContext!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - manager.cancel(NOTIFICATION_ID) - }, 200) + sink?.success(mapOf("onProgressChanged" to parameters)) + } } } \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 9f69dba..381c445 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,18 +30,22 @@ class _MyAppState extends State { deviceId, 'assets/file.zip', fileInAsset: true, - progressListener: DefaultDfuProgressListenerAdapter( - onProgressChangedHandle: ( - deviceAddress, - percent, - speed, - avgSpeed, - currentPart, - partsTotal, - ) { - debugPrint('deviceAddress: $deviceAddress, percent: $percent'); - }, - ), + onDeviceDisconnecting: (string) { + debugPrint('deviceAddress: $string'); + }, + // onErrorHandle: (string) { + // debugPrint('deviceAddress: $string'); + // }, + onProgressChanged: ( + deviceAddress, + percent, + speed, + avgSpeed, + currentPart, + partsTotal, + ) { + debugPrint('deviceAddress: $deviceAddress, percent: $percent'); + }, ); debugPrint(s); dfuRunning = false; @@ -148,27 +152,27 @@ class _MyAppState extends State { } } -class ProgressListenerListener extends DfuProgressListenerAdapter { - @override - void onProgressChanged( - String? deviceAddress, - int? percent, - double? speed, - double? avgSpeed, - int? currentPart, - int? partsTotal, - ) { - super.onProgressChanged( - deviceAddress, - percent, - speed, - avgSpeed, - currentPart, - partsTotal, - ); - debugPrint('deviceAddress: $deviceAddress, percent: $percent'); - } -} +// class ProgressListenerListener extends DfuProgressListenerAdapter { +// @override +// void onProgressChanged( +// String? deviceAddress, +// int? percent, +// double? speed, +// double? avgSpeed, +// int? currentPart, +// int? partsTotal, +// ) { +// super.onProgressChanged( +// deviceAddress, +// percent, +// speed, +// avgSpeed, +// currentPart, +// partsTotal, +// ); +// debugPrint('deviceAddress: $deviceAddress, percent: $percent'); +// } +// } class DeviceItem extends StatelessWidget { final ScanResult scanResult; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5bad1da..ac01e44 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -23,5 +23,5 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: -# assets: -# - assets/file.zip + assets: + - assets/file.zip diff --git a/ios/Classes/SwiftNordicDfuPlugin.swift b/ios/Classes/SwiftNordicDfuPlugin.swift index f53c2f0..bb5d133 100644 --- a/ios/Classes/SwiftNordicDfuPlugin.swift +++ b/ios/Classes/SwiftNordicDfuPlugin.swift @@ -3,67 +3,91 @@ import UIKit import iOSDFULibrary import CoreBluetooth -public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, DFUServiceDelegate, DFUProgressDelegate, LoggerDelegate { +public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, DFUServiceDelegate, DFUProgressDelegate, LoggerDelegate { let registrar: FlutterPluginRegistrar - let channel: FlutterMethodChannel + var sink: FlutterEventSink! var pendingResult: FlutterResult? var deviceAddress: String? - private var dfuController : DFUServiceController! + private var dfuController : DFUServiceController! - init(_ registrar: FlutterPluginRegistrar, _ channel: FlutterMethodChannel) { + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = SwiftNordicDfuPlugin(registrar) + + let method = FlutterMethodChannel(name: "dev.steenbakker.nordic_dfu/method", binaryMessenger: registrar.messenger()) + + let event = FlutterEventChannel(name: + "dev.steenbakker.nordic_dfu/event", binaryMessenger: registrar.messenger()) + + registrar.addMethodCallDelegate(instance, channel: method) + event.setStreamHandler(instance) + } + + init(_ registrar: FlutterPluginRegistrar) { self.registrar = registrar - self.channel = channel + super.init() + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "startDfu": initializeDfu(call, result) + case "abortDfu" : abortDfu() + default: result(FlutterMethodNotImplemented) + } } - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "dev.steenbakker.nordic_dfu/method", binaryMessenger: registrar.messenger()) - let instance = SwiftNordicDfuPlugin(registrar, channel) - registrar.addMethodCallDelegate(instance, channel: channel) + public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + sink = events + return nil } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - if (call.method == "startDfu") { - guard let arguments = call.arguments as? Dictionary else { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) + public func onCancel(withArguments arguments: Any?) -> FlutterError? { + sink = nil + return nil + } + + private func abortDfu() { + _ = dfuController?.abort() + dfuController = nil + } + + private func initializeDfu(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { + guard let arguments = call.arguments as? Dictionary else { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) + return + } + let name = arguments["name"] as? String + guard let address = arguments["address"] as? String, + var filePath = arguments["filePath"] as? String else { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "address and filePath are required", details: nil)) + return + } + + let forceDfu = arguments["forceDfu"] as? Bool + + let enableUnsafeExperimentalButtonlessServiceInSecureDfu = arguments["enableUnsafeExperimentalButtonlessServiceInSecureDfu"] as? Bool + + let fileInAsset = (arguments["fileInAsset"] as? Bool) ?? false + + if (fileInAsset) { + let key = registrar.lookupKey(forAsset: filePath) + guard let pathInAsset = Bundle.main.path(forResource: key, ofType: nil) else { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "file in asset not found \(filePath)", details: nil)) return } - let name = arguments["name"] as? String - guard let address = arguments["address"] as? String, - var filePath = arguments["filePath"] as? String else { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "address and filePath are required", details: nil)) - return - } - - let forceDfu = arguments["forceDfu"] as? Bool - - let enableUnsafeExperimentalButtonlessServiceInSecureDfu = arguments["enableUnsafeExperimentalButtonlessServiceInSecureDfu"] as? Bool - - let fileInAsset = (arguments["fileInAsset"] as? Bool) ?? false - - if (fileInAsset) { - let key = registrar.lookupKey(forAsset: filePath) - guard let pathInAsset = Bundle.main.path(forResource: key, ofType: nil) else { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "file in asset not found \(filePath)", details: nil)) - return - } - - filePath = pathInAsset - } - - let alternativeAdvertisingNameEnabled = arguments["alternativeAdvertisingNameEnabled"] as? Bool - startDfu(address, - name: name, - filePath: filePath, - forceDfu: forceDfu, - enableUnsafeExperimentalButtonlessServiceInSecureDfu: enableUnsafeExperimentalButtonlessServiceInSecureDfu, - alternativeAdvertisingNameEnabled: alternativeAdvertisingNameEnabled, - result: result) - } else if (call.method == "abortDfu") { - _ = dfuController?.abort() - dfuController = nil + filePath = pathInAsset } + + let alternativeAdvertisingNameEnabled = arguments["alternativeAdvertisingNameEnabled"] as? Bool + + startDfu(address, + name: name, + filePath: filePath, + forceDfu: forceDfu, + enableUnsafeExperimentalButtonlessServiceInSecureDfu: enableUnsafeExperimentalButtonlessServiceInSecureDfu, + alternativeAdvertisingNameEnabled: alternativeAdvertisingNameEnabled, + result: result) } private func startDfu( @@ -106,63 +130,44 @@ public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, DFUServiceDelegate, deviceAddress = address dfuController = dfuInitiator.start(targetWithIdentifier: uuid) - print("dfuInitiator have start") } - //MARK: DFUServiceDelegate +// MARK: DFUServiceDelegate public func dfuStateDidChange(to state: DFUState) { switch state { case .completed: + sink?(["onDfuCompleted":deviceAddress]) pendingResult?(deviceAddress) pendingResult = nil - print("\(deviceAddress!) onDfuCompleted") dfuController = nil - channel.invokeMethod("onDfuCompleted", arguments: deviceAddress) case .disconnecting: - print("\(deviceAddress!) onDeviceDisconnecting") - channel.invokeMethod("onDeviceDisconnecting", arguments: deviceAddress) + sink?(["onDeviceDisconnecting":deviceAddress]) case .aborted: - pendingResult?(FlutterError(code: "DFU_ABORRED", message: "Device address: \(deviceAddress!)", details: nil)) + sink?(["onDfuAborted": deviceAddress]) + pendingResult?(FlutterError(code: "DFU_ABORTED", message: "DFU ABORTED by user", details: "device address: \(deviceAddress!)")) pendingResult = nil - print("\(deviceAddress!) onDfuAborted") - channel.invokeMethod("onDfuAborted", arguments: deviceAddress) case .connecting: - print("\(deviceAddress!) onDeviceConnecting") - channel.invokeMethod("onDeviceConnecting", arguments: deviceAddress) + sink?(["onDeviceConnecting":deviceAddress]) case .starting: - print("\(deviceAddress!) onDfuProcessStarting") - channel.invokeMethod("onDfuProcessStarting", arguments: deviceAddress) + sink?(["onDfuProcessStarting":deviceAddress]) case .enablingDfuMode: - print("\(deviceAddress!) onEnablingDfuMode") - channel.invokeMethod("onEnablingDfuMode", arguments: deviceAddress) + sink?(["onEnablingDfuMode":deviceAddress]) case .validating: - print("\(deviceAddress!) onFirmwareValidating") - channel.invokeMethod("onFirmwareValidating", arguments: deviceAddress) + sink?(["onFirmwareValidating":deviceAddress]) case .uploading: - print("\(deviceAddress!) onFirmwareUploading") - channel.invokeMethod("onFirmwareUploading", arguments: deviceAddress) + sink?(["onFirmwareUploading":deviceAddress]) } } public func dfuError(_ error: DFUError, didOccurWithMessage message: String) { - print("\(deviceAddress!) onError, message : \(message)") - channel.invokeMethod("onError", arguments: deviceAddress) - - pendingResult?(FlutterError(code: "DFU_FAILED", message: "Device address: \(deviceAddress!)", details: nil)) + sink?(["onError":["deviceAddress": deviceAddress!, "error": error.rawValue, "errorType":error.rawValue, "message": message]]) + pendingResult?(FlutterError(code: "\(error.rawValue)", message: "DFU FAILED: \(message)", details: "Address: \(deviceAddress!), Error type \(error.rawValue)")) pendingResult = nil } //MARK: DFUProgressDelegate public func dfuProgressDidChange(for part: Int, outOf totalParts: Int, to progress: Int, currentSpeedBytesPerSecond: Double, avgSpeedBytesPerSecond: Double) { - print("onProgressChanged: \(progress)") - channel.invokeMethod("onProgressChanged", arguments: [ - "percent": progress, - "speed": currentSpeedBytesPerSecond, - "avgSpeed": avgSpeedBytesPerSecond, - "currentPart": part, - "partsTotal": totalParts, - "deviceAddress": deviceAddress! - ]) + sink?(["onProgressChanged":["deviceAddress": deviceAddress!, "percent": progress, "speed":currentSpeedBytesPerSecond, "avgSpeed": avgSpeedBytesPerSecond, "currentPart": part, "partsTotal": totalParts]]) } //MARK: - LoggerDelegate diff --git a/lib/nordic_dfu.dart b/lib/nordic_dfu.dart index 6271283..75692a7 100644 --- a/lib/nordic_dfu.dart +++ b/lib/nordic_dfu.dart @@ -1,427 +1,3 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; - -/// Some parameter just use in Android -/// All this parameters can see in -class AndroidSpecialParameter { - ///Sets whether the progress notification in the status bar should be disabled. - ///Defaults to false. - final bool? disableNotification; - - /// - /// Sets whether the DFU service should be started as a foreground service. By default it's - /// true. According to - /// - /// https://developer.android.com/about/versions/oreo/background.html - /// the background service may be killed by the system on Android Oreo after user quits the - /// application so it is recommended to keep it as a foreground service (default) at least on - /// Android Oreo+. - /// - final bool? startAsForegroundService; - - /// Sets whether the bond information should be preserver after flashing new application. - /// This feature requires DFU Bootloader version 0.6 or newer (SDK 8.0.0+). - /// Please see the {@link DfuBaseService#EXTRA_KEEP_BOND} for more information regarding - /// requirements. Remember that currently updating the Soft Device will remove the bond - /// information. - /// - /// This flag is ignored when Secure DFU Buttonless Service is used. It will keep or remove the - /// bond depending on the Buttonless service type. - /// - final bool? keepBond; - - /// Sets whether the bond should be created after the DFU is complete. - /// Please see the {@link DfuBaseService#EXTRA_RESTORE_BOND} for more information regarding - /// requirements. - /// - /// This flag is ignored when Secure DFU Buttonless Service is used. It will keep or will not - /// restore the bond depending on the Buttonless service type. - final bool? restoreBond; - - /// Enables or disables the Packet Receipt Notification (PRN) procedure. - /// - /// By default the PRNs are disabled on devices with Android Marshmallow or newer and enabled on - /// older ones. - final bool? packetReceiptNotificationsEnabled; - - const AndroidSpecialParameter({ - this.disableNotification, - this.keepBond, - this.packetReceiptNotificationsEnabled, - this.restoreBond, - this.startAsForegroundService, - }); -} - -/// Some parameter just use in iOS -/// All this parameters can see in -class IosSpecialParameter { - ///Sets whether to send unique name to device before it is switched into bootloader mode - ///Defaults to true. - final bool? alternativeAdvertisingNameEnabled; - - const IosSpecialParameter({ - this.alternativeAdvertisingNameEnabled, - }); -} - -/// This singleton handles the DFU process. -class NordicDfu { - static final NordicDfu _singleton = NordicDfu._internal(); - - factory NordicDfu() { - return _singleton; - } - - NordicDfu._internal(); - - static const String namespace = 'dev.steenbakker.nordic_dfu'; - static const MethodChannel _channel = MethodChannel('$namespace/method'); - - /// Start dfu handle - /// [address] android: mac address iOS: device uuid - /// [filePath] zip file path - /// [name] device name - /// [progressListener] Dfu progress listener, You can use [DefaultDfuProgressListenerAdapter] - /// [fileInAsset] if [filePath] is a asset path like 'asset/file.zip', must set this value to true, else false - /// [forceDfu] Legacy DFU only, see in nordic library, default is false - /// [enableUnsafeExperimentalButtonlessServiceInSecureDfu] see in nordic library, default is false - /// [androidSpecialParameter] this parameters is only used by android lib - /// [iosSpecialParameter] this parameters is only used by ios lib - Future startDfu( - String address, - String filePath, { - String? name, - DfuProgressListenerAdapter? progressListener, - bool? fileInAsset, - bool? forceDfu, - bool? enablePRNs, - int? numberOfPackets, - bool? enableUnsafeExperimentalButtonlessServiceInSecureDfu, - AndroidSpecialParameter androidSpecialParameter = - const AndroidSpecialParameter(), - IosSpecialParameter iosSpecialParameter = const IosSpecialParameter(), - }) async { - _channel.setMethodCallHandler((MethodCall call) { - switch (call.method) { - case 'onDeviceConnected': - progressListener?.onDeviceConnected(call.arguments as String?); - break; - case 'onDeviceConnecting': - progressListener?.onDeviceConnecting(call.arguments as String?); - break; - case 'onDeviceDisconnected': - progressListener?.onDeviceDisconnected(call.arguments as String?); - break; - case 'onDeviceDisconnecting': - progressListener?.onDeviceDisconnecting(call.arguments as String?); - break; - case 'onDfuAborted': - progressListener?.onDfuAborted(call.arguments as String?); - break; - case 'onDfuCompleted': - progressListener?.onDfuCompleted(call.arguments as String?); - break; - case 'onDfuProcessStarted': - progressListener?.onDfuProcessStarted(call.arguments as String?); - break; - case 'onDfuProcessStarting': - progressListener?.onDfuProcessStarting(call.arguments as String?); - break; - case 'onEnablingDfuMode': - progressListener?.onEnablingDfuMode(call.arguments as String?); - break; - case 'onFirmwareValidating': - progressListener?.onFirmwareValidating(call.arguments as String?); - break; - case 'onError': - final Map result = - call.arguments as Map; - progressListener?.onError( - result['deviceAddress'] as String?, - result['error'] as int?, - result['errorType'] as int?, - result['message'] as String?, - ); - break; - case 'onProgressChanged': - final Map result = - call.arguments as Map; - progressListener?.onProgressChanged( - result['deviceAddress'] as String?, - result['percent'] as int?, - result['speed'] as double?, - result['avgSpeed'] as double?, - result['currentPart'] as int?, - result['partsTotal'] as int?, - ); - break; - default: - throw UnimplementedError(); - } - throw UnimplementedError(); - }); - - return _channel.invokeMethod('startDfu', { - 'address': address, - 'filePath': filePath, - 'name': name, - 'fileInAsset': fileInAsset, - 'forceDfu': forceDfu, - 'enablePRNs': enablePRNs, - 'numberOfPackets': numberOfPackets, - 'enableUnsafeExperimentalButtonlessServiceInSecureDfu': - enableUnsafeExperimentalButtonlessServiceInSecureDfu, - 'disableNotification': androidSpecialParameter.disableNotification, - 'keepBond': androidSpecialParameter.keepBond, - 'restoreBond': androidSpecialParameter.restoreBond, - 'packetReceiptNotificationsEnabled': - androidSpecialParameter.packetReceiptNotificationsEnabled, - 'startAsForegroundService': - androidSpecialParameter.startAsForegroundService, - 'alternativeAdvertisingNameEnabled': - iosSpecialParameter.alternativeAdvertisingNameEnabled, - }); - } - - Future abortDfu() async { - return _channel.invokeMethod('abortDfu'); - } -} - -abstract class DfuProgressListenerAdapter { - /// Callback for when device is connected - /// [deviceAddress] Device connected to - void onDeviceConnected(String? deviceAddress) {} - - /// Callback for when device is connecting - /// [deviceAddress] Device connecting to - void onDeviceConnecting(String? deviceAddress) {} - - /// Callback for when device is disconnected - /// [deviceAddress] Device disconnected from - void onDeviceDisconnected(String? deviceAddress) {} - - /// Callback for when device is disconnecting - /// [deviceAddress] Device disconnecting from - void onDeviceDisconnecting(String? deviceAddress) {} - - /// Callback for dfu is Aborted - /// [deviceAddress] Device aborted from - void onDfuAborted(String? deviceAddress) {} - - /// Callback for when dfu is completed - /// [deviceAddress] Device - void onDfuCompleted(String? deviceAddress) {} - - /// Callback for when dfu has been started - /// [deviceAddress] Device with dfu - void onDfuProcessStarted(String? deviceAddress) {} - - /// Callback for when dfu is starting - /// [deviceAddress] Device with dfu - void onDfuProcessStarting(String? deviceAddress) {} - - /// Callback for when dfu mode is being enabled - /// [deviceAddress] Device with dfu - void onEnablingDfuMode(String? deviceAddress) {} - - /// Callback for when dfu is being verified - /// [deviceAddress] Device from which dfu is being verified - void onFirmwareValidating(String? deviceAddress) {} - - /// Callback for when dfu has error - /// [deviceAddress] Device with error - void onError( - String? deviceAddress, - int? error, - int? errorType, - String? message, - ) {} - - /// Callback for when the dfu progress has changed - /// [deviceAddress] Device with dfu - /// [percent] Percentage dfu completed - /// [speed] Speed of the dfu proces - /// [avgSpeed] Average speed of the dfu process - /// [currentPart] Current part being uploaded - /// [partsTotal] All parts that need to be uploaded - void onProgressChanged( - String? deviceAddress, - int? percent, - double? speed, - double? avgSpeed, - int? currentPart, - int? partsTotal, - ) {} -} - -class DefaultDfuProgressListenerAdapter extends DfuProgressListenerAdapter { - void Function(String? deviceAddress)? onDeviceConnectedHandle; - - void Function(String? deviceAddress)? onDeviceConnectingHandle; - - void Function(String? deviceAddress)? onDeviceDisconnectedHandle; - - void Function(String? deviceAddress)? onDeviceDisconnectingHandle; - - void Function(String? deviceAddress)? onDfuAbortedHandle; - - void Function(String? deviceAddress)? onDfuCompletedHandle; - - void Function(String? deviceAddress)? onDfuProcessStartedHandle; - - void Function(String? deviceAddress)? onDfuProcessStartingHandle; - - void Function(String? deviceAddress)? onEnablingDfuModeHandle; - - void Function(String? deviceAddress)? onFirmwareValidatingHandle; - - void Function( - String? deviceAddress, - int? error, - int? errorType, - String? message, - )? onErrorHandle; - - void Function( - String? deviceAddress, - int? percent, - double? speed, - double? avgSpeed, - int? currentPart, - int? partsTotal, - )? onProgressChangedHandle; - - DefaultDfuProgressListenerAdapter({ - this.onDeviceConnectedHandle, - this.onDeviceConnectingHandle, - this.onDeviceDisconnectedHandle, - this.onDeviceDisconnectingHandle, - this.onDfuAbortedHandle, - this.onDfuCompletedHandle, - this.onDfuProcessStartedHandle, - this.onDfuProcessStartingHandle, - this.onEnablingDfuModeHandle, - this.onFirmwareValidatingHandle, - this.onErrorHandle, - this.onProgressChangedHandle, - }); - - @override - void onDeviceConnected(String? deviceAddress) { - super.onDeviceConnected(deviceAddress); - - onDeviceConnectedHandle?.call(deviceAddress); - } - - @override - void onDeviceConnecting(String? deviceAddress) { - super.onDeviceConnecting(deviceAddress); - onDeviceConnectingHandle?.call(deviceAddress); - } - - @override - void onDeviceDisconnected(String? deviceAddress) { - super.onDeviceDisconnected(deviceAddress); - - onDeviceDisconnectedHandle?.call(deviceAddress); - } - - @override - void onDeviceDisconnecting(String? deviceAddress) { - super.onDeviceDisconnecting(deviceAddress); - onDeviceDisconnectingHandle?.call(deviceAddress); - } - - @override - void onDfuAborted(String? deviceAddress) { - super.onDfuAborted(deviceAddress); - onDfuAbortedHandle?.call(deviceAddress); - } - - @override - void onDfuCompleted(String? deviceAddress) { - super.onDfuCompleted(deviceAddress); - - onDfuCompletedHandle?.call(deviceAddress); - } - - @override - void onDfuProcessStarted(String? deviceAddress) { - super.onDfuProcessStarted(deviceAddress); - onDfuProcessStartedHandle?.call(deviceAddress); - } - - @override - void onDfuProcessStarting(String? deviceAddress) { - super.onDfuProcessStarting(deviceAddress); - - onDfuProcessStartingHandle?.call(deviceAddress); - } - - @override - void onEnablingDfuMode(String? deviceAddress) { - super.onEnablingDfuMode(deviceAddress); - - onEnablingDfuModeHandle?.call(deviceAddress); - } - - @override - void onFirmwareValidating(String? deviceAddress) { - super.onFirmwareValidating(deviceAddress); - - onFirmwareValidatingHandle?.call(deviceAddress); - } - - @override - void onError( - String? deviceAddress, - int? error, - int? errorType, - String? message, - ) { - super.onError( - deviceAddress, - error, - errorType, - message, - ); - - onErrorHandle?.call( - deviceAddress, - error, - errorType, - message, - ); - } - - @override - void onProgressChanged( - String? deviceAddress, - int? percent, - double? speed, - double? avgSpeed, - int? currentPart, - int? partsTotal, - ) { - super.onProgressChanged( - deviceAddress, - percent, - speed, - avgSpeed, - currentPart, - partsTotal, - ); - - onProgressChangedHandle?.call( - deviceAddress, - percent, - speed, - avgSpeed, - currentPart, - partsTotal, - ); - } -} +export 'package:nordic_dfu/src/android_special_paramter.dart'; +export 'package:nordic_dfu/src/ios_special_parameter.dart'; +export 'package:nordic_dfu/src/nordic_dfu.dart'; diff --git a/lib/src/android_special_paramter.dart b/lib/src/android_special_paramter.dart new file mode 100644 index 0000000..9221e37 --- /dev/null +++ b/lib/src/android_special_paramter.dart @@ -0,0 +1,51 @@ +/// Some parameter just use in Android +/// All this parameters can see in +class AndroidSpecialParameter { + ///Sets whether the progress notification in the status bar should be disabled. + ///Defaults to false. + final bool? disableNotification; + + /// + /// Sets whether the DFU service should be started as a foreground service. By default it's + /// true. According to + /// + /// https://developer.android.com/about/versions/oreo/background.html + /// the background service may be killed by the system on Android Oreo after user quits the + /// application so it is recommended to keep it as a foreground service (default) at least on + /// Android Oreo+. + /// + final bool? startAsForegroundService; + + /// Sets whether the bond information should be preserver after flashing new application. + /// This feature requires DFU Bootloader version 0.6 or newer (SDK 8.0.0+). + /// Please see the {@link DfuBaseService#EXTRA_KEEP_BOND} for more information regarding + /// requirements. Remember that currently updating the Soft Device will remove the bond + /// information. + /// + /// This flag is ignored when Secure DFU Buttonless Service is used. It will keep or remove the + /// bond depending on the Buttonless service type. + /// + final bool? keepBond; + + /// Sets whether the bond should be created after the DFU is complete. + /// Please see the {@link DfuBaseService#EXTRA_RESTORE_BOND} for more information regarding + /// requirements. + /// + /// This flag is ignored when Secure DFU Buttonless Service is used. It will keep or will not + /// restore the bond depending on the Buttonless service type. + final bool? restoreBond; + + /// Enables or disables the Packet Receipt Notification (PRN) procedure. + /// + /// By default the PRNs are disabled on devices with Android Marshmallow or newer and enabled on + /// older ones. + final bool? packetReceiptNotificationsEnabled; + + const AndroidSpecialParameter({ + this.disableNotification, + this.keepBond, + this.packetReceiptNotificationsEnabled, + this.restoreBond, + this.startAsForegroundService, + }); +} diff --git a/lib/src/ios_special_parameter.dart b/lib/src/ios_special_parameter.dart new file mode 100644 index 0000000..ffe8146 --- /dev/null +++ b/lib/src/ios_special_parameter.dart @@ -0,0 +1,11 @@ +/// Some parameter just use in iOS +/// All this parameters can see in +class IosSpecialParameter { + ///Sets whether to send unique name to device before it is switched into bootloader mode + ///Defaults to true. + final bool? alternativeAdvertisingNameEnabled; + + const IosSpecialParameter({ + this.alternativeAdvertisingNameEnabled, + }); +} diff --git a/lib/src/nordic_dfu.dart b/lib/src/nordic_dfu.dart new file mode 100644 index 0000000..2bfdec8 --- /dev/null +++ b/lib/src/nordic_dfu.dart @@ -0,0 +1,190 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:nordic_dfu/src/android_special_paramter.dart'; +import 'package:nordic_dfu/src/ios_special_parameter.dart'; + +/// Callback for when dfu status has changed +/// [address] Device with error +typedef DfuCallback = void Function(String address); + +/// Callback for when dfu has error +/// [address] Device with error +/// [error] Error which occurs +/// [errorType] Error type which has occured +/// [message] Message that has been thrown with error +typedef DfuErrorCallback = void Function( + String address, + int error, + int errorType, + String message, +); + +/// Callback for when the dfu progress has changed +/// [address] Device with dfu +/// [percent] Percentage dfu completed +/// [speed] Speed of the dfu proces +/// [avgSpeed] Average speed of the dfu process +/// [currentPart] Current part being uploaded +/// [partsTotal] All parts that need to be uploaded +typedef DfuProgressCallback = void Function( + String address, + int percent, + double speed, + double avgSpeed, + int currentPart, + int totalParts, +); + +/// This singleton handles the DFU process. +class NordicDfu { + static final NordicDfu _singleton = NordicDfu._internal(); + + factory NordicDfu() { + return _singleton; + } + + NordicDfu._internal(); + + static const String namespace = 'dev.steenbakker.nordic_dfu'; + static const MethodChannel _methodChannel = + MethodChannel('$namespace/method'); + static const EventChannel _eventChannel = EventChannel('$namespace/event'); + StreamSubscription? events; + + /// Start dfu handle + /// [address] android: mac address iOS: device uuid + /// [filePath] zip file path + /// [name] device name + /// [progressListener] Dfu progress listener, You can use [DefaultDfuProgressListenerAdapter] + /// [fileInAsset] if [filePath] is a asset path like 'asset/file.zip', must set this value to true, else false + /// [forceDfu] Legacy DFU only, see in nordic library, default is false + /// [enableUnsafeExperimentalButtonlessServiceInSecureDfu] see in nordic library, default is false + /// [androidSpecialParameter] this parameters is only used by android lib + /// [iosSpecialParameter] this parameters is only used by ios lib + /// [onDeviceConnected] Callback for when device is connected + /// [onDeviceConnecting] Callback for when device is connecting + /// [onDeviceDisconnected] Callback for when device is disconnected + /// [onDeviceDisconnecting] Callback for when device is disconnecting + /// [onDfuAborted] Callback for dfu is Aborted + /// [onDfuCompleted] Callback for when dfu is completed + /// [onDfuProcessStarted] Callback for when dfu has been started + /// [onDfuProcessStarting] Callback for when dfu is starting + /// [onEnablingDfuMode] Callback for when dfu mode is being enabled + /// [onFirmwareValidating] Callback for when dfu is being verified + /// [onError] Callback for when dfu has error + /// [onProgressChanged] Callback for when the dfu progress has changed + Future startDfu( + String address, + String filePath, { + String? name, + bool? fileInAsset, + bool? forceDfu, + bool? enablePRNs, + int? numberOfPackets, + bool? enableUnsafeExperimentalButtonlessServiceInSecureDfu, + AndroidSpecialParameter androidSpecialParameter = + const AndroidSpecialParameter(), + IosSpecialParameter iosSpecialParameter = const IosSpecialParameter(), + DfuCallback? onDeviceConnected, + DfuCallback? onDeviceConnecting, + DfuCallback? onDeviceDisconnected, + DfuCallback? onDeviceDisconnecting, + DfuCallback? onDfuAborted, + DfuCallback? onDfuCompleted, + DfuCallback? onDfuProcessStarted, + DfuCallback? onDfuProcessStarting, + DfuCallback? onEnablingDfuMode, + DfuCallback? onFirmwareValidating, + DfuErrorCallback? onError, + DfuProgressCallback? onProgressChanged, + }) async { + events = _eventChannel.receiveBroadcastStream().listen((data) { + data as Map; + for (final key in data.keys) { + switch (key) { + case 'onDeviceConnected': + onDeviceConnected?.call(data[key] as String); + break; + case 'onDeviceConnecting': + onDeviceConnecting?.call(data[key] as String); + break; + case 'onDeviceDisconnected': + onDeviceDisconnected?.call(data[key] as String); + break; + case 'onDeviceDisconnecting': + onDeviceDisconnecting?.call(data[key] as String); + break; + case 'onDfuAborted': + onDfuAborted?.call(data[key] as String); + events?.cancel(); + break; + case 'onDfuCompleted': + onDfuCompleted?.call(data[key] as String); + events?.cancel(); + break; + case 'onDfuProcessStarted': + onDfuProcessStarted?.call(data[key] as String); + break; + case 'onDfuProcessStarting': + onDfuProcessStarting?.call(data[key] as String); + break; + case 'onEnablingDfuMode': + onEnablingDfuMode?.call(data[key] as String); + break; + case 'onFirmwareValidating': + onFirmwareValidating?.call(data[key] as String); + break; + case 'onError': + final Map result = + Map.from(data[key] as Map); + onError?.call( + result['deviceAddress'] as String, + result['error'] as int, + result['errorType'] as int, + result['message'] as String, + ); + events?.cancel(); + break; + case 'onProgressChanged': + final Map result = + Map.from(data[key] as Map); + onProgressChanged?.call( + result['deviceAddress'] as String, + result['percent'] as int, + result['speed'] as double, + result['avgSpeed'] as double, + result['currentPart'] as int, + result['partsTotal'] as int, + ); + break; + } + } + }); + + return _methodChannel.invokeMethod('startDfu', { + 'address': address, + 'filePath': filePath, + 'name': name, + 'fileInAsset': fileInAsset, + 'forceDfu': forceDfu, + 'enablePRNs': enablePRNs, + 'numberOfPackets': numberOfPackets, + 'enableUnsafeExperimentalButtonlessServiceInSecureDfu': + enableUnsafeExperimentalButtonlessServiceInSecureDfu, + 'disableNotification': androidSpecialParameter.disableNotification, + 'keepBond': androidSpecialParameter.keepBond, + 'restoreBond': androidSpecialParameter.restoreBond, + 'packetReceiptNotificationsEnabled': + androidSpecialParameter.packetReceiptNotificationsEnabled, + 'startAsForegroundService': + androidSpecialParameter.startAsForegroundService, + 'alternativeAdvertisingNameEnabled': + iosSpecialParameter.alternativeAdvertisingNameEnabled, + }); + } + + Future abortDfu() async { + return _methodChannel.invokeMethod('abortDfu'); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 0854a22..0240df2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: nordic_dfu description: This library allows you to do a Device Firmware Update (DFU) of your nrf51 or nrf52 chip from Nordic Semiconductor. Fork of flutter-nordic-dfu. -version: 4.0.0 +version: 5.0.0 homepage: https://github.com/juliansteenbakker/nordic_dfu environment: