diff --git a/CHANGELOG.md b/CHANGELOG.md index 728506e..013415f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.21.0 +* BREAKING CHANGE: `connectionTimeout` argument from `connect`, `isPaired` and `pair` API renamed to `timeout` +* Add `timeout` argument to all APIs + ## 0.20.2 * Fix `BleCharacteristic.onValueReceived` diff --git a/README.md b/README.md index a1cae44..ebb7cb4 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ UniversalBle.onQueueUpdate = (String id, int remainingItems) { ## Timeout -By default, all commands have a timeout of 10 seconds. +By default, all commands have a global timeout of 10 seconds. ```dart // Change timeout @@ -432,6 +432,8 @@ UniversalBle.timeout = const Duration(seconds: 10); UniversalBle.timeout = null; ``` +You can also specify the `timeout` parameter when sending a command. This will override the global timeout. + ## UUID Format Agnostic Universal BLE is agnostic to the UUID format of services and characteristics regardless of the platform the app runs on. When passing a UUID, you can pass it in any format (long/short) or character case (upper/lower case) you want. Universal BLE will take care of necessary conversions, across all platforms, so that you don't need to worry about underlying platform differences. diff --git a/lib/src/extensions/ble_characteristic_extension.dart b/lib/src/extensions/ble_characteristic_extension.dart index 307e526..e605e96 100644 --- a/lib/src/extensions/ble_characteristic_extension.dart +++ b/lib/src/extensions/ble_characteristic_extension.dart @@ -21,14 +21,25 @@ extension BleCharacteristicExtension on BleCharacteristic { CharacteristicSubscription(this, CharacteristicProperty.indicate); /// Unsubscribes notifications/indications from this characteristic. - Future unsubscribe() => - UniversalBle.unsubscribe(_deviceId, _serviceId, uuid); - - /// Reads the current value of the characteristic. - Future read() => UniversalBle.read( + Future unsubscribe({ + Duration? timeout, + }) => + UniversalBle.unsubscribe( _deviceId, _serviceId, uuid, + timeout: timeout, + ); + + /// Reads the current value of the characteristic. + Future read({ + Duration? timeout, + }) => + UniversalBle.read( + _deviceId, + _serviceId, + uuid, + timeout: timeout, ); /// Writes a value to the characteristic. @@ -37,13 +48,18 @@ extension BleCharacteristicExtension on BleCharacteristic { /// [withResponse] indicates whether the write should be performed with a response from the device. /// Default is true, meaning the device will acknowledge the write operation. /// If set to false, the write operation will be performed without waiting for a response. - Future write(List value, {bool withResponse = true}) async { + Future write( + List value, { + bool withResponse = true, + Duration? timeout, + }) async { await UniversalBle.write( _deviceId, _serviceId, uuid, Uint8List.fromList(value), withoutResponse: !withResponse, + timeout: timeout, ); } @@ -103,7 +119,9 @@ class CharacteristicSubscription { } /// Subscribes to this characteristic. - Future subscribe() { + Future subscribe({ + Duration? timeout, + }) { if (!isSupported) throw Exception('Operation not supported'); if (_property == CharacteristicProperty.indicate) { @@ -111,6 +129,7 @@ class CharacteristicSubscription { _characteristic._deviceId, _characteristic._serviceId, _characteristic.uuid, + timeout: timeout, ); } @@ -118,16 +137,20 @@ class CharacteristicSubscription { _characteristic._deviceId, _characteristic._serviceId, _characteristic.uuid, + timeout: timeout, ); } /// Unsubscribes from this characteristic. - Future unsubscribe() { + Future unsubscribe({ + Duration? timeout, + }) { if (!isSupported) throw Exception('Operation not supported'); return UniversalBle.unsubscribe( _characteristic._deviceId, _characteristic._serviceId, _characteristic.uuid, + timeout: timeout, ); } diff --git a/lib/src/extensions/ble_device_extension.dart b/lib/src/extensions/ble_device_extension.dart index dd46c59..786ca22 100644 --- a/lib/src/extensions/ble_device_extension.dart +++ b/lib/src/extensions/ble_device_extension.dart @@ -33,12 +33,12 @@ extension BleDeviceExtension on BleDevice { /// Note that it will trigger pairing if the device is not already paired. Future isPaired({ BleCommand? pairingCommand, - Duration? connectionTimeout, + Duration? timeout, }) { return UniversalBle.isPaired( deviceId, pairingCommand: pairingCommand, - connectionTimeout: connectionTimeout, + timeout: timeout, ); } @@ -55,26 +55,33 @@ extension BleDeviceExtension on BleDevice { /// Can throw `PairingException`, `ConnectionException` or `PlatformException`. Future pair({ BleCommand? pairingCommand, - Duration? connectionTimeout, + Duration? timeout, }) { return UniversalBle.pair( deviceId, pairingCommand: pairingCommand, - connectionTimeout: connectionTimeout, + timeout: timeout, ); } /// Unpair a device. /// /// It might throw an error if device is not paired. - Future unpair() => UniversalBle.unpair(deviceId); + Future unpair({ + Duration? timeout, + }) => + UniversalBle.unpair(deviceId, timeout: timeout); /// Discovers the services offered by the device. /// /// Returns cached services if already discovered after connection. - Future> discoverServices() async { - List servicesCache = - await UniversalBle.discoverServices(deviceId); + Future> discoverServices({ + Duration? timeout, + }) async { + List servicesCache = await UniversalBle.discoverServices( + deviceId, + timeout: timeout, + ); CacheHandler.instance.saveServices(deviceId, servicesCache); return servicesCache; } @@ -87,13 +94,14 @@ extension BleDeviceExtension on BleDevice { Future getService( String service, { bool preferCached = true, + Duration? timeout, }) async { List discoveredServices = []; if (preferCached) { discoveredServices = CacheHandler.instance.getServices(deviceId) ?? []; } if (discoveredServices.isEmpty) { - discoveredServices = await discoverServices(); + discoveredServices = await discoverServices(timeout: timeout); } if (discoveredServices.isEmpty) { @@ -118,9 +126,13 @@ extension BleDeviceExtension on BleDevice { String characteristic, { required String service, bool preferCached = true, + Duration? timeout, }) async { - BleService bluetoothService = - await getService(service, preferCached: preferCached); + BleService bluetoothService = await getService( + service, + preferCached: preferCached, + timeout: timeout, + ); return bluetoothService.getCharacteristic(characteristic); } } diff --git a/lib/src/universal_ble.dart b/lib/src/universal_ble.dart index 895021f..944f8bf 100644 --- a/lib/src/universal_ble.dart +++ b/lib/src/universal_ble.dart @@ -95,9 +95,9 @@ class UniversalBle { /// Can throw `ConnectionException` or `PlatformException`. static Future connect( String deviceId, { - Duration? connectionTimeout, + Duration? timeout, }) async { - connectionTimeout ??= const Duration(seconds: 60); + timeout ??= const Duration(seconds: 60); StreamSubscription? connectionSubscription; Completer completer = Completer(); @@ -126,10 +126,10 @@ class UniversalBle { ); _platform - .connect(deviceId, connectionTimeout: connectionTimeout) + .connect(deviceId, connectionTimeout: timeout) .catchError(handleError); - if (!await completer.future.timeout(connectionTimeout)) { + if (!await completer.future.timeout(timeout)) { throw ConnectionException("Failed to connect"); } } finally { @@ -139,17 +139,25 @@ class UniversalBle { /// Disconnect from a device. /// Get notified of connection state changes in [onConnectionChange] listener. - static Future disconnect(String deviceId) async { + static Future disconnect( + String deviceId, { + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.disconnect(deviceId), + timeout: timeout, deviceId: deviceId, ); } /// Discover services of a device. - static Future> discoverServices(String deviceId) async { + static Future> discoverServices( + String deviceId, { + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.discoverServices(deviceId), + timeout: timeout, deviceId: deviceId, ); } @@ -160,13 +168,15 @@ class UniversalBle { static Future subscribeNotifications( String deviceId, String service, - String characteristic, - ) async { + String characteristic, { + Duration? timeout, + }) async { return _sendBleInputPropertyCommand( deviceId, service, characteristic, BleInputProperty.notification, + timeout: timeout, ); } @@ -176,13 +186,15 @@ class UniversalBle { static Future subscribeIndications( String deviceId, String service, - String characteristic, - ) async { + String characteristic, { + Duration? timeout, + }) async { return _sendBleInputPropertyCommand( deviceId, service, characteristic, BleInputProperty.indication, + timeout: timeout, ); } @@ -190,13 +202,15 @@ class UniversalBle { static Future unsubscribe( String deviceId, String service, - String characteristic, - ) async { + String characteristic, { + Duration? timeout, + }) async { return _sendBleInputPropertyCommand( deviceId, service, characteristic, BleInputProperty.disabled, + timeout: timeout, ); } @@ -228,6 +242,7 @@ class UniversalBle { String characteristic, Uint8List value, { bool withoutResponse = false, + Duration? timeout, }) async { await _bleCommandQueue.queueCommand( () => _platform.writeValue( @@ -239,6 +254,7 @@ class UniversalBle { ? BleOutputProperty.withoutResponse : BleOutputProperty.withResponse, ), + timeout: timeout, deviceId: deviceId, ); } @@ -246,9 +262,14 @@ class UniversalBle { /// Request MTU value. /// It will **attempt** to set the MTU (Maximum Transmission Unit) but it is not guaranteed to succeed due to platform limitations. /// It will always return the current MTU. - static Future requestMtu(String deviceId, int expectedMtu) async { + static Future requestMtu( + String deviceId, + int expectedMtu, { + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.requestMtu(deviceId, expectedMtu), + timeout: timeout, deviceId: deviceId, ); } @@ -262,12 +283,13 @@ class UniversalBle { static Future isPaired( String deviceId, { BleCommand? pairingCommand, - Duration? connectionTimeout, + Duration? timeout, }) async { if (BleCapabilities.hasSystemPairingApi) { return _bleCommandQueue.queueCommand( () => _platform.isPaired(deviceId), deviceId: deviceId, + timeout: timeout, ); } @@ -280,8 +302,8 @@ class UniversalBle { await _connectAndExecuteBleCommand( deviceId, pairingCommand, - connectionTimeout: connectionTimeout, updateCallbackValue: false, + timeout: timeout, ); // Because pairingCommand will be never null, so we wont get Unknown result here @@ -306,10 +328,14 @@ class UniversalBle { static Future pair( String deviceId, { BleCommand? pairingCommand, - Duration? connectionTimeout, + Duration? timeout, }) async { if (BleCapabilities.hasSystemPairingApi) { - bool paired = await _platform.pair(deviceId); + bool paired = await _bleCommandQueue.queueCommand( + () => _platform.pair(deviceId), + deviceId: deviceId, + timeout: timeout, + ); if (!paired) throw PairingException(); } else { if (pairingCommand == null) { @@ -318,17 +344,21 @@ class UniversalBle { await _connectAndExecuteBleCommand( deviceId, pairingCommand, - connectionTimeout: connectionTimeout, + timeout: timeout, ); } } /// Unpair a device. /// It might throw an error if device is not paired. - static Future unpair(String deviceId) async { + static Future unpair( + String deviceId, { + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.unpair(deviceId), deviceId: deviceId, + timeout: timeout, ); } @@ -339,36 +369,48 @@ class UniversalBle { /// Not supported on `Web`. static Future> getSystemDevices({ List? withServices, + Duration? timeout, }) async { return await _bleCommandQueue.queueCommand( () => _platform.getSystemDevices(withServices?.toValidUUIDList()), + timeout: timeout, ); } /// Returns connection state of the device. /// All platforms will return `Connected/Disconnected` states. /// `Android` and `Apple` can also return `Connecting/Disconnecting` states. - static Future getConnectionState(String deviceId) async { + static Future getConnectionState( + String deviceId, { + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.getConnectionState(deviceId), + timeout: timeout, ); } /// Enable Bluetooth. /// It might throw errors if Bluetooth is not available. /// Not supported on `Web` and `Apple`. - static Future enableBluetooth() async { + static Future enableBluetooth({ + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.enableBluetooth(), + timeout: timeout, ); } /// Disable Bluetooth. /// It might throw errors if Bluetooth is not available. /// Not supported on `Web` and `Apple`. - static Future disableBluetooth() async { + static Future disableBluetooth({ + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.disableBluetooth(), + timeout: timeout, ); } @@ -439,8 +481,9 @@ class UniversalBle { String deviceId, String service, String characteristic, - BleInputProperty bleInputProperty, - ) async { + BleInputProperty bleInputProperty, { + Duration? timeout, + }) async { return await _bleCommandQueue.queueCommand( () => _platform.setNotifiable( deviceId, @@ -449,42 +492,48 @@ class UniversalBle { bleInputProperty, ), deviceId: deviceId, + timeout: timeout, ); } static Future _connectAndExecuteBleCommand( String deviceId, BleCommand? bleCommand, { - Duration? connectionTimeout, bool updateCallbackValue = false, + Duration? timeout, }) async { + var connectionState = await getConnectionState(deviceId, timeout: timeout); // Try to connect first - if (await getConnectionState(deviceId) != BleConnectionState.connected) { + if (connectionState != BleConnectionState.connected) { UniversalLogger.logInfo("Connecting to $deviceId"); await connect( deviceId, - connectionTimeout: connectionTimeout, + timeout: timeout, ); } - List services = await discoverServices(deviceId); + List services = await discoverServices( + deviceId, + timeout: timeout, + ); UniversalLogger.logInfo("Discovered services: ${services.length}"); if (bleCommand == null) { // Just attempt pairing - await _attemptPairingReadingAll(deviceId, services); + await _attemptPairingReadingAll(deviceId, services, timeout: timeout); return; } - await _executeBleCommand(deviceId, services, bleCommand); + await _executeBleCommand(deviceId, services, bleCommand, timeout: timeout); if (updateCallbackValue) _platform.updatePairingState(deviceId, true); } // Fire and forget, and do not rely on result static Future _attemptPairingReadingAll( String deviceId, - List services, - ) async { + List services, { + Duration? timeout, + }) async { bool containsReadCharacteristics = false; try { // If BleCommand not given, fallback to reading all characteristics @@ -496,7 +545,7 @@ class UniversalBle { deviceId, service.uuid, characteristic.uuid, - timeout: const Duration(seconds: 30), + timeout: timeout ?? const Duration(seconds: 30), ); } } @@ -511,8 +560,9 @@ class UniversalBle { static Future _executeBleCommand( String deviceId, List services, - BleCommand bleCommand, - ) async { + BleCommand bleCommand, { + Duration? timeout, + }) async { // First find BleCommand's characteristic BleCharacteristic? characteristic; for (BleService service in services) { @@ -555,6 +605,7 @@ class UniversalBle { bleCommand.characteristic, value, withoutResponse: withoutResponse, + timeout: timeout, ); } else { // Fallback to read if supported @@ -562,7 +613,7 @@ class UniversalBle { deviceId, bleCommand.service, bleCommand.characteristic, - timeout: const Duration(seconds: 30), + timeout: timeout ?? const Duration(seconds: 30), ); } } catch (e) { diff --git a/pubspec.yaml b/pubspec.yaml index 70404de..43e854e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: universal_ble description: A cross-platform (Android/iOS/macOS/Windows/Linux/Web) Bluetooth Low Energy (BLE) plugin for Flutter -version: 0.20.2 +version: 0.21.0 homepage: https://navideck.com repository: https://github.com/Navideck/universal_ble issue_tracker: https://github.com/Navideck/universal_ble/issues