From 7b97b63f5cc52883dd962e29645cc7e6d6aad098 Mon Sep 17 00:00:00 2001 From: Julian Steenbakker Date: Thu, 5 Jan 2023 16:25:30 +0100 Subject: [PATCH] imp: add parameters --- CHANGELOG.md | 3 + ios/Classes/SwiftNordicDfuPlugin.swift | 79 ++++++++++++--------- lib/src/android_special_paramter.dart | 14 +++- lib/src/ios_special_parameter.dart | 95 ++++++++++++++++++++++++-- lib/src/nordic_dfu.dart | 24 +++---- 5 files changed, 162 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cfc7a..541e496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 5.3.0 +[Android] Add dataDelay and numberOfRetries parameters to androidSpecialParameter. + ## 5.2.1 [Android] revert kotlin 1.8.0 to 1.7.10 due to compatibility issues. diff --git a/ios/Classes/SwiftNordicDfuPlugin.swift b/ios/Classes/SwiftNordicDfuPlugin.swift index 5c87697..be3ca5e 100644 --- a/ios/Classes/SwiftNordicDfuPlugin.swift +++ b/ios/Classes/SwiftNordicDfuPlugin.swift @@ -64,9 +64,15 @@ public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, FlutterStreamHandler } let forceDfu = arguments["forceDfu"] as? Bool - + let forceScanningForNewAddressInLegacyDfu = arguments["forceScanningForNewAddressInLegacyDfu"] as? Bool + let packetReceiptNotificationParameter = arguments["packetReceiptNotificationParameter"] as? UInt16 + let connectionTimeout = arguments["connectionTimeout"] as? TimeInterval + let dataObjectPreparationDelay = arguments["dataObjectPreparationDelay"] as? TimeInterval + let alternativeAdvertisingNameEnabled = arguments["alternativeAdvertisingNameEnabled"] as? Bool + let alternativeAdvertisingName = arguments["alternativeAdvertisingName"] as? String let enableUnsafeExperimentalButtonlessServiceInSecureDfu = arguments["enableUnsafeExperimentalButtonlessServiceInSecureDfu"] as? Bool - + let disableResume = arguments["disableResume"] as? Bool + let fileInAsset = (arguments["fileInAsset"] as? Bool) ?? false if (fileInAsset) { @@ -79,14 +85,18 @@ public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, FlutterStreamHandler filePath = pathInAsset } - let alternativeAdvertisingNameEnabled = arguments["alternativeAdvertisingNameEnabled"] as? Bool - startDfu(address, name: name, filePath: filePath, + packetReceiptNotificationParameter: packetReceiptNotificationParameter, forceDfu: forceDfu, - enableUnsafeExperimentalButtonlessServiceInSecureDfu: enableUnsafeExperimentalButtonlessServiceInSecureDfu, + forceScanningForNewAddressInLegacyDfu: forceScanningForNewAddressInLegacyDfu, + connectionTimeout: connectionTimeout, + dataObjectPreparationDelay: dataObjectPreparationDelay, alternativeAdvertisingNameEnabled: alternativeAdvertisingNameEnabled, + alternativeAdvertisingName: alternativeAdvertisingName, + enableUnsafeExperimentalButtonlessServiceInSecureDfu: enableUnsafeExperimentalButtonlessServiceInSecureDfu, + disableResume: disableResume, result: result) } @@ -94,42 +104,45 @@ public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, FlutterStreamHandler _ address: String, name: String?, filePath: String, + packetReceiptNotificationParameter: UInt16?, forceDfu: Bool?, - enableUnsafeExperimentalButtonlessServiceInSecureDfu: Bool?, + forceScanningForNewAddressInLegacyDfu: Bool?, + connectionTimeout: TimeInterval?, + dataObjectPreparationDelay: TimeInterval?, alternativeAdvertisingNameEnabled: Bool?, + alternativeAdvertisingName: String?, + enableUnsafeExperimentalButtonlessServiceInSecureDfu: Bool?, + disableResume: Bool?, result: @escaping FlutterResult) { guard let uuid = UUID(uuidString: address) else { result(FlutterError(code: "DEVICE_ADDRESS_ERROR", message: "Device address conver to uuid failed", details: "Device uuid \(address) convert to uuid failed")) return } - do{ - let firmware = try DFUFirmware(urlToZipFile: URL(fileURLWithPath: filePath)) + do { + let firmware = try DFUFirmware(urlToZipFile: URL(fileURLWithPath: filePath)) - - - let dfuInitiator = DFUServiceInitiator(queue: nil) - .with(firmware: firmware); - dfuInitiator.delegate = self - dfuInitiator.progressDelegate = self - dfuInitiator.logger = self - - if let enableUnsafeExperimentalButtonlessServiceInSecureDfu = enableUnsafeExperimentalButtonlessServiceInSecureDfu { - dfuInitiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = enableUnsafeExperimentalButtonlessServiceInSecureDfu - } - - if let forceDfu = forceDfu { - dfuInitiator.forceDfu = forceDfu - } - - if let alternativeAdvertisingNameEnabled = alternativeAdvertisingNameEnabled { - dfuInitiator.alternativeAdvertisingNameEnabled = alternativeAdvertisingNameEnabled - } - - pendingResult = result - deviceAddress = address - - dfuController = dfuInitiator.start(targetWithIdentifier: uuid) + let dfuInitiator = DFUServiceInitiator(queue: nil) + .with(firmware: firmware); + dfuInitiator.delegate = self + dfuInitiator.progressDelegate = self + dfuInitiator.logger = self + + packetReceiptNotificationParameter.map { dfuInitiator.packetReceiptNotificationParameter = $0 } + forceDfu.map { dfuInitiator.forceDfu = $0 } + forceScanningForNewAddressInLegacyDfu.map { dfuInitiator.forceScanningForNewAddressInLegacyDfu = $0 } + connectionTimeout.map { dfuInitiator.connectionTimeout = $0 } + dataObjectPreparationDelay.map { dfuInitiator.dataObjectPreparationDelay = $0 } + alternativeAdvertisingNameEnabled.map { dfuInitiator.alternativeAdvertisingNameEnabled = $0 } + alternativeAdvertisingName.map { dfuInitiator.alternativeAdvertisingName = $0 } + enableUnsafeExperimentalButtonlessServiceInSecureDfu.map { dfuInitiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = $0 } +// uuidHelper.map { dfuInitiator.uuidHelper = $0 } + disableResume.map { dfuInitiator.disableResume = $0 } + + pendingResult = result + deviceAddress = address + + dfuController = dfuInitiator.start(targetWithIdentifier: uuid) } catch{ result(FlutterError(code: "DFU_FIRMWARE_NOT_FOUND", message: "Could not dfu zip file", details: nil)) @@ -177,6 +190,6 @@ public class SwiftNordicDfuPlugin: NSObject, FlutterPlugin, FlutterStreamHandler //MARK: - LoggerDelegate public func logWith(_ level: LogLevel, message: String) { - //print("\(level.name()): \(message)") + print("\(level.name()): \(message)") } } diff --git a/lib/src/android_special_paramter.dart b/lib/src/android_special_paramter.dart index 0c3aa50..141d59e 100644 --- a/lib/src/android_special_paramter.dart +++ b/lib/src/android_special_paramter.dart @@ -1,5 +1,5 @@ -/// Some parameter just use in Android -/// All this parameters can see in +/// Android parameters for DFUServiceInitiator object. +/// See https://github.com/NordicSemiconductor/Android-DFU-Library for more information. class AndroidSpecialParameter { ///Sets whether the progress notification in the status bar should be disabled. ///Defaults to false. @@ -77,4 +77,14 @@ class AndroidSpecialParameter { this.dataDelay = 400, this.numberOfRetries = 10, }); + + Map toJson() => { + 'disableNotification': disableNotification, + 'keepBond': keepBond, + 'packetReceiptNotificationsEnabled': packetReceiptNotificationsEnabled, + 'restoreBond': restoreBond, + 'startAsForegroundService': startAsForegroundService, + 'dataDelay': dataDelay, + 'numberOfRetries': numberOfRetries + }; } diff --git a/lib/src/ios_special_parameter.dart b/lib/src/ios_special_parameter.dart index ffe8146..7878f41 100644 --- a/lib/src/ios_special_parameter.dart +++ b/lib/src/ios_special_parameter.dart @@ -1,11 +1,98 @@ -/// Some parameter just use in iOS -/// All this parameters can see in +/// iOS parameters for DFUServiceInitiator object. +/// See https://github.com/NordicSemiconductor/IOS-Pods-DFU-Library for more information. class IosSpecialParameter { - ///Sets whether to send unique name to device before it is switched into bootloader mode - ///Defaults to true. + /// By default, the Legacy DFU bootloader starting from SDK 7.1, when enabled using + /// buttonless service, advertises with the same Bluetooth address as the application + /// using direct advertisement. This complies with the Bluetooth specification. + /// However, starting from iOS 13.x, iPhones and iPads use random addresses on each + /// connection and do not expect direct advertising unless bonded. This causes thiose + /// packets being missed and not reported to the library, making reconnection to the + /// bootloader and proceeding with DFU impossible. + /// A solution requires modifying either the bootloader not to use the direct advertising, + /// or the application not to share the peer data with bootloader, in which case it will + /// advertise undirectly using address +1, like it does when the switch to bootloader mode + /// is initiated with a button. After such modification, setting this flag to true will make the + /// library scan for the bootloader using `DFUPeripheralSelector`. + /// + /// Setting this flag to true without modifying the booloader behavior will break the DFU, + /// as the direct advertising packets are empty and will not pass the default + /// `DFUPeripheralSelector`. + + final bool? forceScanningForNewAddressInLegacyDfu; + + /// Connection timeout. + /// + /// When the DFU target does not connect before the time runs out, a timeout error + /// is reported. + final double? connectionTimeout; + + /// Duration of a delay, that the service will wait before sending each data object in + /// Secure DFU. The delay will be done after a data object is created, and before + /// any data byte is sent. The default value is 0, which disables this feature for the + /// second and following data objects, but the first one will be delayed by 0.4 sec. + /// + /// It has been found, that a delay of at least 0.3 sec reduces the risk of packet lose + /// (the bootloader needs some time to prepare flash memory) on DFU bootloader from + /// SDK 15, 16 and 17. The delay does not have to be longer than 0.4 sec, as according to + /// performed tests, such delay is sufficient. + /// + /// The longer the delay, the more time DFU will take to complete (delay will be repeated for + /// each data object (4096 bytes)). However, with too small delay a packet lose may occur, + /// causing the service to enable PRN and set them to 1 making DFU process very, very slow + /// (but reliable). + /// + /// The recommended delay is from 0.3 to 0.4 second if your DFU bootloader is from + /// SDK 15, 16 or 17. Older bootloaders do not need this delay. + /// + /// This variable is ignored in Legacy DFU. + final double? dataObjectPreparationDelay; + + /// In SDK 14.0.0 a new feature was added to the Buttonless DFU for non-bonded + /// devices which allows to send a unique name to the device before it is switched + /// to bootloader mode. After jump, the bootloader will advertise with this name + /// as the Complete Local Name making it easy to select proper device. In this case + /// you don't have to override the default peripheral selector. + /// + /// Read more: + /// http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v14.0.0/service_dfu.html + /// + /// Setting this flag to false you will disable this feature. iOS DFU Library will + /// not send the 0x02-[len]-[new name] command prior jumping and will rely on the DfuPeripheralSelectorDelegate just like it used to in previous SDK. + /// + /// This flag is ignored in Legacy DFU. + /// + /// **It is recommended to keep this flag set to true unless necessary.** + /// + /// For more information read: + /// https://github.com/NordicSemiconductor/IOS-nRF-Connect/issues/16 final bool? alternativeAdvertisingNameEnabled; + /// If `alternativeAdvertisingNameEnabled` is `true` then this specifies the + /// alternative name to use. If nil (default) then a random name is generated. + /// + /// The maximum length of the alertnative advertising name is 20 bytes. + /// Longer name will be trundated. UTF-8 characters can be cut in the middle. + final String? alternativeAdvertisingName; + + /// Disable the ability for the DFU process to resume from where it was. + final bool? disableResume; + const IosSpecialParameter({ this.alternativeAdvertisingNameEnabled, + this.forceScanningForNewAddressInLegacyDfu, + this.connectionTimeout, + this.dataObjectPreparationDelay, + this.alternativeAdvertisingName, + this.disableResume, }); + + Map toJson() => { + 'alternativeAdvertisingNameEnabled': alternativeAdvertisingNameEnabled, + 'forceScanningForNewAddressInLegacyDfu': + forceScanningForNewAddressInLegacyDfu, + 'connectionTimeout': connectionTimeout, + 'dataObjectPreparationDelay': dataObjectPreparationDelay, + 'alternativeAdvertisingName': alternativeAdvertisingName, + 'disableResume': disableResume + }; } diff --git a/lib/src/nordic_dfu.dart b/lib/src/nordic_dfu.dart index 87c4d48..ed83fb4 100644 --- a/lib/src/nordic_dfu.dart +++ b/lib/src/nordic_dfu.dart @@ -52,16 +52,21 @@ class NordicDfu { static const EventChannel _eventChannel = EventChannel('$namespace/event'); StreamSubscription? events; - /// Start dfu handle + /// Start the DFU Process. + /// Required: /// [address] android: mac address iOS: device uuid /// [filePath] zip file path - /// [name] device name - /// [progressListener] Dfu progress listener, You can use [DefaultDfuProgressListenerAdapter] + /// + /// Optional: + /// [name] The device name /// [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 + /// [numberOfPackets] The number of packets of firmware data to be received by the DFU target before sending a new Packet Receipt Notification. /// [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 + /// + /// Callbacks: /// [onDeviceConnected] Callback for when device is connected /// [onDeviceConnecting] Callback for when device is connecting /// [onDeviceDisconnected] Callback for when device is disconnected @@ -170,17 +175,8 @@ class NordicDfu { 'numberOfPackets': numberOfPackets, 'enableUnsafeExperimentalButtonlessServiceInSecureDfu': enableUnsafeExperimentalButtonlessServiceInSecureDfu, - 'disableNotification': androidSpecialParameter.disableNotification, - 'keepBond': androidSpecialParameter.keepBond, - 'restoreBond': androidSpecialParameter.restoreBond, - 'packetReceiptNotificationsEnabled': - androidSpecialParameter.packetReceiptNotificationsEnabled, - 'startAsForegroundService': - androidSpecialParameter.startAsForegroundService, - 'dataDelay': androidSpecialParameter.dataDelay, - 'numberOfRetries': androidSpecialParameter.numberOfRetries, - 'alternativeAdvertisingNameEnabled': - iosSpecialParameter.alternativeAdvertisingNameEnabled + ...androidSpecialParameter.toJson(), + ...iosSpecialParameter.toJson() }); }