BLE select cancel

This commit is contained in:
Ben Allfree
2026-02-22 08:05:19 -08:00
parent b4f79c1aae
commit 5522f9a236
2 changed files with 111 additions and 63 deletions
+93 -52
View File
@@ -686,64 +686,96 @@ class MeshCoreConnector extends ChangeNotifier {
}) async { }) async {
if (_state == MeshCoreConnectionState.scanning) return; if (_state == MeshCoreConnectionState.scanning) return;
_scanResults.clear(); try {
_setState(MeshCoreConnectionState.scanning);
// Ensure any previous scan is fully stopped
await FlutterBluePlus.stopScan();
await _scanSubscription?.cancel();
// On iOS/macOS, wait for Bluetooth to be powered on before scanning
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
// Wait for adapter state to be powered on
final adapterState = await FlutterBluePlus.adapterState.first;
if (adapterState != BluetoothAdapterState.on) {
// Wait for the adapter to turn on, with timeout
await FlutterBluePlus.adapterState
.firstWhere((state) => state == BluetoothAdapterState.on)
.timeout(
const Duration(seconds: 5),
onTimeout: () {
_setState(MeshCoreConnectionState.disconnected);
throw Exception('Bluetooth adapter not available');
},
);
}
// Add a small delay to allow BLE stack to fully initialize
await Future.delayed(const Duration(milliseconds: 300));
}
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
_scanResults.clear(); _scanResults.clear();
for (var result in results) { _setState(MeshCoreConnectionState.scanning);
if (result.device.platformName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("MeshCore-") || // Ensure any previous scan is fully stopped
result.advertisementData.advName.startsWith("Whisper-")) { try {
_scanResults.add(result); await FlutterBluePlus.stopScan();
} catch (_) {}
try {
await _scanSubscription?.cancel();
} catch (_) {}
_scanSubscription = null;
// On iOS/macOS, wait for Bluetooth to be powered on before scanning
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
// Wait for adapter state to be powered on
final adapterState = await FlutterBluePlus.adapterState.first;
if (adapterState != BluetoothAdapterState.on) {
// Wait for the adapter to turn on, with timeout
await FlutterBluePlus.adapterState
.firstWhere((state) => state == BluetoothAdapterState.on)
.timeout(
const Duration(seconds: 5),
onTimeout: () {
_setState(MeshCoreConnectionState.disconnected);
throw Exception('Bluetooth adapter not available');
},
);
} }
// Add a small delay to allow BLE stack to fully initialize
await Future.delayed(const Duration(milliseconds: 300));
} }
notifyListeners();
});
await FlutterBluePlus.startScan( _scanSubscription = FlutterBluePlus.scanResults.listen(
withServices: [Guid(MeshCoreUuids.service)], (results) {
timeout: timeout, _scanResults.clear();
androidScanMode: AndroidScanMode.lowLatency, for (var result in results) {
); if (result.device.platformName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("Whisper-")) {
_scanResults.add(result);
}
}
notifyListeners();
},
onError: (Object e) {
debugPrint("scanResults stream error: $e");
stopScan();
},
);
await Future.delayed(timeout); await FlutterBluePlus.startScan(
await stopScan(); withServices: [Guid(MeshCoreUuids.service)],
timeout: timeout,
androidScanMode: AndroidScanMode.lowLatency,
);
await Future.delayed(timeout);
} catch (e) {
debugPrint("Scan error: $e");
// On web, suppress common cancellation and chooser errors
if (kIsWeb) return;
if (!PlatformInfo.isWeb) {
rethrow;
}
} finally {
await stopScan();
}
} }
Future<void> stopScan() async { Future<void> stopScan() async {
await FlutterBluePlus.stopScan();
await _scanSubscription?.cancel();
_scanSubscription = null;
if (_state == MeshCoreConnectionState.scanning) { if (_state == MeshCoreConnectionState.scanning) {
_setState(MeshCoreConnectionState.disconnected); _setState(MeshCoreConnectionState.disconnected);
} }
try {
await FlutterBluePlus.stopScan();
} catch (e) {
debugPrint("stopScan error: $e");
}
try {
if (_scanSubscription != null) {
await _scanSubscription!.cancel();
}
} catch (_) {}
_scanSubscription = null;
} }
Future<void> connect(BluetoothDevice device, {String? displayName}) async { Future<void> connect(BluetoothDevice device, {String? displayName}) async {
@@ -770,11 +802,17 @@ class MeshCoreConnector extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
_connectionSubscription = device.connectionState.listen((state) { _connectionSubscription = device.connectionState.listen(
if (state == BluetoothConnectionState.disconnected && isConnected) { (state) {
_handleDisconnection(); if (state == BluetoothConnectionState.disconnected && isConnected) {
} _handleDisconnection();
}); }
},
onError: (Object e) {
debugPrint("connectionState stream error: $e");
if (isConnected) _handleDisconnection();
},
);
await device.connect( await device.connect(
timeout: const Duration(seconds: 15), timeout: const Duration(seconds: 15),
@@ -833,6 +871,9 @@ class MeshCoreConnector extends ChangeNotifier {
} }
_notifySubscription = _txCharacteristic!.onValueReceived.listen( _notifySubscription = _txCharacteristic!.onValueReceived.listen(
_handleFrame, _handleFrame,
onError: (Object e) {
debugPrint("onValueReceived stream error: $e");
},
); );
_setState(MeshCoreConnectionState.connected); _setState(MeshCoreConnectionState.connected);
+18 -11
View File
@@ -45,17 +45,22 @@ class _ScannerScreenState extends State<ScannerScreen> {
connector.addListener(_connectionListener); connector.addListener(_connectionListener);
_bluetoothStateSubscription = FlutterBluePlus.adapterState.listen((state) { _bluetoothStateSubscription = FlutterBluePlus.adapterState.listen(
if (mounted) { (state) {
setState(() { if (mounted) {
_bluetoothState = state; setState(() {
}); _bluetoothState = state;
// Cancel scan if Bluetooth turns off while scanning });
if (state != BluetoothAdapterState.on) { // Cancel scan if Bluetooth turns off while scanning
unawaited(connector.stopScan()); if (state != BluetoothAdapterState.on) {
unawaited(connector.stopScan());
}
} }
} },
}); onError: (Object e) {
debugPrint("Scanner adapterState stream error: $e");
},
);
} }
@override @override
@@ -107,7 +112,9 @@ class _ScannerScreenState extends State<ScannerScreen> {
if (isScanning) { if (isScanning) {
connector.stopScan(); connector.stopScan();
} else { } else {
connector.startScan(); unawaited(connector.startScan().catchError((e) {
debugPrint("Scanner screen startScan error: $e");
}));
} }
}, },
icon: isScanning icon: isScanning