mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-08 09:43:23 +10:00
Add contact settings and discovery features
- Implemented contact settings in localization files for Swedish, Ukrainian, and Chinese. - Added new DiscoveryContact model to handle discovered contacts. - Created DiscoveryScreen to display discovered contacts with filtering and sorting options. - Integrated contact discovery storage to persist discovered contacts. - Updated settings screen to include options for automatic contact addition. - Enhanced app bar and list filter widgets for better user experience. - Fixed variable naming inconsistencies in contact model.
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
import 'package:meshcore_open/models/discovery_contact.dart';
|
||||
import 'package:pointycastle/export.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
@@ -24,6 +25,7 @@ import '../storage/channel_message_store.dart';
|
||||
import '../storage/channel_order_store.dart';
|
||||
import '../storage/channel_settings_store.dart';
|
||||
import '../storage/channel_store.dart';
|
||||
import '../storage/contact_discovery_store.dart';
|
||||
import '../storage/contact_settings_store.dart';
|
||||
import '../storage/contact_store.dart';
|
||||
import '../storage/message_store.dart';
|
||||
@@ -111,6 +113,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
|
||||
final List<ScanResult> _scanResults = [];
|
||||
final List<Contact> _contacts = [];
|
||||
final List<DiscoveryContact> _discoveredContacts = [];
|
||||
final List<Channel> _channels = [];
|
||||
final Map<String, List<Message>> _conversations = {};
|
||||
final Map<int, List<ChannelMessage>> _channelMessages = {};
|
||||
@@ -155,6 +158,12 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
bool _batteryRequested = false;
|
||||
bool _awaitingSelfInfo = false;
|
||||
bool _preserveContactsOnRefresh = false;
|
||||
bool _autoAddUsers = false;
|
||||
bool _autoAddRepeaters = false;
|
||||
bool _autoAddRoomServers = false;
|
||||
bool _autoAddSensors = false;
|
||||
bool _overwriteOldest = false;
|
||||
|
||||
static const int _defaultMaxContacts = 32;
|
||||
static const int _defaultMaxChannels = 8;
|
||||
int _maxContacts = _defaultMaxContacts;
|
||||
@@ -195,6 +204,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
final ChannelSettingsStore _channelSettingsStore = ChannelSettingsStore();
|
||||
final ContactSettingsStore _contactSettingsStore = ContactSettingsStore();
|
||||
final ContactStore _contactStore = ContactStore();
|
||||
final ContactDiscoveryStore _discoveryContactStore = ContactDiscoveryStore();
|
||||
final ChannelStore _channelStore = ChannelStore();
|
||||
final UnreadStore _unreadStore = UnreadStore();
|
||||
List<Channel> _cachedChannels = [];
|
||||
@@ -242,6 +252,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
List<DiscoveryContact> get discoveredContacts {
|
||||
return List.unmodifiable(_discoveredContacts);
|
||||
}
|
||||
|
||||
List<Channel> get channels => List.unmodifiable(_channels);
|
||||
bool get isConnected => _state == MeshCoreConnectionState.connected;
|
||||
bool get isLoadingContacts => _isLoadingContacts;
|
||||
@@ -258,6 +272,11 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
int? get currentBwHz => _currentBwHz;
|
||||
int? get currentSf => _currentSf;
|
||||
int? get currentCr => _currentCr;
|
||||
bool? get autoAddUsers => _autoAddUsers;
|
||||
bool? get autoAddRepeaters => _autoAddRepeaters;
|
||||
bool? get autoAddRoomServers => _autoAddRoomServers;
|
||||
bool? get autoAddSensors => _autoAddSensors;
|
||||
bool? get autoAddOverwriteOldest => _overwriteOldest;
|
||||
bool? get clientRepeat => _clientRepeat;
|
||||
int? get firmwareVerCode => _firmwareVerCode;
|
||||
Map<String, String>? get currentCustomVars => _currentCustomVars;
|
||||
@@ -602,6 +621,13 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadDiscoveredContactCache() async {
|
||||
final cached = await _discoveryContactStore.loadContacts();
|
||||
_discoveredContacts
|
||||
..clear()
|
||||
..addAll(cached);
|
||||
}
|
||||
|
||||
Future<void> loadChannelSettings({int? maxChannels}) async {
|
||||
_channelSmazEnabled.clear();
|
||||
final channelCount = maxChannels ?? _maxChannels;
|
||||
@@ -852,6 +878,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
|
||||
// Fetch channels so we can track unread counts for incoming messages
|
||||
unawaited(getChannels());
|
||||
|
||||
// Load discovered contacts from storage
|
||||
unawaited(loadDiscoveredContactCache());
|
||||
} catch (e) {
|
||||
debugPrint("Connection error: $e");
|
||||
await disconnect(manual: false);
|
||||
@@ -972,6 +1001,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_deviceDisplayName = null;
|
||||
_deviceId = null;
|
||||
_contacts.clear();
|
||||
_discoveredContacts.clear();
|
||||
_conversations.clear();
|
||||
_loadedConversationKeys.clear();
|
||||
_selfPublicKey = null;
|
||||
@@ -1064,6 +1094,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
await requestBatteryStatus(force: true);
|
||||
await sendFrame(buildGetRadioSettingsFrame());
|
||||
await sendFrame(buildGetCustomVarsFrame());
|
||||
await sendFrame(buildGetAutoAddFlagsFrame());
|
||||
_scheduleSelfInfoRetry();
|
||||
}
|
||||
|
||||
@@ -1074,7 +1105,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
await sendFrame(buildAppStartFrame());
|
||||
await sendFrame(buildGetCustomVarsFrame());
|
||||
await requestBatteryStatus();
|
||||
|
||||
await sendFrame(buildGetAutoAddFlagsFrame());
|
||||
_scheduleSelfInfoRetry();
|
||||
}
|
||||
|
||||
@@ -1903,8 +1934,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
case respCodeChannelInfo:
|
||||
_handleChannelInfo(frame);
|
||||
break;
|
||||
case respCodeRadioSettings:
|
||||
_handleRadioSettings(frame);
|
||||
case respCodeAutoAddConfig:
|
||||
_handleAutoAddConfig(frame);
|
||||
break;
|
||||
case respCodeBattAndStorage:
|
||||
_handleBatteryAndStorage(frame);
|
||||
@@ -1985,6 +2016,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_selfLatitude = readInt32LE(frame, 36) / 1000000.0;
|
||||
_selfLongitude = readInt32LE(frame, 40) / 1000000.0;
|
||||
|
||||
if (frame.length >= 47 && frame[47] == 0x00) {
|
||||
sendFrame(buildSetOtherParamsFrame(0, 0, 0));
|
||||
}
|
||||
|
||||
// Radio settings (if frame is long enough)
|
||||
if (frame.length >= 58) {
|
||||
_currentFreqHz = readUint32LE(frame, 48);
|
||||
@@ -1992,7 +2027,6 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_currentSf = frame[56];
|
||||
_currentCr = frame[57];
|
||||
}
|
||||
|
||||
// Node name starts at offset 58 if frame is long enough
|
||||
if (frame.length > 58) {
|
||||
_selfName = readCString(frame, 58, frame.length - 58);
|
||||
@@ -2056,25 +2090,6 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
unawaited(_requestNextQueuedMessage());
|
||||
}
|
||||
|
||||
void _handleRadioSettings(Uint8List frame) {
|
||||
// Frame format from C++:
|
||||
// [0] = RESP_CODE_RADIO_SETTINGS
|
||||
// [1-4] = freq (uint32 LE, in Hz)
|
||||
// [5-8] = bw (uint32 LE, in Hz)
|
||||
// [9] = sf
|
||||
// [10] = cr
|
||||
if (frame.length >= 11) {
|
||||
_currentFreqHz = readUint32LE(frame, 1);
|
||||
_currentBwHz = readUint32LE(frame, 5);
|
||||
_currentSf = frame[9];
|
||||
_currentCr = frame[10];
|
||||
debugPrint(
|
||||
'Radio settings: freq=$_currentFreqHz bw=$_currentBwHz sf=$_currentSf cr=$_currentCr',
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleBatteryAndStorage(Uint8List frame) {
|
||||
// Frame format from C++:
|
||||
// [0] = RESP_CODE_BATT_AND_STORAGE
|
||||
@@ -2275,6 +2290,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
await _contactStore.saveContacts(_contacts);
|
||||
}
|
||||
|
||||
Future<void> _persistDiscoveredContacts() async {
|
||||
await _discoveryContactStore.saveContacts(_discoveredContacts);
|
||||
}
|
||||
|
||||
int _latestContactLastmod() {
|
||||
if (_contacts.isEmpty) return 0;
|
||||
var latest = 0;
|
||||
@@ -3739,6 +3758,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
//We ignore our own adverts
|
||||
if (listEquals(publicKey, _selfPublicKey)) {
|
||||
return;
|
||||
}
|
||||
@@ -3759,7 +3779,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
longitude: longitude,
|
||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
||||
);
|
||||
_handleContactAdvert(newContact);
|
||||
if ((_autoAddUsers && type == advTypeChat) ||
|
||||
(_autoAddRepeaters && type == advTypeRepeater) ||
|
||||
(_autoAddRoomServers && type == advTypeRoom) ||
|
||||
(_autoAddSensors && type == advTypeSensor)) {
|
||||
_handleContactAdvert(newContact);
|
||||
} else {
|
||||
_handleDiscovery(newContact);
|
||||
}
|
||||
_updateDirectRepeater(newContact, snr, path);
|
||||
return;
|
||||
}
|
||||
@@ -3847,6 +3874,50 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _handleAutoAddConfig(Uint8List frame) {
|
||||
final reader = BufferReader(frame);
|
||||
try {
|
||||
reader.skipBytes(1); // Skip the response code byte
|
||||
final flags = reader.readByte();
|
||||
_autoAddUsers = flags & autoAddChatFlag != 0;
|
||||
_autoAddRepeaters = flags & autoAddRepeaterFlag != 0;
|
||||
_autoAddRoomServers = flags & autoAddRoomServerFlag != 0;
|
||||
_autoAddSensors = flags & autoAddSensorFlag != 0;
|
||||
_overwriteOldest = flags & autoAddOverwriteOldestFlag != 0;
|
||||
} catch (e) {
|
||||
appLogger.error('Failed to parse auto-add config: $e', tag: 'Connector');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDiscovery(Contact contact) {
|
||||
debugPrint('Discovered new contact: ${contact.name}');
|
||||
final disContact = DiscoveryContact(
|
||||
publicKey: contact.publicKey,
|
||||
name: contact.name,
|
||||
type: contact.type,
|
||||
pathLength: contact.pathLength,
|
||||
path: contact.path,
|
||||
latitude: contact.latitude,
|
||||
longitude: contact.longitude,
|
||||
lastSeen: contact.lastSeen,
|
||||
);
|
||||
_discoveredContacts.add(disContact);
|
||||
|
||||
unawaited(_persistDiscoveredContacts());
|
||||
|
||||
// Show notification for new contact (advertisement)
|
||||
if (_appSettingsService != null) {
|
||||
final settings = _appSettingsService!.settings;
|
||||
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
|
||||
_notificationService.showAdvertNotification(
|
||||
contactName: contact.name,
|
||||
contactType: contact.typeLabel,
|
||||
contactId: contact.publicKeyHex,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int _phRouteMask = 0x03;
|
||||
|
||||
@@ -167,6 +167,8 @@ const int cmdGetTelemetryReq = 39;
|
||||
const int cmdGetCustomVar = 40;
|
||||
const int cmdSetCustomVar = 41;
|
||||
const int cmdSendBinaryReq = 50;
|
||||
const int cmdSetAutoAddConfig = 58;
|
||||
const int cmdGetAutoAddConfig = 59;
|
||||
|
||||
// Text message types
|
||||
const int txtTypePlain = 0;
|
||||
@@ -200,8 +202,8 @@ const int respCodeDeviceInfo = 13;
|
||||
const int respCodeContactMsgRecvV3 = 16;
|
||||
const int respCodeChannelMsgRecvV3 = 17;
|
||||
const int respCodeChannelInfo = 18;
|
||||
const int respCodeRadioSettings = 25;
|
||||
const int respCodeCustomVars = 21;
|
||||
const int respCodeAutoAddConfig = 25;
|
||||
|
||||
// Push codes (async from device)
|
||||
const int pushCodeAdvert = 0x80;
|
||||
@@ -247,6 +249,18 @@ const int payloadTypeCONTROL = 0x0B; // a control/discovery packet
|
||||
const int payloadTypeRawCustom =
|
||||
0x0F; // custom packet as raw bytes, for applications with custom encryption, payloads, etc
|
||||
|
||||
//auto-add flags
|
||||
const int autoAddOverwriteOldestFlag =
|
||||
1 << 0; // 0x01 - overwrite oldest non-favourite when full
|
||||
const int autoAddChatFlag =
|
||||
1 << 1; // 0x02 - auto-add Chat (Companion) (ADV_TYPE_CHAT)
|
||||
const int autoAddRepeaterFlag =
|
||||
1 << 2; // 0x04 - auto-add Repeater (ADV_TYPE_REPEATER)
|
||||
const int autoAddRoomServerFlag =
|
||||
1 << 3; // 0x08 - auto-add Room Server (ADV_TYPE_ROOM)
|
||||
const int autoAddSensorFlag =
|
||||
1 << 4; // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR)
|
||||
|
||||
// Sizes
|
||||
const int pubKeySize = 32;
|
||||
const int maxPathSize = 64;
|
||||
@@ -297,7 +311,7 @@ const int contactNameOffset = 100;
|
||||
const int contactTimestampOffset = 132;
|
||||
const int contactLatOffset = 136;
|
||||
const int contactLonOffset = 140;
|
||||
const int contactLastmodOffset = 144;
|
||||
const int contactLastModOffset = 144;
|
||||
const int contactFrameSize = 148;
|
||||
|
||||
// Message frame offsets
|
||||
@@ -685,6 +699,10 @@ Uint8List buildGetCustomVarsFrame() {
|
||||
return Uint8List.fromList([cmdGetCustomVar]);
|
||||
}
|
||||
|
||||
Uint8List buildGetAutoAddFlagsFrame() {
|
||||
return Uint8List.fromList([cmdGetAutoAddConfig]);
|
||||
}
|
||||
|
||||
// Calculate LoRa airtime for a packet
|
||||
// Based on Semtech SX127x datasheet formula
|
||||
// Returns airtime in milliseconds
|
||||
@@ -826,20 +844,40 @@ Uint8List buildZeroHopContact(Uint8List pubKey) {
|
||||
}
|
||||
|
||||
// Build CMD_SET_OTHER_PARAMS frame
|
||||
// Format: [cmd][allowAutoAddContacts][allowTelemetryFlags][advertLocationPolicy][multiAcks]
|
||||
// Format: [cmd][allowTelemetryFlags][advertLocationPolicy][multiAcks]
|
||||
Uint8List buildSetOtherParamsFrame(
|
||||
bool allowAutoAddContacts,
|
||||
int allowTelemetryFlags,
|
||||
int advertLocationPolicy,
|
||||
int multiAcks,
|
||||
) {
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSetOtherParams);
|
||||
writer.writeByte(
|
||||
allowAutoAddContacts ? 0x00 : 0x01,
|
||||
); // Allow Auto Add Contacts
|
||||
//Going forward the app will just set Auto Add Contacts to disabled, and use the filter flags
|
||||
//Allow Auto Add Contacts use inverted logic (0x01 = disabled, 0x00 = enabled).
|
||||
writer.writeByte(0x01);
|
||||
writer.writeByte(allowTelemetryFlags); // Allow Telemetry Flags
|
||||
writer.writeByte(advertLocationPolicy); // Advertisement Location Policy
|
||||
writer.writeByte(multiAcks); // Multi Acknowledgements
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
// Build CMD_SET_AUTO_ADD_CONFIG frame
|
||||
// Format: [cmd][flags]
|
||||
Uint8List buildSetAutoAddConfigFrame({
|
||||
required bool autoAddChat,
|
||||
required bool autoAddRepeater,
|
||||
required bool autoAddRoomServer,
|
||||
required bool autoAddSensor,
|
||||
required bool overwriteOldest,
|
||||
}) {
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSetAutoAddConfig);
|
||||
int flags = 0;
|
||||
if (autoAddChat) flags |= autoAddChatFlag;
|
||||
if (autoAddRepeater) flags |= autoAddRepeaterFlag;
|
||||
if (autoAddRoomServer) flags |= autoAddRoomServerFlag;
|
||||
if (autoAddSensor) flags |= autoAddSensorFlag;
|
||||
if (overwriteOldest) flags |= autoAddOverwriteOldestFlag;
|
||||
writer.writeByte(flags);
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user