Improve disconnect api (#192)

* Improve disconnect api

* Get disconnection event on Web from callback

* Wait for disconnection result on calling disconnect

* Improve linux disconnection

* Update Changelog

* Fix Ai comments
This commit is contained in:
Rohit Sangwan
2025-11-14 20:20:22 +05:30
committed by GitHub
parent 970db165dd
commit 09bb22750a
9 changed files with 181 additions and 89 deletions
+95 -39
View File
@@ -120,42 +120,18 @@ class UniversalBle {
Duration? timeout,
}) async {
timeout ??= const Duration(seconds: 60);
StreamSubscription? connectionSubscription;
Completer<bool> completer = Completer();
Completer<bool> completer =
_connectionEventCompleter(deviceId, timeout: timeout);
void handleError(dynamic error) {
if (completer.isCompleted) return;
connectionSubscription?.cancel();
completer.completeError(ConnectionException(error));
}
_platform.connect(deviceId, connectionTimeout: timeout).catchError(
(error) {
if (completer.isCompleted) return;
completer.completeError(ConnectionException(error));
},
);
try {
connectionSubscription = _platform
.bleConnectionUpdateStreamController.stream
.where((e) => e.deviceId == deviceId)
.listen(
(e) {
if (e.error != null) {
handleError(e.error);
} else {
if (!completer.isCompleted) {
completer.complete(e.isConnected);
}
}
},
onError: handleError,
cancelOnError: true,
);
_platform
.connect(deviceId, connectionTimeout: timeout)
.catchError(handleError);
if (!await completer.future.timeout(timeout)) {
throw ConnectionException("Failed to connect");
}
} finally {
connectionSubscription?.cancel();
if (!await completer.future.timeout(timeout)) {
throw ConnectionException("Failed to connect");
}
}
@@ -165,11 +141,45 @@ class UniversalBle {
String deviceId, {
Duration? timeout,
}) async {
return await _bleCommandQueue.queueCommand(
() => _platform.disconnect(deviceId),
timeout: timeout,
deviceId: deviceId,
);
timeout ??= const Duration(seconds: 60);
BleConnectionState? connectionState;
try {
connectionState = await _platform.getConnectionState(deviceId);
} catch (e) {
UniversalLogger.logError("Get connection state failed: $e");
}
if (connectionState == BleConnectionState.disconnected ||
connectionState == BleConnectionState.disconnecting) {
_platform.updateConnection(deviceId, false);
UniversalLogger.logInfo(
"Device $deviceId already disconnected: $connectionState",
);
return;
}
try {
Completer<bool> completer =
_connectionEventCompleter(deviceId, timeout: timeout);
await _bleCommandQueue
.queueCommand(() => _platform.disconnect(deviceId),
timeout: timeout, deviceId: deviceId)
.catchError(
(error) {
if (completer.isCompleted) return;
completer.completeError(ConnectionException(error));
},
);
if (await completer.future.timeout(timeout)) {
UniversalLogger.logError(
"Device $deviceId is still connected after disconnect attempt",
);
}
} catch (e) {
UniversalLogger.logError("Disconnect failed: $e");
}
}
/// Discover services of a device.
@@ -505,6 +515,52 @@ class UniversalBle {
return read(deviceId, service, characteristic, timeout: timeout);
}
static Completer<bool> _connectionEventCompleter(
String deviceId, {
Duration? timeout,
}) {
timeout ??= const Duration(seconds: 60);
StreamSubscription? connectionSubscription;
Completer<bool> completer = Completer();
void cancelSubscription() {
connectionSubscription?.cancel();
connectionSubscription = null;
}
void handleError(dynamic error) {
cancelSubscription();
if (completer.isCompleted) return;
completer.completeError(ConnectionException(error));
}
connectionSubscription = _platform
.bleConnectionUpdateStreamController.stream
.where((e) => e.deviceId == deviceId)
.listen(
(e) {
cancelSubscription();
if (e.error != null) {
handleError(e.error);
} else {
if (!completer.isCompleted) {
completer.complete(e.isConnected);
}
}
},
onError: handleError,
cancelOnError: true,
);
completer.future.timeout(timeout).then((_) {
cancelSubscription();
}).catchError((_) {
cancelSubscription();
});
return completer;
}
static Future<void> _sendBleInputPropertyCommand(
String deviceId,
String service,
@@ -133,10 +133,7 @@ class UniversalBleLinux extends UniversalBlePlatform {
@override
Future<BleConnectionState> getConnectionState(String deviceId) async {
BlueZDevice? device = _devices[deviceId] ??
_client.devices.cast<BlueZDevice?>().firstWhere(
(device) => device?.address == deviceId,
orElse: () => null);
BlueZDevice? device = _getDeviceById(deviceId);
bool connected = device?.connected ?? false;
return connected
? BleConnectionState.connected
@@ -155,12 +152,11 @@ class UniversalBleLinux extends UniversalBlePlatform {
@override
Future<void> disconnect(String deviceId) async {
final device = _findDeviceById(deviceId);
if (!device.connected) {
updateConnection(deviceId, false);
return;
final device = _getDeviceById(deviceId);
if (device?.connected == true) {
await device?.disconnect();
}
await device.disconnect();
updateConnection(deviceId, false);
}
@override
@@ -414,11 +410,10 @@ class UniversalBleLinux extends UniversalBlePlatform {
: AvailabilityState.poweredOff;
}
/// Find device by id from cache or from client
/// Throws exception if device not found
BlueZDevice _findDeviceById(String deviceId) {
final device = _devices[deviceId] ??
_client.devices.cast<BlueZDevice?>().firstWhere(
(device) => device?.address == deviceId,
orElse: () => null);
final device = _getDeviceById(deviceId);
if (device == null) {
throw UniversalBleException(
code: UniversalBleErrorCode.deviceNotFound,
@@ -428,6 +423,14 @@ class UniversalBleLinux extends UniversalBlePlatform {
return device;
}
/// Get device by id from cache or from client
BlueZDevice? _getDeviceById(String deviceId) {
return _devices[deviceId] ??
_client.devices.cast<BlueZDevice?>().firstWhere(
(device) => device?.address == deviceId,
orElse: () => null);
}
Future<void> _ensureInitialized() async {
if (isInitialized) return;
@@ -58,8 +58,6 @@ class UniversalBleWeb extends UniversalBlePlatform {
@override
Future<void> disconnect(String deviceId) async {
_cleanConnection(deviceId);
updateConnection(deviceId, false);
_getDeviceById(deviceId)?.disconnect();
}