mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-10 02:32:47 +10:00
Add initial contacts sync handling for web Bluetooth transport
This commit is contained in:
committed by
just-stuff-tm
parent
98f7c3b088
commit
ee3af52c0f
@@ -164,6 +164,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
bool _awaitingSelfInfo = false;
|
bool _awaitingSelfInfo = false;
|
||||||
bool _hasReceivedDeviceInfo = false;
|
bool _hasReceivedDeviceInfo = false;
|
||||||
bool _pendingInitialChannelSync = false;
|
bool _pendingInitialChannelSync = false;
|
||||||
|
bool _pendingInitialContactsSync = false;
|
||||||
bool _preserveContactsOnRefresh = false;
|
bool _preserveContactsOnRefresh = false;
|
||||||
static const int _defaultMaxContacts = 32;
|
static const int _defaultMaxContacts = 32;
|
||||||
static const int _defaultMaxChannels = 8;
|
static const int _defaultMaxChannels = 8;
|
||||||
@@ -783,6 +784,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_lastDeviceDisplayName = _deviceDisplayName;
|
_lastDeviceDisplayName = _deviceDisplayName;
|
||||||
_manualDisconnect = false;
|
_manualDisconnect = false;
|
||||||
_cancelReconnectTimer();
|
_cancelReconnectTimer();
|
||||||
|
if (PlatformInfo.isWeb) {
|
||||||
|
_resetConnectionHandshakeState();
|
||||||
|
}
|
||||||
unawaited(_backgroundService?.start());
|
unawaited(_backgroundService?.start());
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
@@ -799,12 +803,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
license: License.free,
|
license: License.free,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Request larger MTU for sending larger frames
|
// Request larger MTU only on native platforms; web does not support it.
|
||||||
try {
|
if (!PlatformInfo.isWeb) {
|
||||||
final mtu = await device.requestMtu(185);
|
try {
|
||||||
debugPrint('MTU set to: $mtu');
|
final mtu = await device.requestMtu(185);
|
||||||
} catch (e) {
|
debugPrint('MTU set to: $mtu');
|
||||||
debugPrint('MTU request failed: $e, using default');
|
} catch (e) {
|
||||||
|
debugPrint('MTU request failed: $e, using default');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BluetoothService> services = await device.discoverServices();
|
List<BluetoothService> services = await device.discoverServices();
|
||||||
@@ -834,17 +840,30 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
throw Exception("MeshCore characteristics not found");
|
throw Exception("MeshCore characteristics not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool notifySet = false;
|
if (PlatformInfo.isWeb) {
|
||||||
for (int attempt = 0; attempt < 3 && !notifySet; attempt++) {
|
debugPrint('Starting setNotifyValue(true)');
|
||||||
try {
|
debugPrint('Web: Calling setNotifyValue(true) without awaiting');
|
||||||
if (attempt > 0) {
|
unawaited(() async {
|
||||||
await Future.delayed(Duration(milliseconds: 500 * attempt));
|
try {
|
||||||
|
await _txCharacteristic!.setNotifyValue(true);
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint('Web setNotifyValue error (ignoring): $error');
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
debugPrint('setNotifyValue(true) configuration completed');
|
||||||
|
} else {
|
||||||
|
bool notifySet = false;
|
||||||
|
for (int attempt = 0; attempt < 3 && !notifySet; attempt++) {
|
||||||
|
try {
|
||||||
|
if (attempt > 0) {
|
||||||
|
await Future.delayed(Duration(milliseconds: 500 * attempt));
|
||||||
|
}
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_notifySubscription = _txCharacteristic!.onValueReceived.listen(
|
_notifySubscription = _txCharacteristic!.onValueReceived.listen(
|
||||||
@@ -852,22 +871,34 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
|
|
||||||
_setState(MeshCoreConnectionState.connected);
|
_setState(MeshCoreConnectionState.connected);
|
||||||
|
if (_shouldGateInitialChannelSync) {
|
||||||
|
_hasReceivedDeviceInfo = false;
|
||||||
|
_pendingInitialChannelSync = true;
|
||||||
|
}
|
||||||
|
|
||||||
await _requestDeviceInfo();
|
await _requestDeviceInfo();
|
||||||
_startBatteryPolling();
|
_startBatteryPolling();
|
||||||
final gotSelfInfo = await _waitForSelfInfo(
|
if (PlatformInfo.isWeb &&
|
||||||
timeout: const Duration(seconds: 3),
|
_activeTransport == MeshCoreTransportType.bluetooth) {
|
||||||
);
|
// Chrome's Web Bluetooth stack commonly delays incoming notifications
|
||||||
if (!gotSelfInfo) {
|
// until the non-blocking notify setup settles. Avoid stacking extra
|
||||||
await refreshDeviceInfo();
|
// startup writes while that is happening.
|
||||||
await _waitForSelfInfo(timeout: const Duration(seconds: 3));
|
} else {
|
||||||
|
final gotSelfInfo = await _waitForSelfInfo(
|
||||||
|
timeout: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
if (!gotSelfInfo) {
|
||||||
|
await refreshDeviceInfo();
|
||||||
|
await _waitForSelfInfo(timeout: const Duration(seconds: 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
unawaited(syncTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep device clock aligned on every connection.
|
|
||||||
await syncTime();
|
|
||||||
|
|
||||||
// Fetch channels so we can track unread counts for incoming messages
|
// Fetch channels so we can track unread counts for incoming messages
|
||||||
unawaited(getChannels());
|
if (!_shouldGateInitialChannelSync) {
|
||||||
|
unawaited(getChannels());
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Connection error: $e");
|
debugPrint("Connection error: $e");
|
||||||
await disconnect(manual: false);
|
await disconnect(manual: false);
|
||||||
@@ -981,6 +1012,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_selfInfoRetryTimer = null;
|
_selfInfoRetryTimer = null;
|
||||||
_hasReceivedDeviceInfo = false;
|
_hasReceivedDeviceInfo = false;
|
||||||
_pendingInitialChannelSync = false;
|
_pendingInitialChannelSync = false;
|
||||||
|
_pendingInitialContactsSync = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _shouldAutoReconnect =>
|
bool get _shouldAutoReconnect =>
|
||||||
@@ -988,6 +1020,11 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_lastDeviceId != null &&
|
_lastDeviceId != null &&
|
||||||
_activeTransport == MeshCoreTransportType.bluetooth;
|
_activeTransport == MeshCoreTransportType.bluetooth;
|
||||||
|
|
||||||
|
bool get _shouldGateInitialChannelSync =>
|
||||||
|
_activeTransport == MeshCoreTransportType.usb ||
|
||||||
|
(_activeTransport == MeshCoreTransportType.bluetooth &&
|
||||||
|
PlatformInfo.isWeb);
|
||||||
|
|
||||||
void _cancelReconnectTimer() {
|
void _cancelReconnectTimer() {
|
||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
_reconnectTimer = null;
|
_reconnectTimer = null;
|
||||||
@@ -1087,6 +1124,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_awaitingSelfInfo = false;
|
_awaitingSelfInfo = false;
|
||||||
_hasReceivedDeviceInfo = false;
|
_hasReceivedDeviceInfo = false;
|
||||||
_pendingInitialChannelSync = false;
|
_pendingInitialChannelSync = false;
|
||||||
|
_pendingInitialContactsSync = false;
|
||||||
_maxContacts = _defaultMaxContacts;
|
_maxContacts = _defaultMaxContacts;
|
||||||
_maxChannels = _defaultMaxChannels;
|
_maxChannels = _defaultMaxChannels;
|
||||||
_isSyncingQueuedMessages = false;
|
_isSyncingQueuedMessages = false;
|
||||||
@@ -1191,6 +1229,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
|
|
||||||
void _scheduleSelfInfoRetry() {
|
void _scheduleSelfInfoRetry() {
|
||||||
_selfInfoRetryTimer?.cancel();
|
_selfInfoRetryTimer?.cancel();
|
||||||
|
if (PlatformInfo.isWeb &&
|
||||||
|
_activeTransport == MeshCoreTransportType.bluetooth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_selfInfoRetryTimer = Timer.periodic(const Duration(milliseconds: 3500), (
|
_selfInfoRetryTimer = Timer.periodic(const Duration(milliseconds: 3500), (
|
||||||
timer,
|
timer,
|
||||||
) {
|
) {
|
||||||
@@ -1974,6 +2016,12 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_preserveContactsOnRefresh = false;
|
_preserveContactsOnRefresh = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
unawaited(_persistContacts());
|
unawaited(_persistContacts());
|
||||||
|
if (PlatformInfo.isWeb &&
|
||||||
|
_activeTransport == MeshCoreTransportType.bluetooth &&
|
||||||
|
_isSyncingChannels &&
|
||||||
|
!_channelSyncInFlight) {
|
||||||
|
unawaited(_requestNextChannel());
|
||||||
|
}
|
||||||
if (!_didInitialQueueSync || _pendingQueueSync) {
|
if (!_didInitialQueueSync || _pendingQueueSync) {
|
||||||
_didInitialQueueSync = true;
|
_didInitialQueueSync = true;
|
||||||
_pendingQueueSync = false;
|
_pendingQueueSync = false;
|
||||||
@@ -2120,16 +2168,22 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_selfInfoRetryTimer = null;
|
_selfInfoRetryTimer = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
// Auto-fetch contacts after getting self info
|
// Auto-fetch contacts after getting self info. On web BLE, defer this
|
||||||
getContacts();
|
// until after channel 0 so startup writes stay serialized.
|
||||||
if (_activeTransport == MeshCoreTransportType.usb) {
|
if (PlatformInfo.isWeb &&
|
||||||
|
_activeTransport == MeshCoreTransportType.bluetooth) {
|
||||||
|
_pendingInitialContactsSync = true;
|
||||||
|
} else {
|
||||||
|
getContacts();
|
||||||
|
}
|
||||||
|
if (_shouldGateInitialChannelSync) {
|
||||||
_maybeStartInitialChannelSync();
|
_maybeStartInitialChannelSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDeviceInfo(Uint8List frame) {
|
void _handleDeviceInfo(Uint8List frame) {
|
||||||
if (frame.length < 4) return;
|
if (frame.length < 4) return;
|
||||||
if (_activeTransport == MeshCoreTransportType.usb) {
|
if (_shouldGateInitialChannelSync) {
|
||||||
_hasReceivedDeviceInfo = true;
|
_hasReceivedDeviceInfo = true;
|
||||||
}
|
}
|
||||||
_firmwareVerCode = frame[1];
|
_firmwareVerCode = frame[1];
|
||||||
@@ -2156,14 +2210,13 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
unawaited(loadChannelSettings(maxChannels: nextMaxChannels));
|
unawaited(loadChannelSettings(maxChannels: nextMaxChannels));
|
||||||
unawaited(loadAllChannelMessages(maxChannels: nextMaxChannels));
|
unawaited(loadAllChannelMessages(maxChannels: nextMaxChannels));
|
||||||
if (isConnected &&
|
if (isConnected &&
|
||||||
(_activeTransport != MeshCoreTransportType.usb ||
|
(!_shouldGateInitialChannelSync || !_pendingInitialChannelSync)) {
|
||||||
!_pendingInitialChannelSync)) {
|
|
||||||
unawaited(getChannels(maxChannels: nextMaxChannels));
|
unawaited(getChannels(maxChannels: nextMaxChannels));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
if (_activeTransport == MeshCoreTransportType.usb) {
|
if (_shouldGateInitialChannelSync) {
|
||||||
_maybeStartInitialChannelSync();
|
_maybeStartInitialChannelSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3075,6 +3128,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
|
|
||||||
// Move to next channel
|
// Move to next channel
|
||||||
_nextChannelIndexToRequest++;
|
_nextChannelIndexToRequest++;
|
||||||
|
if (PlatformInfo.isWeb &&
|
||||||
|
_activeTransport == MeshCoreTransportType.bluetooth &&
|
||||||
|
channel.index == 0 &&
|
||||||
|
_pendingInitialContactsSync) {
|
||||||
|
_pendingInitialContactsSync = false;
|
||||||
|
unawaited(getContacts());
|
||||||
|
return;
|
||||||
|
}
|
||||||
unawaited(_requestNextChannel());
|
unawaited(_requestNextChannel());
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -3738,6 +3799,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
// They're only cleared on manual disconnect via disconnect() method
|
// They're only cleared on manual disconnect via disconnect() method
|
||||||
_hasReceivedDeviceInfo = false;
|
_hasReceivedDeviceInfo = false;
|
||||||
_pendingInitialChannelSync = false;
|
_pendingInitialChannelSync = false;
|
||||||
|
_pendingInitialContactsSync = false;
|
||||||
_maxContacts = _defaultMaxContacts;
|
_maxContacts = _defaultMaxContacts;
|
||||||
_maxChannels = _defaultMaxChannels;
|
_maxChannels = _defaultMaxChannels;
|
||||||
_isSyncingQueuedMessages = false;
|
_isSyncingQueuedMessages = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user