diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index d7b3ea4..3f47a9c 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -236,11 +236,12 @@ class AppProvider with ChangeNotifier { // When a contact's routing path is updated in the mesh network connectionProvider.onPathUpdated = (publicKey) { debugPrint('🔄 [AppProvider] Path updated for contact: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}...'); - // Trigger a contact sync to get the updated path information + // Trigger a single contact fetch to get the updated path information + // This is much more efficient than fetching all contacts // This happens asynchronously to avoid blocking the event handler Future.delayed(const Duration(milliseconds: 100), () { if (connectionProvider.deviceInfo.isConnected) { - connectionProvider.getContacts(); + connectionProvider.getContact(publicKey); } }); }; @@ -252,11 +253,12 @@ class AppProvider with ChangeNotifier { // Check if this is an existing contact that might have updated location final contact = contactsProvider.findContactByKey(publicKey); if (contact != null) { - debugPrint(' Existing contact "${contact.advName}" - triggering contact sync for updates'); - // Trigger a contact sync to get the updated contact information + debugPrint(' Existing contact "${contact.advName}" - fetching updated contact info (optimized)'); + // Trigger a single contact fetch to get the updated contact information + // This is much more efficient than fetching all contacts Future.delayed(const Duration(milliseconds: 100), () { if (connectionProvider.deviceInfo.isConnected) { - connectionProvider.getContacts(); + connectionProvider.getContact(publicKey); } }); } else { diff --git a/lib/providers/connection_provider.dart b/lib/providers/connection_provider.dart index b5dc1bb..32431bd 100644 --- a/lib/providers/connection_provider.dart +++ b/lib/providers/connection_provider.dart @@ -686,6 +686,30 @@ class ConnectionProvider with ChangeNotifier { } } + /// Get a single contact by public key from device + /// + /// This is more efficient than getContacts() when you only need to refresh + /// one specific contact (e.g., after receiving an advertisement or path update). + /// + /// The contact will be delivered via the onContactReceived callback. + Future getContact(Uint8List publicKey) async { + if (!_bleService.isConnected) { + _error = 'Not connected to device'; + notifyListeners(); + return; + } + + try { + await _bleService.getContactByKey(publicKey); + } catch (e) { + _error = 'Failed to get contact: $e'; + debugPrint('⚠️ [Provider] Failed to get contact by key, falling back to full contact sync'); + // Fallback to full contact sync if command not supported + await _bleService.getContacts(); + notifyListeners(); + } + } + /// Sync all channels from device Future syncChannels({int? maxChannels}) async { if (!_bleService.isConnected) { diff --git a/lib/services/meshcore_ble_service.dart b/lib/services/meshcore_ble_service.dart index 98041ca..038d98b 100644 --- a/lib/services/meshcore_ble_service.dart +++ b/lib/services/meshcore_ble_service.dart @@ -311,6 +311,20 @@ class MeshCoreBleService { await _commandSender.writeData(FrameBuilder.buildGetContacts()); } + /// Get a single contact by public key from device + /// + /// This is more efficient than getContacts() when you only need to refresh + /// one specific contact (e.g., after receiving an advertisement). + /// + /// The contact will be delivered via the onContactReceived callback. + Future getContactByKey(Uint8List publicKey) async { + debugPrint('🔍 [BLE] Requesting single contact by key:'); + debugPrint( + ' Public key prefix: ${publicKey.sublist(0, 6).map((b) => b.toRadixString(16).padLeft(2, '0')).join(':')}...', + ); + await _commandSender.writeData(FrameBuilder.buildGetContactByKey(publicKey)); + } + /// Manually add or update a contact on the companion radio Future addOrUpdateContact(Contact contact) async { debugPrint('📝 [BLE] Adding/updating contact on companion radio:'); diff --git a/lib/services/meshcore_constants.dart b/lib/services/meshcore_constants.dart index 36cee01..2c9d3c5 100644 --- a/lib/services/meshcore_constants.dart +++ b/lib/services/meshcore_constants.dart @@ -39,6 +39,7 @@ class MeshCoreConstants { static const int cmdSendRawData = 25; static const int cmdSendLogin = 26; static const int cmdSendStatusReq = 27; + static const int cmdGetContactByKey = 30; static const int cmdGetChannel = 31; static const int cmdSetChannel = 32; static const int cmdSignStart = 33; diff --git a/lib/services/meshcore_opcode_names.dart b/lib/services/meshcore_opcode_names.dart index ecb70a1..07a2dfb 100644 --- a/lib/services/meshcore_opcode_names.dart +++ b/lib/services/meshcore_opcode_names.dart @@ -59,6 +59,8 @@ class MeshCoreOpcodeNames { return 'SEND_LOGIN'; case MeshCoreConstants.cmdSendStatusReq: return 'SEND_STATUS_REQ'; + case MeshCoreConstants.cmdGetContactByKey: + return 'GET_CONTACT_BY_KEY'; case MeshCoreConstants.cmdGetChannel: return 'GET_CHANNEL'; case MeshCoreConstants.cmdSetChannel: diff --git a/lib/services/protocol/frame_builder.dart b/lib/services/protocol/frame_builder.dart index 7ae669a..cd37ef4 100644 --- a/lib/services/protocol/frame_builder.dart +++ b/lib/services/protocol/frame_builder.dart @@ -31,6 +31,14 @@ class FrameBuilder { return writer.toBytes(); } + /// Build GetContactByKey command - retrieves a single contact by public key + static Uint8List buildGetContactByKey(Uint8List publicKey) { + final writer = BufferWriter(); + writer.writeByte(MeshCoreConstants.cmdGetContactByKey); // 0x1E (30) + writer.writeBytes(publicKey); // 32 bytes + return writer.toBytes(); + } + /// Build AddUpdateContact command static Uint8List buildAddUpdateContact(Contact contact) { final writer = BufferWriter();