mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-14 22:55:12 +10:00
ble filtering
This commit is contained in:
@@ -30,7 +30,6 @@ import '../storage/message_store.dart';
|
|||||||
import '../storage/unread_store.dart';
|
import '../storage/unread_store.dart';
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
import '../utils/battery_utils.dart';
|
import '../utils/battery_utils.dart';
|
||||||
import '../utils/platform_info.dart';
|
|
||||||
import 'meshcore_protocol.dart';
|
import 'meshcore_protocol.dart';
|
||||||
|
|
||||||
class MeshCoreUuids {
|
class MeshCoreUuids {
|
||||||
@@ -686,111 +685,59 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
}) async {
|
}) async {
|
||||||
if (_state == MeshCoreConnectionState.scanning) return;
|
if (_state == MeshCoreConnectionState.scanning) return;
|
||||||
|
|
||||||
try {
|
_scanResults.clear();
|
||||||
_scanResults.clear();
|
_setState(MeshCoreConnectionState.scanning);
|
||||||
_setState(MeshCoreConnectionState.scanning);
|
|
||||||
|
|
||||||
// Ensure any previous scan is fully stopped
|
// Ensure any previous scan is fully stopped
|
||||||
try {
|
await FlutterBluePlus.stopScan();
|
||||||
await FlutterBluePlus.stopScan();
|
await _scanSubscription?.cancel();
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
try {
|
// On iOS/macOS, wait for Bluetooth to be powered on before scanning
|
||||||
await _scanSubscription?.cancel();
|
if (defaultTargetPlatform == TargetPlatform.iOS ||
|
||||||
} catch (_) {}
|
defaultTargetPlatform == TargetPlatform.macOS) {
|
||||||
_scanSubscription = null;
|
// Wait for adapter state to be powered on
|
||||||
|
final adapterState = await FlutterBluePlus.adapterState.first;
|
||||||
// On iOS/macOS, wait for Bluetooth to be powered on before scanning
|
if (adapterState != BluetoothAdapterState.on) {
|
||||||
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
|
// Wait for the adapter to turn on, with timeout
|
||||||
// Wait for adapter state to be powered on
|
await FlutterBluePlus.adapterState
|
||||||
final adapterState = await FlutterBluePlus.adapterState.first;
|
.firstWhere((state) => state == BluetoothAdapterState.on)
|
||||||
if (adapterState != BluetoothAdapterState.on) {
|
.timeout(
|
||||||
// Wait for the adapter to turn on, with timeout
|
const Duration(seconds: 5),
|
||||||
await FlutterBluePlus.adapterState
|
onTimeout: () {
|
||||||
.firstWhere((state) => state == BluetoothAdapterState.on)
|
_setState(MeshCoreConnectionState.disconnected);
|
||||||
.timeout(
|
throw Exception('Bluetooth adapter not available');
|
||||||
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(
|
// Add a small delay to allow BLE stack to fully initialize
|
||||||
(results) {
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
_scanResults.clear();
|
|
||||||
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();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (PlatformInfo.isWeb) {
|
|
||||||
await FlutterBluePlus.startScan(
|
|
||||||
withServices: [Guid(MeshCoreUuids.service)],
|
|
||||||
);
|
|
||||||
// On web, the chooser returns once a device is picked, but the scanResults
|
|
||||||
// stream might take a moment to emit the last result. Wait briefly so the
|
|
||||||
// device appears in the UI before stopScan() clears the list.
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
} else {
|
|
||||||
await FlutterBluePlus.startScan(
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||||
|
_scanResults.clear();
|
||||||
|
_scanResults.addAll(results);
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
await FlutterBluePlus.startScan(
|
||||||
|
withKeywords: ["MeshCore-", "Whisper-"],
|
||||||
|
webOptionalServices: [Guid(MeshCoreUuids.service)],
|
||||||
|
timeout: timeout,
|
||||||
|
androidScanMode: AndroidScanMode.lowLatency,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future.delayed(timeout);
|
||||||
|
await stopScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopScan() async {
|
Future<void> stopScan() async {
|
||||||
if (_state == MeshCoreConnectionState.scanning) {
|
await FlutterBluePlus.stopScan();
|
||||||
_setState(MeshCoreConnectionState.disconnected);
|
await _scanSubscription?.cancel();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await FlutterBluePlus.stopScan();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("stopScan error: $e");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (_scanSubscription != null) {
|
|
||||||
await _scanSubscription!.cancel();
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
_scanSubscription = null;
|
_scanSubscription = null;
|
||||||
|
|
||||||
// On web, don't clear results immediately so the picked device remains visible
|
if (_state == MeshCoreConnectionState.scanning) {
|
||||||
if (!PlatformInfo.isWeb) {
|
_setState(MeshCoreConnectionState.disconnected);
|
||||||
_scanResults.clear();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,17 +765,11 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_connectionSubscription = device.connectionState.listen(
|
_connectionSubscription = device.connectionState.listen((state) {
|
||||||
(state) {
|
if (state == BluetoothConnectionState.disconnected && isConnected) {
|
||||||
if (state == BluetoothConnectionState.disconnected && isConnected) {
|
_handleDisconnection();
|
||||||
_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),
|
||||||
@@ -837,17 +778,11 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Request larger MTU for sending larger frames
|
// Request larger MTU for sending larger frames
|
||||||
if (!PlatformInfo.isWeb) {
|
try {
|
||||||
try {
|
final mtu = await device.requestMtu(185);
|
||||||
final mtu = await device.requestMtu(185);
|
debugPrint('MTU set to: $mtu');
|
||||||
debugPrint('MTU set to: $mtu');
|
} catch (e) {
|
||||||
} catch (e) {
|
debugPrint('MTU request failed: $e, using default');
|
||||||
debugPrint('MTU request failed: $e, using default');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// On Chrome Web Bluetooth, give the GATT connection a moment to settle
|
|
||||||
// before discovering services, which is a common quirk to avoid timeouts.
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BluetoothService> services = await device.discoverServices();
|
List<BluetoothService> services = await device.discoverServices();
|
||||||
@@ -877,44 +812,23 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
throw Exception("MeshCore characteristics not found");
|
throw Exception("MeshCore characteristics not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup listener BEFORE enabling notifications so we don't miss anything
|
// Retry setNotifyValue with increasing delays
|
||||||
_notifySubscription = _txCharacteristic!.onValueReceived.listen(
|
bool notifySet = false;
|
||||||
_handleFrame,
|
for (int attempt = 0; attempt < 3 && !notifySet; attempt++) {
|
||||||
onError: (Object e) {
|
try {
|
||||||
debugPrint("onValueReceived stream error: $e");
|
if (attempt > 0) {
|
||||||
},
|
await Future.delayed(Duration(milliseconds: 500 * attempt));
|
||||||
);
|
|
||||||
|
|
||||||
debugPrint('Starting setNotifyValue(true)');
|
|
||||||
if (PlatformInfo.isWeb) {
|
|
||||||
// On Web, setNotifyValue often hangs indefinitely on the Promise resolution.
|
|
||||||
// We trigger it but don't await its completion to avoid blocking the connection flow.
|
|
||||||
debugPrint('Web: Calling setNotifyValue(true) without awaiting');
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
_txCharacteristic!.setNotifyValue(true, timeout: 2).catchError((e) {
|
|
||||||
debugPrint('Web setNotifyValue error (ignoring): $e');
|
|
||||||
return false; // catchError must return a bool to match Future<bool>
|
|
||||||
});
|
|
||||||
// Give the browser a moment to process the underlying startNotifications call
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
} else {
|
|
||||||
// Native platforms handle setNotifyValue blockingly with CCCD descriptors
|
|
||||||
bool notifySet = false;
|
|
||||||
for (int attempt = 0; attempt < 3 && !notifySet; attempt++) {
|
|
||||||
try {
|
|
||||||
if (attempt > 0) {
|
|
||||||
await Future.delayed(Duration(milliseconds: 500 * attempt));
|
|
||||||
}
|
|
||||||
debugPrint('Calling setNotifyValue(true), attempt ${attempt + 1}');
|
|
||||||
await _txCharacteristic!.setNotifyValue(true);
|
|
||||||
notifySet = true;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('setNotifyValue attempt ${attempt + 1}/3 failed: $e');
|
|
||||||
if (attempt == 2) rethrow;
|
|
||||||
}
|
}
|
||||||
|
await _txCharacteristic!.setNotifyValue(true);
|
||||||
|
notifySet = true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('setNotifyValue attempt ${attempt + 1}/3 failed: $e');
|
||||||
|
if (attempt == 2) rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debugPrint('setNotifyValue(true) configuration completed');
|
_notifySubscription = _txCharacteristic!.onValueReceived.listen(
|
||||||
|
_handleFrame,
|
||||||
|
);
|
||||||
|
|
||||||
_setState(MeshCoreConnectionState.connected);
|
_setState(MeshCoreConnectionState.connected);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user