Add timeout argument to all APIs (#172)

* Add timeout argument to all apis

* Update Version and changelog

* Bump version

* Update readme

---------

Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
Rohit Sangwan
2025-07-16 15:17:48 +05:30
committed by GitHub
parent 2170588045
commit 0f4bae71e1
6 changed files with 149 additions and 57 deletions
+4
View File
@@ -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`
+3 -1
View File
@@ -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.
@@ -21,14 +21,25 @@ extension BleCharacteristicExtension on BleCharacteristic {
CharacteristicSubscription(this, CharacteristicProperty.indicate);
/// Unsubscribes notifications/indications from this characteristic.
Future<void> unsubscribe() =>
UniversalBle.unsubscribe(_deviceId, _serviceId, uuid);
/// Reads the current value of the characteristic.
Future<Uint8List> read() => UniversalBle.read(
Future<void> unsubscribe({
Duration? timeout,
}) =>
UniversalBle.unsubscribe(
_deviceId,
_serviceId,
uuid,
timeout: timeout,
);
/// Reads the current value of the characteristic.
Future<Uint8List> 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<void> write(List<int> value, {bool withResponse = true}) async {
Future<void> write(
List<int> 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<void> subscribe() {
Future<void> 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<void> unsubscribe() {
Future<void> unsubscribe({
Duration? timeout,
}) {
if (!isSupported) throw Exception('Operation not supported');
return UniversalBle.unsubscribe(
_characteristic._deviceId,
_characteristic._serviceId,
_characteristic.uuid,
timeout: timeout,
);
}
+23 -11
View File
@@ -33,12 +33,12 @@ extension BleDeviceExtension on BleDevice {
/// Note that it will trigger pairing if the device is not already paired.
Future<bool?> 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<void> 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<void> unpair() => UniversalBle.unpair(deviceId);
Future<void> unpair({
Duration? timeout,
}) =>
UniversalBle.unpair(deviceId, timeout: timeout);
/// Discovers the services offered by the device.
///
/// Returns cached services if already discovered after connection.
Future<List<BleService>> discoverServices() async {
List<BleService> servicesCache =
await UniversalBle.discoverServices(deviceId);
Future<List<BleService>> discoverServices({
Duration? timeout,
}) async {
List<BleService> servicesCache = await UniversalBle.discoverServices(
deviceId,
timeout: timeout,
);
CacheHandler.instance.saveServices(deviceId, servicesCache);
return servicesCache;
}
@@ -87,13 +94,14 @@ extension BleDeviceExtension on BleDevice {
Future<BleService> getService(
String service, {
bool preferCached = true,
Duration? timeout,
}) async {
List<BleService> 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);
}
}
+87 -36
View File
@@ -95,9 +95,9 @@ class UniversalBle {
/// Can throw `ConnectionException` or `PlatformException`.
static Future<void> connect(
String deviceId, {
Duration? connectionTimeout,
Duration? timeout,
}) async {
connectionTimeout ??= const Duration(seconds: 60);
timeout ??= const Duration(seconds: 60);
StreamSubscription? connectionSubscription;
Completer<bool> 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<void> disconnect(String deviceId) async {
static Future<void> disconnect(
String deviceId, {
Duration? timeout,
}) async {
return await _bleCommandQueue.queueCommand(
() => _platform.disconnect(deviceId),
timeout: timeout,
deviceId: deviceId,
);
}
/// Discover services of a device.
static Future<List<BleService>> discoverServices(String deviceId) async {
static Future<List<BleService>> 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<void> 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<void> 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<void> 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<int> requestMtu(String deviceId, int expectedMtu) async {
static Future<int> 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<bool?> 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<void> 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<void> unpair(String deviceId) async {
static Future<void> 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<List<BleDevice>> getSystemDevices({
List<String>? 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<BleConnectionState> getConnectionState(String deviceId) async {
static Future<BleConnectionState> 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<bool> enableBluetooth() async {
static Future<bool> 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<bool> disableBluetooth() async {
static Future<bool> 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<void> _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<BleService> services = await discoverServices(deviceId);
List<BleService> 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<void> _attemptPairingReadingAll(
String deviceId,
List<BleService> services,
) async {
List<BleService> 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<void> _executeBleCommand(
String deviceId,
List<BleService> 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) {
+1 -1
View File
@@ -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