diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a535f99..2eef3756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## 3.1.1 +* Apple, Linux: `discoverServices` honours its `timeout`. Both gave up after a fixed 10 seconds, so a longer `timeout` could not help a peripheral that is slow to answer. On Apple, `timeout` (or, without one, the global `UniversalBle.timeout`) is now passed on as the native limit, which `AppleDiscoverServicesOptions(timeoutMillis:)` in `DiscoverServicesPlatformConfig.apple` also sets directly, and then bounds the whole call when no `timeout` is given; without either, it stays 10 seconds. On Linux the discovery no longer has a limit of its own and is bounded by `timeout` alone, as on Android, Windows and Web, so one that runs out of time now fails with a `TimeoutException` rather than `Failed to resolve services` (#349). * Apple: a read, write or subscription the peripheral refuses now reports its ATT error as the matching `UniversalBleErrorCode` (`insufficientEncryption`, `insufficientAuthentication`, `readNotPermitted`, ...), as Android does, instead of `unknownError`. The error code is still in `details`. Codes in `CBErrorDomain` are unchanged: its numbers overlap ATT's with other meanings (#348). * Bump `bluez` to `^0.9.0`. * Bump the `pigeon` dev dependency to `^29.0.0`. diff --git a/README.md b/README.md index b4edbfbf..f5730949 100644 --- a/README.md +++ b/README.md @@ -472,6 +472,14 @@ List services = await bleDevice.discoverServices( ); ``` +On iOS and macOS a discovery that has not completed after 10 seconds fails, so that it cannot hold back later calls for the same device. A `timeout` passed to `discoverServices`, or a global `UniversalBle.timeout` above 10 seconds, extends that, for a peripheral that is slow to answer; on the other platforms it is the only limit. `AppleDiscoverServicesOptions(timeoutMillis: ...)` sets the iOS and macOS limit directly, and without a `timeout` it bounds the whole call too. + +```dart +List services = await bleDevice.discoverServices( + timeout: const Duration(seconds: 30), +); +``` + #### GetService Retrieves a specific service. Returns a `Future`. diff --git a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt index 6db224cf..a27a6270 100644 --- a/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt +++ b/android/src/main/kotlin/com/navideck/universal_ble/UniversalBle.g.kt @@ -1388,6 +1388,53 @@ data class AndroidDiscoverServicesOptions ( } } +/** Generated class from Pigeon that represents data sent in messages. */ +data class AppleDiscoverServicesOptions ( + /** + * How long, in milliseconds, the native discovery may take before it fails. + * iOS and macOS bound discovery themselves, so that a discovery that never + * completes cannot hold back later calls; 10 seconds when not set. Other + * platforms are bounded by the `timeout` of `discoverServices` alone. + * + * `discoverServices` fills this in from its `timeout` (by default the global + * `UniversalBle.timeout`) when only that is given, and takes this as its + * `timeout` when only this is. A value that is not positive counts as unset. + */ + val timeoutMillis: Long? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): AppleDiscoverServicesOptions { + val timeoutMillis = pigeonVar_list[0] as Long? + return AppleDiscoverServicesOptions(timeoutMillis) + } + } + fun toList(): List { + return listOf( + timeoutMillis, + ) + } + override fun equals(other: Any?): Boolean { + if (other == null || other.javaClass != javaClass) { + return false + } + if (this === other) { + return true + } + val other = other as AppleDiscoverServicesOptions + return UniversalBlePigeonUtils.deepEquals(this.timeoutMillis, other.timeoutMillis) + } + + override fun hashCode(): Int { + var result = javaClass.hashCode() + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.timeoutMillis) + return result + } + override fun toString(): String { + return "AppleDiscoverServicesOptions(timeoutMillis=$timeoutMillis)" + } +} + /** * Platform-specific options for `discoverServices`. Ignored on platforms * that have no equivalent. @@ -1395,18 +1442,21 @@ data class AndroidDiscoverServicesOptions ( * Generated class from Pigeon that represents data sent in messages. */ data class DiscoverServicesPlatformConfig ( - val android: AndroidDiscoverServicesOptions? = null + val android: AndroidDiscoverServicesOptions? = null, + val apple: AppleDiscoverServicesOptions? = null ) { companion object { fun fromList(pigeonVar_list: List): DiscoverServicesPlatformConfig { val android = pigeonVar_list[0] as AndroidDiscoverServicesOptions? - return DiscoverServicesPlatformConfig(android) + val apple = pigeonVar_list[1] as AppleDiscoverServicesOptions? + return DiscoverServicesPlatformConfig(android, apple) } } fun toList(): List { return listOf( android, + apple, ) } override fun equals(other: Any?): Boolean { @@ -1417,16 +1467,17 @@ data class DiscoverServicesPlatformConfig ( return true } val other = other as DiscoverServicesPlatformConfig - return UniversalBlePigeonUtils.deepEquals(this.android, other.android) + return UniversalBlePigeonUtils.deepEquals(this.android, other.android) && UniversalBlePigeonUtils.deepEquals(this.apple, other.apple) } override fun hashCode(): Int { var result = javaClass.hashCode() result = 31 * result + UniversalBlePigeonUtils.deepHash(this.android) + result = 31 * result + UniversalBlePigeonUtils.deepHash(this.apple) return result } override fun toString(): String { - return "DiscoverServicesPlatformConfig(android=$android)" + return "DiscoverServicesPlatformConfig(android=$android, apple=$apple)" } } @@ -1917,40 +1968,45 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() { } 161.toByte() -> { return (readValue(buffer) as? List)?.let { - DiscoverServicesPlatformConfig.fromList(it) + AppleDiscoverServicesOptions.fromList(it) } } 162.toByte() -> { return (readValue(buffer) as? List)?.let { - PeripheralAndroidOptions.fromList(it) + DiscoverServicesPlatformConfig.fromList(it) } } 163.toByte() -> { return (readValue(buffer) as? List)?.let { - PeripheralPlatformConfig.fromList(it) + PeripheralAndroidOptions.fromList(it) } } 164.toByte() -> { return (readValue(buffer) as? List)?.let { - PeripheralService.fromList(it) + PeripheralPlatformConfig.fromList(it) } } 165.toByte() -> { return (readValue(buffer) as? List)?.let { - PeripheralCharacteristic.fromList(it) + PeripheralService.fromList(it) } } 166.toByte() -> { return (readValue(buffer) as? List)?.let { - PeripheralDescriptor.fromList(it) + PeripheralCharacteristic.fromList(it) } } 167.toByte() -> { return (readValue(buffer) as? List)?.let { - PeripheralReadRequestResult.fromList(it) + PeripheralDescriptor.fromList(it) } } 168.toByte() -> { + return (readValue(buffer) as? List)?.let { + PeripheralReadRequestResult.fromList(it) + } + } + 169.toByte() -> { return (readValue(buffer) as? List)?.let { PeripheralWriteRequestResult.fromList(it) } @@ -2088,38 +2144,42 @@ private open class UniversalBlePigeonCodec : StandardMessageCodec() { stream.write(160) writeValue(stream, value.toList()) } - is DiscoverServicesPlatformConfig -> { + is AppleDiscoverServicesOptions -> { stream.write(161) writeValue(stream, value.toList()) } - is PeripheralAndroidOptions -> { + is DiscoverServicesPlatformConfig -> { stream.write(162) writeValue(stream, value.toList()) } - is PeripheralPlatformConfig -> { + is PeripheralAndroidOptions -> { stream.write(163) writeValue(stream, value.toList()) } - is PeripheralService -> { + is PeripheralPlatformConfig -> { stream.write(164) writeValue(stream, value.toList()) } - is PeripheralCharacteristic -> { + is PeripheralService -> { stream.write(165) writeValue(stream, value.toList()) } - is PeripheralDescriptor -> { + is PeripheralCharacteristic -> { stream.write(166) writeValue(stream, value.toList()) } - is PeripheralReadRequestResult -> { + is PeripheralDescriptor -> { stream.write(167) writeValue(stream, value.toList()) } - is PeripheralWriteRequestResult -> { + is PeripheralReadRequestResult -> { stream.write(168) writeValue(stream, value.toList()) } + is PeripheralWriteRequestResult -> { + stream.write(169) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift index 1d2ec110..25c8523b 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBle.g.swift @@ -1259,41 +1259,89 @@ struct AndroidDiscoverServicesOptions: Hashable, CustomStringConvertible { } } +/// Generated class from Pigeon that represents data sent in messages. +struct AppleDiscoverServicesOptions: Hashable, CustomStringConvertible { + /// How long, in milliseconds, the native discovery may take before it fails. + /// iOS and macOS bound discovery themselves, so that a discovery that never + /// completes cannot hold back later calls; 10 seconds when not set. Other + /// platforms are bounded by the `timeout` of `discoverServices` alone. + /// + /// `discoverServices` fills this in from its `timeout` (by default the global + /// `UniversalBle.timeout`) when only that is given, and takes this as its + /// `timeout` when only this is. A value that is not positive counts as unset. + var timeoutMillis: Int64? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> AppleDiscoverServicesOptions? { + let timeoutMillis: Int64? = nilOrValue(pigeonVar_list[0]) + + return AppleDiscoverServicesOptions( + timeoutMillis: timeoutMillis + ) + } + func toList() -> [Any?] { + return [ + timeoutMillis + ] + } + static func == (lhs: AppleDiscoverServicesOptions, rhs: AppleDiscoverServicesOptions) -> Bool { + if Swift.type(of: lhs) != Swift.type(of: rhs) { + return false + } + return UniversalBlePigeonInternal.deepEquals(lhs.timeoutMillis, rhs.timeoutMillis) + } + + func hash(into hasher: inout Hasher) { + hasher.combine("AppleDiscoverServicesOptions") + UniversalBlePigeonInternal.deepHash(value: timeoutMillis, hasher: &hasher) + } + + public var description: String { + return "AppleDiscoverServicesOptions(timeoutMillis: \(String(describing: timeoutMillis)))" + } +} + /// Platform-specific options for `discoverServices`. Ignored on platforms /// that have no equivalent. /// /// Generated class from Pigeon that represents data sent in messages. struct DiscoverServicesPlatformConfig: Hashable, CustomStringConvertible { var android: AndroidDiscoverServicesOptions? = nil + var apple: AppleDiscoverServicesOptions? = nil // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> DiscoverServicesPlatformConfig? { let android: AndroidDiscoverServicesOptions? = nilOrValue(pigeonVar_list[0]) + let apple: AppleDiscoverServicesOptions? = nilOrValue(pigeonVar_list[1]) return DiscoverServicesPlatformConfig( - android: android + android: android, + apple: apple ) } func toList() -> [Any?] { return [ - android + android, + apple, ] } static func == (lhs: DiscoverServicesPlatformConfig, rhs: DiscoverServicesPlatformConfig) -> Bool { if Swift.type(of: lhs) != Swift.type(of: rhs) { return false } - return UniversalBlePigeonInternal.deepEquals(lhs.android, rhs.android) + return UniversalBlePigeonInternal.deepEquals(lhs.android, rhs.android) && UniversalBlePigeonInternal.deepEquals(lhs.apple, rhs.apple) } func hash(into hasher: inout Hasher) { hasher.combine("DiscoverServicesPlatformConfig") UniversalBlePigeonInternal.deepHash(value: android, hasher: &hasher) + UniversalBlePigeonInternal.deepHash(value: apple, hasher: &hasher) } public var description: String { - return "DiscoverServicesPlatformConfig(android: \(String(describing: android)))" + return "DiscoverServicesPlatformConfig(android: \(String(describing: android)), apple: \(String(describing: apple)))" } } @@ -1743,20 +1791,22 @@ private class UniversalBlePigeonCodecReader: FlutterStandardReader { case 160: return AndroidDiscoverServicesOptions.fromList(self.readValue() as! [Any?]) case 161: - return DiscoverServicesPlatformConfig.fromList(self.readValue() as! [Any?]) + return AppleDiscoverServicesOptions.fromList(self.readValue() as! [Any?]) case 162: - return PeripheralAndroidOptions.fromList(self.readValue() as! [Any?]) + return DiscoverServicesPlatformConfig.fromList(self.readValue() as! [Any?]) case 163: - return PeripheralPlatformConfig.fromList(self.readValue() as! [Any?]) + return PeripheralAndroidOptions.fromList(self.readValue() as! [Any?]) case 164: - return PeripheralService.fromList(self.readValue() as! [Any?]) + return PeripheralPlatformConfig.fromList(self.readValue() as! [Any?]) case 165: - return PeripheralCharacteristic.fromList(self.readValue() as! [Any?]) + return PeripheralService.fromList(self.readValue() as! [Any?]) case 166: - return PeripheralDescriptor.fromList(self.readValue() as! [Any?]) + return PeripheralCharacteristic.fromList(self.readValue() as! [Any?]) case 167: - return PeripheralReadRequestResult.fromList(self.readValue() as! [Any?]) + return PeripheralDescriptor.fromList(self.readValue() as! [Any?]) case 168: + return PeripheralReadRequestResult.fromList(self.readValue() as! [Any?]) + case 169: return PeripheralWriteRequestResult.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) @@ -1862,30 +1912,33 @@ private class UniversalBlePigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? AndroidDiscoverServicesOptions { super.writeByte(160) super.writeValue(value.toList()) - } else if let value = value as? DiscoverServicesPlatformConfig { + } else if let value = value as? AppleDiscoverServicesOptions { super.writeByte(161) super.writeValue(value.toList()) - } else if let value = value as? PeripheralAndroidOptions { + } else if let value = value as? DiscoverServicesPlatformConfig { super.writeByte(162) super.writeValue(value.toList()) - } else if let value = value as? PeripheralPlatformConfig { + } else if let value = value as? PeripheralAndroidOptions { super.writeByte(163) super.writeValue(value.toList()) - } else if let value = value as? PeripheralService { + } else if let value = value as? PeripheralPlatformConfig { super.writeByte(164) super.writeValue(value.toList()) - } else if let value = value as? PeripheralCharacteristic { + } else if let value = value as? PeripheralService { super.writeByte(165) super.writeValue(value.toList()) - } else if let value = value as? PeripheralDescriptor { + } else if let value = value as? PeripheralCharacteristic { super.writeByte(166) super.writeValue(value.toList()) - } else if let value = value as? PeripheralReadRequestResult { + } else if let value = value as? PeripheralDescriptor { super.writeByte(167) super.writeValue(value.toList()) - } else if let value = value as? PeripheralWriteRequestResult { + } else if let value = value as? PeripheralReadRequestResult { super.writeByte(168) super.writeValue(value.toList()) + } else if let value = value as? PeripheralWriteRequestResult { + super.writeByte(169) + super.writeValue(value.toList()) } else { super.writeValue(value) } diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBleAsyncServiceDiscovery.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBleAsyncServiceDiscovery.swift index 1a6a463c..1131197d 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBleAsyncServiceDiscovery.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBleAsyncServiceDiscovery.swift @@ -20,13 +20,27 @@ class UniversalBleAsyncServiceDiscovery: NSObject { private var hasCompleted = false private var withDescriptors: Bool private var timeoutWorkItem: DispatchWorkItem? - private let timeoutSeconds: TimeInterval = 10.0 + private let timeoutSeconds: TimeInterval - init(peripheral: CBPeripheral, deviceId: String, withDescriptors: Bool, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) { + /// Discovery that has not completed after the timeout fails, so that it cannot hold back + /// later calls for the same device. + static let defaultTimeoutSeconds: TimeInterval = 10.0 + + /// The discovery timeout for these options: `AppleDiscoverServicesOptions.timeoutMillis`, + /// else `defaultTimeoutSeconds`. + static func timeoutSeconds(for platformConfig: DiscoverServicesPlatformConfig?) -> TimeInterval { + guard let millis = platformConfig?.apple?.timeoutMillis, millis > 0 else { + return defaultTimeoutSeconds + } + return TimeInterval(millis) / 1000 + } + + init(peripheral: CBPeripheral, deviceId: String, withDescriptors: Bool, timeoutSeconds: TimeInterval = UniversalBleAsyncServiceDiscovery.defaultTimeoutSeconds, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) { self.peripheral = peripheral self.deviceId = deviceId self.completion = completion self.withDescriptors = withDescriptors + self.timeoutSeconds = timeoutSeconds super.init() } diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift index cb69ce7b..a46485f0 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift @@ -318,7 +318,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral discoverServicesFutures.failAndRemoveAll(matching: deviceId, with: error) } - func discoverServices(deviceId: String, withDescriptors: Bool, platformConfig _: DiscoverServicesPlatformConfig?, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) { + func discoverServices(deviceId: String, withDescriptors: Bool, platformConfig: DiscoverServicesPlatformConfig?, completion: @escaping (Result<[UniversalBleService], Error>) -> Void) { guard let peripheral = peripheralCache.find(deviceId, manager: manager) else { completion( Result.failure(createFlutterError(code: .deviceNotFound, message: "Unknown deviceId:\(deviceId)")) @@ -359,6 +359,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral peripheral: peripheral, deviceId: normalizedDeviceId, withDescriptors: withDescriptors, + timeoutSeconds: UniversalBleAsyncServiceDiscovery.timeoutSeconds(for: platformConfig), completion: wrappedCompletion ) diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift index a8036dd9..4124264f 100644 --- a/example/ios/RunnerTests/RunnerTests.swift +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -58,4 +58,20 @@ class RunnerTests: XCTestCase { XCTAssertEqual(error.code, UniversalBleErrorCode.unknownError.rawValue.description) XCTAssertEqual(error.details as? String, "15") } + + func testDiscoveryWaitsAsLongAsTheCallerAsks() { + let config = DiscoverServicesPlatformConfig(apple: AppleDiscoverServicesOptions(timeoutMillis: 30000)) + + XCTAssertEqual(UniversalBleAsyncServiceDiscovery.timeoutSeconds(for: config), 30) + } + + func testDiscoveryKeepsItsDefaultWithoutATimeout() { + XCTAssertEqual(UniversalBleAsyncServiceDiscovery.timeoutSeconds(for: nil), 10) + XCTAssertEqual(UniversalBleAsyncServiceDiscovery.timeoutSeconds(for: DiscoverServicesPlatformConfig()), 10) + XCTAssertEqual( + UniversalBleAsyncServiceDiscovery.timeoutSeconds( + for: DiscoverServicesPlatformConfig(apple: AppleDiscoverServicesOptions(timeoutMillis: 0))), + 10, + "a timeout of zero would fail every discovery at once") + } } diff --git a/lib/src/extensions/ble_device_extension.dart b/lib/src/extensions/ble_device_extension.dart index c0362595..2c53144e 100644 --- a/lib/src/extensions/ble_device_extension.dart +++ b/lib/src/extensions/ble_device_extension.dart @@ -106,6 +106,8 @@ extension BleDeviceExtension on BleDevice { /// Discovers the services offered by the device. /// /// Returns cached services if already discovered after connection. + /// [timeout] bounds the call, and on Apple the native discovery too (see + /// [UniversalBle.discoverServices]). /// [platformConfig]: platform-specific options, e.g. Android's `clearGattCache`. Future> discoverServices({ Duration? timeout, diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index 53b1f69d..ab247f65 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -32,7 +32,10 @@ class UniversalBle { /// Set global timeout for all commands. /// Default timeout is 10 seconds. - /// Set to null to disable. + /// + /// Set to null to remove the Dart-side bound. On iOS and macOS this does not + /// lift native service discovery's own 10-second limit; pass a `timeout` to + /// [discoverServices] to extend that. static set timeout(Duration? duration) { _bleCommandQueue.timeout = duration; } @@ -324,9 +327,19 @@ class UniversalBle { /// `AndroidDiscoverServicesOptions(clearGattCache: true)` to drop Android's /// GATT cache before discovering. Ignored where not applicable. /// + /// [timeout] bounds the whole call, and defaults to `UniversalBle.timeout`. On + /// Apple it is also passed to the native discovery, which otherwise gives up + /// after 10 seconds, unless [platformConfig] already sets a positive + /// `apple.timeoutMillis`. That option, given without a [timeout], bounds the + /// whole call as well. + /// /// Concurrent calls for the same device that request the same result share a /// single discovery and resolve together. A call that asks for something - /// different (other [withDescriptors]/[platformConfig]) still runs its own. + /// different (other [withDescriptors] or Android options) still runs its own. + /// A timeout does not change the result, so calls that differ only in their + /// timeout share the discovery that started first, and its timeout. On Apple + /// that also holds for a call that only differs in Android options, which the + /// native side joins to the discovery in progress. static Future> discoverServices( String deviceId, { bool withDescriptors = false, @@ -334,11 +347,17 @@ class UniversalBle { String? queueId, DiscoverServicesPlatformConfig? platformConfig, }) { + timeout ??= _appleDiscoveryTimeout(platformConfig); + platformConfig = _withAppleDiscoveryTimeout( + platformConfig, + timeout ?? _bleCommandQueue.timeout, + ); final key = canonicalDeviceId(deviceId); final pending = _pendingDiscoveries[key]; if (pending != null && pending.withDescriptors == withDescriptors && - pending.platformConfig == platformConfig) { + // Apple's options hold only its timeout, which is left out (see above). + pending.platformConfig?.android == platformConfig?.android) { return pending.future; } final future = _runDiscovery( @@ -357,6 +376,34 @@ class UniversalBle { return future; } + /// The `apple.timeoutMillis` of [platformConfig] as a [Duration], or `null` + /// when it is not set or not positive (the native side then uses 10 seconds, + /// and the call the command queue's default). + static Duration? _appleDiscoveryTimeout( + DiscoverServicesPlatformConfig? platformConfig, + ) { + final millis = platformConfig?.apple?.timeoutMillis; + if (millis == null || millis <= 0) return null; + return Duration(milliseconds: millis); + } + + /// [platformConfig] with [timeout] as Apple's native discovery timeout, when + /// a timeout is given and the caller set none (a non-positive one counting as + /// none, as on the native side). Returned unchanged otherwise. + static DiscoverServicesPlatformConfig? _withAppleDiscoveryTimeout( + DiscoverServicesPlatformConfig? platformConfig, + Duration? timeout, + ) { + if (timeout == null || _appleDiscoveryTimeout(platformConfig) != null) { + return platformConfig; + } + return DiscoverServicesPlatformConfig( + android: platformConfig?.android, + apple: + AppleDiscoverServicesOptions(timeoutMillis: timeout.inMilliseconds), + ); + } + static Future> _runDiscovery( String deviceId, { required bool withDescriptors, diff --git a/lib/src/universal_ble.g.dart b/lib/src/universal_ble.g.dart index 885131c0..e4a28ceb 100644 --- a/lib/src/universal_ble.g.dart +++ b/lib/src/universal_ble.g.dart @@ -1412,18 +1412,77 @@ class AndroidDiscoverServicesOptions { } } +class AppleDiscoverServicesOptions { + AppleDiscoverServicesOptions({ + this.timeoutMillis, + }); + + /// How long, in milliseconds, the native discovery may take before it fails. + /// iOS and macOS bound discovery themselves, so that a discovery that never + /// completes cannot hold back later calls; 10 seconds when not set. Other + /// platforms are bounded by the `timeout` of `discoverServices` alone. + /// + /// `discoverServices` fills this in from its `timeout` (by default the global + /// `UniversalBle.timeout`) when only that is given, and takes this as its + /// `timeout` when only this is. A value that is not positive counts as unset. + int? timeoutMillis; + + List _toList() { + return [ + timeoutMillis, + ]; + } + + Object encode() { + return _toList(); + } + + static AppleDiscoverServicesOptions decode(Object result) { + result as List; + return AppleDiscoverServicesOptions( + timeoutMillis: result[0] as int?, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! AppleDiscoverServicesOptions || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(timeoutMillis, other.timeoutMillis); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => _deepHash([runtimeType, ..._toList()]); + + @override + String toString() { + return 'AppleDiscoverServicesOptions(timeoutMillis: $timeoutMillis)'; + } +} + /// Platform-specific options for `discoverServices`. Ignored on platforms /// that have no equivalent. class DiscoverServicesPlatformConfig { DiscoverServicesPlatformConfig({ this.android, + this.apple, }); AndroidDiscoverServicesOptions? android; + AppleDiscoverServicesOptions? apple; + List _toList() { return [ android, + apple, ]; } @@ -1435,6 +1494,7 @@ class DiscoverServicesPlatformConfig { result as List; return DiscoverServicesPlatformConfig( android: result[0] as AndroidDiscoverServicesOptions?, + apple: result[1] as AppleDiscoverServicesOptions?, ); } @@ -1448,7 +1508,8 @@ class DiscoverServicesPlatformConfig { if (identical(this, other)) { return true; } - return _deepEquals(android, other.android); + return _deepEquals(android, other.android) && + _deepEquals(apple, other.apple); } @override @@ -1457,7 +1518,7 @@ class DiscoverServicesPlatformConfig { @override String toString() { - return 'DiscoverServicesPlatformConfig(android: $android)'; + return 'DiscoverServicesPlatformConfig(android: $android, apple: $apple)'; } } @@ -1980,30 +2041,33 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is AndroidDiscoverServicesOptions) { buffer.putUint8(160); writeValue(buffer, value.encode()); - } else if (value is DiscoverServicesPlatformConfig) { + } else if (value is AppleDiscoverServicesOptions) { buffer.putUint8(161); writeValue(buffer, value.encode()); - } else if (value is PeripheralAndroidOptions) { + } else if (value is DiscoverServicesPlatformConfig) { buffer.putUint8(162); writeValue(buffer, value.encode()); - } else if (value is PeripheralPlatformConfig) { + } else if (value is PeripheralAndroidOptions) { buffer.putUint8(163); writeValue(buffer, value.encode()); - } else if (value is PeripheralService) { + } else if (value is PeripheralPlatformConfig) { buffer.putUint8(164); writeValue(buffer, value.encode()); - } else if (value is PeripheralCharacteristic) { + } else if (value is PeripheralService) { buffer.putUint8(165); writeValue(buffer, value.encode()); - } else if (value is PeripheralDescriptor) { + } else if (value is PeripheralCharacteristic) { buffer.putUint8(166); writeValue(buffer, value.encode()); - } else if (value is PeripheralReadRequestResult) { + } else if (value is PeripheralDescriptor) { buffer.putUint8(167); writeValue(buffer, value.encode()); - } else if (value is PeripheralWriteRequestResult) { + } else if (value is PeripheralReadRequestResult) { buffer.putUint8(168); writeValue(buffer, value.encode()); + } else if (value is PeripheralWriteRequestResult) { + buffer.putUint8(169); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -2094,20 +2158,22 @@ class _PigeonCodec extends StandardMessageCodec { case 160: return AndroidDiscoverServicesOptions.decode(readValue(buffer)!); case 161: - return DiscoverServicesPlatformConfig.decode(readValue(buffer)!); + return AppleDiscoverServicesOptions.decode(readValue(buffer)!); case 162: - return PeripheralAndroidOptions.decode(readValue(buffer)!); + return DiscoverServicesPlatformConfig.decode(readValue(buffer)!); case 163: - return PeripheralPlatformConfig.decode(readValue(buffer)!); + return PeripheralAndroidOptions.decode(readValue(buffer)!); case 164: - return PeripheralService.decode(readValue(buffer)!); + return PeripheralPlatformConfig.decode(readValue(buffer)!); case 165: - return PeripheralCharacteristic.decode(readValue(buffer)!); + return PeripheralService.decode(readValue(buffer)!); case 166: - return PeripheralDescriptor.decode(readValue(buffer)!); + return PeripheralCharacteristic.decode(readValue(buffer)!); case 167: - return PeripheralReadRequestResult.decode(readValue(buffer)!); + return PeripheralDescriptor.decode(readValue(buffer)!); case 168: + return PeripheralReadRequestResult.decode(readValue(buffer)!); + case 169: return PeripheralWriteRequestResult.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index 639f6d30..11a2e645 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -187,6 +187,8 @@ class UniversalBleLinux extends UniversalBlePlatform { // platformConfig has no equivalent on this platform final device = _findDeviceById(deviceId); if (device.gattServices.isEmpty && !device.servicesResolved) { + // No timeout of its own: the command queue bounds the call with the + // caller's `timeout`, as on Android, Windows and Web. await device.propertiesChanged.firstWhere((element) { if (element.contains(BluezProperty.connected)) { if (!device.connected) { @@ -197,13 +199,7 @@ class UniversalBleLinux extends UniversalBlePlatform { } } return element.contains(BluezProperty.servicesResolved); - }).timeout( - const Duration(seconds: 10), - onTimeout: () { - UniversalLogger.logInfo("DiscoverServicesFailed: Timeout"); - return []; - }, - ); + }); } // Few ble devices requires delay to perform operations after discovering services diff --git a/lib/universal_ble.dart b/lib/universal_ble.dart index de1e4854..fbe89f52 100644 --- a/lib/universal_ble.dart +++ b/lib/universal_ble.dart @@ -18,6 +18,7 @@ export 'package:universal_ble/src/universal_ble.g.dart' ConnectionPlatformConfig, DiscoverServicesPlatformConfig, AndroidDiscoverServicesOptions, + AppleDiscoverServicesOptions, AndroidScanMode, AndroidScanCallbackType, AndroidScanMatchMode, diff --git a/pigeon/universal_ble.dart b/pigeon/universal_ble.dart index ec49fc7d..b841dc4f 100644 --- a/pigeon/universal_ble.dart +++ b/pigeon/universal_ble.dart @@ -392,11 +392,26 @@ class AndroidDiscoverServicesOptions { AndroidDiscoverServicesOptions({this.clearGattCache}); } +class AppleDiscoverServicesOptions { + /// How long, in milliseconds, the native discovery may take before it fails. + /// iOS and macOS bound discovery themselves, so that a discovery that never + /// completes cannot hold back later calls; 10 seconds when not set. Other + /// platforms are bounded by the `timeout` of `discoverServices` alone. + /// + /// `discoverServices` fills this in from its `timeout` (by default the global + /// `UniversalBle.timeout`) when only that is given, and takes this as its + /// `timeout` when only this is. A value that is not positive counts as unset. + int? timeoutMillis; + + AppleDiscoverServicesOptions({this.timeoutMillis}); +} + /// Platform-specific options for `discoverServices`. Ignored on platforms /// that have no equivalent. class DiscoverServicesPlatformConfig { AndroidDiscoverServicesOptions? android; - DiscoverServicesPlatformConfig({this.android}); + AppleDiscoverServicesOptions? apple; + DiscoverServicesPlatformConfig({this.android, this.apple}); } class PeripheralAndroidOptions { diff --git a/test/discover_services_platform_config_test.dart b/test/discover_services_platform_config_test.dart index 1310a184..1c128b69 100644 --- a/test/discover_services_platform_config_test.dart +++ b/test/discover_services_platform_config_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:universal_ble/universal_ble.dart'; @@ -19,9 +21,30 @@ class _CapturingPlatform extends UniversalBlePlatformMock { } } +/// Answers each discovery after [delay], counting the discoveries it ran. +class _SlowPlatform extends UniversalBlePlatformMock { + _SlowPlatform(this.delay); + + final Duration delay; + int discoveries = 0; + + @override + Future> discoverServices( + String deviceId, + bool withDescriptors, { + DiscoverServicesPlatformConfig? platformConfig, + }) async { + discoveries++; + await Future.delayed(delay); + return const []; + } +} + void main() { const deviceId = 'AA:BB:CC:DD:EE:FF'; + tearDown(() => UniversalBle.timeout = const Duration(seconds: 10)); + test('discoverServices forwards platformConfig to the platform', () async { final platform = _CapturingPlatform(); UniversalBle.setInstance(platform); @@ -29,16 +52,156 @@ void main() { android: AndroidDiscoverServicesOptions(clearGattCache: true), ); await UniversalBle.discoverServices(deviceId, platformConfig: config); - expect(platform.lastPlatformConfig, same(config)); + expect(platform.lastPlatformConfig?.android, same(config.android)); expect(platform.lastPlatformConfig?.android?.clearGattCache, isTrue); expect(platform.lastWithDescriptors, isFalse); }); - test('discoverServices without platformConfig passes null', () async { + test('discoverServices without platformConfig passes only the global timeout', + () async { final platform = _CapturingPlatform(); UniversalBle.setInstance(platform); await UniversalBle.discoverServices(deviceId, withDescriptors: true); - expect(platform.lastPlatformConfig, isNull); + expect(platform.lastPlatformConfig?.android, isNull); + expect(platform.lastPlatformConfig?.apple?.timeoutMillis, 10000); expect(platform.lastWithDescriptors, isTrue); }); + + test('a raised global timeout is passed to Apple', () async { + final platform = _CapturingPlatform(); + UniversalBle.setInstance(platform); + UniversalBle.timeout = const Duration(seconds: 30); + await UniversalBle.discoverServices(deviceId); + expect(platform.lastPlatformConfig?.apple?.timeoutMillis, 30000); + }); + + test('with no timeout at all, no platformConfig is passed', () async { + final platform = _CapturingPlatform(); + UniversalBle.setInstance(platform); + UniversalBle.timeout = null; + await UniversalBle.discoverServices(deviceId); + expect(platform.lastPlatformConfig, isNull); + }); + + test('a non-positive Apple timeout gives way to the timeout', () async { + final platform = _CapturingPlatform(); + UniversalBle.setInstance(platform); + await UniversalBle.discoverServices( + deviceId, + timeout: const Duration(seconds: 30), + platformConfig: DiscoverServicesPlatformConfig( + apple: AppleDiscoverServicesOptions(timeoutMillis: 0), + ), + ); + expect(platform.lastPlatformConfig?.apple?.timeoutMillis, 30000); + }); + + test( + 'discoverServices passes its timeout to Apple as the native discovery timeout', + () async { + final platform = _CapturingPlatform(); + UniversalBle.setInstance(platform); + await UniversalBle.discoverServices( + deviceId, + timeout: const Duration(seconds: 30), + ); + expect(platform.lastPlatformConfig?.apple?.timeoutMillis, 30000); + expect(platform.lastPlatformConfig?.android, isNull); + }, + ); + + test('the timeout keeps the Android options it was given', () async { + final platform = _CapturingPlatform(); + UniversalBle.setInstance(platform); + await UniversalBle.discoverServices( + deviceId, + timeout: const Duration(seconds: 30), + platformConfig: DiscoverServicesPlatformConfig( + android: AndroidDiscoverServicesOptions(clearGattCache: true), + ), + ); + expect(platform.lastPlatformConfig?.apple?.timeoutMillis, 30000); + expect(platform.lastPlatformConfig?.android?.clearGattCache, isTrue); + }); + + test('an Apple timeout set by the caller wins over the timeout', () async { + final platform = _CapturingPlatform(); + UniversalBle.setInstance(platform); + final config = DiscoverServicesPlatformConfig( + apple: AppleDiscoverServicesOptions(timeoutMillis: 45000), + ); + await UniversalBle.discoverServices( + deviceId, + timeout: const Duration(seconds: 30), + platformConfig: config, + ); + expect(platform.lastPlatformConfig, same(config)); + expect(platform.lastPlatformConfig?.apple?.timeoutMillis, 45000); + }); + + group('an Apple timeout given without a timeout', () { + // The command queue's default stands in for the 10 s it has in practice. + setUp(() => UniversalBle.timeout = const Duration(milliseconds: 50)); + + test('bounds the whole call', () async { + UniversalBle.setInstance(_SlowPlatform(const Duration(milliseconds: 150))); + final services = await UniversalBle.discoverServices( + deviceId, + platformConfig: DiscoverServicesPlatformConfig( + apple: AppleDiscoverServicesOptions(timeoutMillis: 1000), + ), + ); + expect(services, isEmpty); + }); + + test('leaves the default when it is not positive', () async { + UniversalBle.setInstance(_SlowPlatform(const Duration(milliseconds: 150))); + await expectLater( + UniversalBle.discoverServices( + deviceId, + platformConfig: DiscoverServicesPlatformConfig( + apple: AppleDiscoverServicesOptions(timeoutMillis: 0), + ), + ), + throwsA(isA()), + ); + }); + }); + + test('calls that differ only in their timeout share one discovery', () async { + final platform = _SlowPlatform(const Duration(milliseconds: 20)); + UniversalBle.setInstance(platform); + await Future.wait([ + UniversalBle.discoverServices(deviceId), + UniversalBle.discoverServices( + deviceId, + timeout: const Duration(seconds: 30), + ), + UniversalBle.discoverServices( + deviceId, + platformConfig: DiscoverServicesPlatformConfig( + apple: AppleDiscoverServicesOptions(timeoutMillis: 45000), + ), + ), + ]); + expect(platform.discoveries, 1); + }); + + test('calls with other Android options still run their own', () async { + final platform = _SlowPlatform(const Duration(milliseconds: 20)); + UniversalBle.setInstance(platform); + await Future.wait([ + UniversalBle.discoverServices( + deviceId, + timeout: const Duration(seconds: 30), + ), + UniversalBle.discoverServices( + deviceId, + platformConfig: DiscoverServicesPlatformConfig( + android: AndroidDiscoverServicesOptions(clearGattCache: true), + ), + ), + ]); + expect(platform.discoveries, 2); + }); } diff --git a/windows/src/generated/universal_ble.g.cpp b/windows/src/generated/universal_ble.g.cpp index ead7272f..0b1d9bbf 100644 --- a/windows/src/generated/universal_ble.g.cpp +++ b/windows/src/generated/universal_ble.g.cpp @@ -2311,18 +2311,92 @@ size_t PigeonInternalDeepHash(const AndroidDiscoverServicesOptions& v) { return v.Hash(); } +// AppleDiscoverServicesOptions + +AppleDiscoverServicesOptions::AppleDiscoverServicesOptions() {} + +AppleDiscoverServicesOptions::AppleDiscoverServicesOptions(const int64_t* timeout_millis) + : timeout_millis_(timeout_millis ? std::optional(*timeout_millis) : std::nullopt) {} + +const int64_t* AppleDiscoverServicesOptions::timeout_millis() const { + return timeout_millis_ ? &(*timeout_millis_) : nullptr; +} + +void AppleDiscoverServicesOptions::set_timeout_millis(const int64_t* value_arg) { + timeout_millis_ = value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void AppleDiscoverServicesOptions::set_timeout_millis(int64_t value_arg) { + timeout_millis_ = value_arg; +} + + +EncodableList AppleDiscoverServicesOptions::ToEncodableList() const { + EncodableList list; + list.reserve(1); + list.push_back(timeout_millis_ ? EncodableValue(*timeout_millis_) : EncodableValue()); + return list; +} + +AppleDiscoverServicesOptions AppleDiscoverServicesOptions::FromEncodableList(const EncodableList& list) { + AppleDiscoverServicesOptions decoded; + auto& encodable_timeout_millis = list[0]; + if (!encodable_timeout_millis.IsNull()) { + decoded.set_timeout_millis(std::get(encodable_timeout_millis)); + } + return decoded; +} + +bool AppleDiscoverServicesOptions::operator==(const AppleDiscoverServicesOptions& other) const { + return PigeonInternalDeepEquals(timeout_millis_, other.timeout_millis_); +} + +bool AppleDiscoverServicesOptions::operator!=(const AppleDiscoverServicesOptions& other) const { + return !(*this == other); +} + +size_t AppleDiscoverServicesOptions::Hash() const { + size_t result = 1; + result = result * 31 + PigeonInternalDeepHash(timeout_millis_); + return result; +} + +std::ostream& operator<<( + std::ostream& os, + const AppleDiscoverServicesOptions& obj) { + os << "AppleDiscoverServicesOptions("; + os << "timeout_millis: "; + if (obj.timeout_millis_) { + os << PigeonInternalToString(*obj.timeout_millis_); + } + else { + os << "null"; + } + os << ")"; + return os; +} + +size_t PigeonInternalDeepHash(const AppleDiscoverServicesOptions& v) { + return v.Hash(); +} + // DiscoverServicesPlatformConfig DiscoverServicesPlatformConfig::DiscoverServicesPlatformConfig() {} -DiscoverServicesPlatformConfig::DiscoverServicesPlatformConfig(const AndroidDiscoverServicesOptions* android) - : android_(android ? std::make_unique(*android) : nullptr) {} +DiscoverServicesPlatformConfig::DiscoverServicesPlatformConfig( + const AndroidDiscoverServicesOptions* android, + const AppleDiscoverServicesOptions* apple) + : android_(android ? std::make_unique(*android) : nullptr), + apple_(apple ? std::make_unique(*apple) : nullptr) {} DiscoverServicesPlatformConfig::DiscoverServicesPlatformConfig(const DiscoverServicesPlatformConfig& other) - : android_(other.android_ ? std::make_unique(*other.android_) : nullptr) {} + : android_(other.android_ ? std::make_unique(*other.android_) : nullptr), + apple_(other.apple_ ? std::make_unique(*other.apple_) : nullptr) {} DiscoverServicesPlatformConfig& DiscoverServicesPlatformConfig::operator=(const DiscoverServicesPlatformConfig& other) { android_ = other.android_ ? std::make_unique(*other.android_) : nullptr; + apple_ = other.apple_ ? std::make_unique(*other.apple_) : nullptr; return *this; } @@ -2339,10 +2413,24 @@ void DiscoverServicesPlatformConfig::set_android(const AndroidDiscoverServicesOp } +const AppleDiscoverServicesOptions* DiscoverServicesPlatformConfig::apple() const { + return apple_.get(); +} + +void DiscoverServicesPlatformConfig::set_apple(const AppleDiscoverServicesOptions* value_arg) { + apple_ = value_arg ? std::make_unique(*value_arg) : nullptr; +} + +void DiscoverServicesPlatformConfig::set_apple(const AppleDiscoverServicesOptions& value_arg) { + apple_ = std::make_unique(value_arg); +} + + EncodableList DiscoverServicesPlatformConfig::ToEncodableList() const { EncodableList list; - list.reserve(1); + list.reserve(2); list.push_back(android_ ? CustomEncodableValue(*android_) : EncodableValue()); + list.push_back(apple_ ? CustomEncodableValue(*apple_) : EncodableValue()); return list; } @@ -2352,11 +2440,15 @@ DiscoverServicesPlatformConfig DiscoverServicesPlatformConfig::FromEncodableList if (!encodable_android.IsNull()) { decoded.set_android(std::any_cast(std::get(encodable_android))); } + auto& encodable_apple = list[1]; + if (!encodable_apple.IsNull()) { + decoded.set_apple(std::any_cast(std::get(encodable_apple))); + } return decoded; } bool DiscoverServicesPlatformConfig::operator==(const DiscoverServicesPlatformConfig& other) const { - return PigeonInternalDeepEquals(android_, other.android_); + return PigeonInternalDeepEquals(android_, other.android_) && PigeonInternalDeepEquals(apple_, other.apple_); } bool DiscoverServicesPlatformConfig::operator!=(const DiscoverServicesPlatformConfig& other) const { @@ -2366,6 +2458,7 @@ bool DiscoverServicesPlatformConfig::operator!=(const DiscoverServicesPlatformCo size_t DiscoverServicesPlatformConfig::Hash() const { size_t result = 1; result = result * 31 + PigeonInternalDeepHash(android_); + result = result * 31 + PigeonInternalDeepHash(apple_); return result; } @@ -2380,6 +2473,13 @@ std::ostream& operator<<( else { os << "null"; } + os << ", apple: "; + if (obj.apple_) { + os << *obj.apple_; + } + else { + os << "null"; + } os << ")"; return os; } @@ -3293,27 +3393,30 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType( return CustomEncodableValue(AndroidDiscoverServicesOptions::FromEncodableList(std::get(ReadValue(stream)))); } case 161: { - return CustomEncodableValue(DiscoverServicesPlatformConfig::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(AppleDiscoverServicesOptions::FromEncodableList(std::get(ReadValue(stream)))); } case 162: { - return CustomEncodableValue(PeripheralAndroidOptions::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(DiscoverServicesPlatformConfig::FromEncodableList(std::get(ReadValue(stream)))); } case 163: { - return CustomEncodableValue(PeripheralPlatformConfig::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(PeripheralAndroidOptions::FromEncodableList(std::get(ReadValue(stream)))); } case 164: { - return CustomEncodableValue(PeripheralService::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(PeripheralPlatformConfig::FromEncodableList(std::get(ReadValue(stream)))); } case 165: { - return CustomEncodableValue(PeripheralCharacteristic::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(PeripheralService::FromEncodableList(std::get(ReadValue(stream)))); } case 166: { - return CustomEncodableValue(PeripheralDescriptor::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(PeripheralCharacteristic::FromEncodableList(std::get(ReadValue(stream)))); } case 167: { - return CustomEncodableValue(PeripheralReadRequestResult::FromEncodableList(std::get(ReadValue(stream)))); + return CustomEncodableValue(PeripheralDescriptor::FromEncodableList(std::get(ReadValue(stream)))); } case 168: { + return CustomEncodableValue(PeripheralReadRequestResult::FromEncodableList(std::get(ReadValue(stream)))); + } + case 169: { return CustomEncodableValue(PeripheralWriteRequestResult::FromEncodableList(std::get(ReadValue(stream)))); } default: @@ -3485,43 +3588,48 @@ void PigeonInternalCodecSerializer::WriteValue( WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } - if (custom_value->type() == typeid(DiscoverServicesPlatformConfig)) { + if (custom_value->type() == typeid(AppleDiscoverServicesOptions)) { stream->WriteByte(161); + WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); + return; + } + if (custom_value->type() == typeid(DiscoverServicesPlatformConfig)) { + stream->WriteByte(162); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralAndroidOptions)) { - stream->WriteByte(162); + stream->WriteByte(163); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralPlatformConfig)) { - stream->WriteByte(163); + stream->WriteByte(164); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralService)) { - stream->WriteByte(164); + stream->WriteByte(165); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralCharacteristic)) { - stream->WriteByte(165); + stream->WriteByte(166); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralDescriptor)) { - stream->WriteByte(166); + stream->WriteByte(167); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralReadRequestResult)) { - stream->WriteByte(167); + stream->WriteByte(168); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } if (custom_value->type() == typeid(PeripheralWriteRequestResult)) { - stream->WriteByte(168); + stream->WriteByte(169); WriteValue(EncodableValue(std::any_cast(*custom_value).ToEncodableList()), stream); return; } diff --git a/windows/src/generated/universal_ble.g.h b/windows/src/generated/universal_ble.g.h index 350268f4..e6f125f6 100644 --- a/windows/src/generated/universal_ble.g.h +++ b/windows/src/generated/universal_ble.g.h @@ -1109,6 +1109,47 @@ class AndroidDiscoverServicesOptions { }; +// Generated class from Pigeon that represents data sent in messages. +class AppleDiscoverServicesOptions { + public: + // Constructs an object setting all non-nullable fields. + AppleDiscoverServicesOptions(); + + // Constructs an object setting all fields. + explicit AppleDiscoverServicesOptions(const int64_t* timeout_millis); + + // How long, in milliseconds, the native discovery may take before it fails. + // iOS and macOS bound discovery themselves, so that a discovery that never + // completes cannot hold back later calls; 10 seconds when not set. Other + // platforms are bounded by the `timeout` of `discoverServices` alone. + // + // `discoverServices` fills this in from its `timeout` (by default the global + // `UniversalBle.timeout`) when only that is given, and takes this as its + // `timeout` when only this is. A value that is not positive counts as unset. + const int64_t* timeout_millis() const; + void set_timeout_millis(const int64_t* value_arg); + void set_timeout_millis(int64_t value_arg); + + bool operator==(const AppleDiscoverServicesOptions& other) const; + bool operator!=(const AppleDiscoverServicesOptions& other) const; + /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. + size_t Hash() const; + /// Stream output operator for formatted string representation. + friend std::ostream& operator<<(std::ostream& os, const AppleDiscoverServicesOptions& obj); + private: + static AppleDiscoverServicesOptions FromEncodableList(const ::flutter::EncodableList& list); + ::flutter::EncodableList ToEncodableList() const; + friend class DiscoverServicesPlatformConfig; + friend class UniversalBlePlatformChannel; + friend class UniversalBleCallbackChannel; + friend class UniversalBlePeripheralChannel; + friend class UniversalBleAndroidChannel; + friend class UniversalBlePeripheralCallback; + friend class PigeonInternalCodecSerializer; + std::optional timeout_millis_; +}; + + // Platform-specific options for `discoverServices`. Ignored on platforms // that have no equivalent. // @@ -1119,7 +1160,9 @@ class DiscoverServicesPlatformConfig { DiscoverServicesPlatformConfig(); // Constructs an object setting all fields. - explicit DiscoverServicesPlatformConfig(const AndroidDiscoverServicesOptions* android); + explicit DiscoverServicesPlatformConfig( + const AndroidDiscoverServicesOptions* android, + const AppleDiscoverServicesOptions* apple); ~DiscoverServicesPlatformConfig() = default; DiscoverServicesPlatformConfig(const DiscoverServicesPlatformConfig& other); @@ -1130,6 +1173,10 @@ class DiscoverServicesPlatformConfig { void set_android(const AndroidDiscoverServicesOptions* value_arg); void set_android(const AndroidDiscoverServicesOptions& value_arg); + const AppleDiscoverServicesOptions* apple() const; + void set_apple(const AppleDiscoverServicesOptions* value_arg); + void set_apple(const AppleDiscoverServicesOptions& value_arg); + bool operator==(const DiscoverServicesPlatformConfig& other) const; bool operator!=(const DiscoverServicesPlatformConfig& other) const; /// Returns a hash code value for the object. This method is supported for the benefit of hash tables. @@ -1146,6 +1193,7 @@ class DiscoverServicesPlatformConfig { friend class UniversalBlePeripheralCallback; friend class PigeonInternalCodecSerializer; std::unique_ptr android_; + std::unique_ptr apple_; };