mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-27 12:47:31 +10:00
Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes
This commit is contained in:
@@ -54,6 +54,19 @@ class DirectRepeater {
|
|||||||
lastUpdated = DateTime.now();
|
lastUpdated = DateTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get ranking {
|
||||||
|
if (isStale()) {
|
||||||
|
return -1; // Stale repeaters get lowest rank
|
||||||
|
}
|
||||||
|
// Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties.
|
||||||
|
final ageMs =
|
||||||
|
DateTime.now().millisecondsSinceEpoch -
|
||||||
|
lastUpdated.millisecondsSinceEpoch;
|
||||||
|
final maxAgeMs = maxAgeMinutes * 60 * 1000;
|
||||||
|
final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs);
|
||||||
|
return (snr * 1000).round() + recencyScore;
|
||||||
|
}
|
||||||
|
|
||||||
bool isStale() {
|
bool isStale() {
|
||||||
return DateTime.now().difference(lastUpdated) >
|
return DateTime.now().difference(lastUpdated) >
|
||||||
const Duration(minutes: maxAgeMinutes);
|
const Duration(minutes: maxAgeMinutes);
|
||||||
@@ -3466,8 +3479,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publicKey == _selfPublicKey) {
|
if (listEquals(publicKey, _selfPublicKey)) {
|
||||||
appLogger.info('Ignoring advert from self', tag: 'Connector');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3480,7 +3492,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
pathLength: path.length,
|
pathLength: path.length,
|
||||||
path: path,
|
path: Uint8List.fromList(
|
||||||
|
path.reversed.toList(),
|
||||||
|
), // Store path in reverse for easier use in outgoing messages
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
||||||
@@ -3510,7 +3524,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
latitude: hasLocation ? latitude : existing.latitude,
|
latitude: hasLocation ? latitude : existing.latitude,
|
||||||
longitude: hasLocation ? longitude : existing.longitude,
|
longitude: hasLocation ? longitude : existing.longitude,
|
||||||
name: hasName ? name : existing.name,
|
name: hasName ? name : existing.name,
|
||||||
path: path,
|
path: Uint8List.fromList(path.reversed.toList()),
|
||||||
pathLength: path.length,
|
pathLength: path.length,
|
||||||
lastMessageAt: mergedLastMessageAt,
|
lastMessageAt: mergedLastMessageAt,
|
||||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
||||||
@@ -3518,6 +3532,12 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
pathOverrideBytes: existing.pathOverrideBytes,
|
pathOverrideBytes: existing.pathOverrideBytes,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add path to history if we have a valid path
|
||||||
|
if (_pathHistoryService != null &&
|
||||||
|
_contacts[existingIndex].pathLength >= 0) {
|
||||||
|
_pathHistoryService!.handlePathUpdated(_contacts[existingIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
_updateDirectRepeater(_contacts[existingIndex], snr, path);
|
_updateDirectRepeater(_contacts[existingIndex], snr, path);
|
||||||
|
|
||||||
appLogger.info(
|
appLogger.info(
|
||||||
|
|||||||
@@ -437,6 +437,20 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
builder: (context) => Consumer<PathHistoryService>(
|
builder: (context) => Consumer<PathHistoryService>(
|
||||||
builder: (context, pathService, _) {
|
builder: (context, pathService, _) {
|
||||||
final paths = pathService.getRecentPaths(widget.contact.publicKeyHex);
|
final paths = pathService.getRecentPaths(widget.contact.publicKeyHex);
|
||||||
|
|
||||||
|
final repeatersList = List.of(connector.directRepeaters)
|
||||||
|
..sort((a, b) => b.ranking.compareTo(a.ranking));
|
||||||
|
|
||||||
|
final directRepeater = repeatersList.isEmpty
|
||||||
|
? null
|
||||||
|
: repeatersList.first;
|
||||||
|
final secondDirectRepeater = repeatersList.length < 2
|
||||||
|
? null
|
||||||
|
: repeatersList.elementAt(1);
|
||||||
|
final thirdDirectRepeater = repeatersList.length < 3
|
||||||
|
? null
|
||||||
|
: repeatersList.elementAt(2);
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -478,15 +492,38 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
],
|
],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
...paths.map((path) {
|
...paths.map((path) {
|
||||||
|
final isDirectRepeater =
|
||||||
|
directRepeater != null &&
|
||||||
|
path.pathBytes.isNotEmpty &&
|
||||||
|
directRepeater.pubkeyFirstByte ==
|
||||||
|
path.pathBytes.first;
|
||||||
|
final isSecoundDirectRepeater =
|
||||||
|
secondDirectRepeater != null &&
|
||||||
|
path.pathBytes.isNotEmpty &&
|
||||||
|
secondDirectRepeater.pubkeyFirstByte ==
|
||||||
|
path.pathBytes.first;
|
||||||
|
final isThirdDirectRepeater =
|
||||||
|
thirdDirectRepeater != null &&
|
||||||
|
path.pathBytes.isNotEmpty &&
|
||||||
|
thirdDirectRepeater.pubkeyFirstByte ==
|
||||||
|
path.pathBytes.first;
|
||||||
|
Color color = Colors.grey;
|
||||||
|
if (isDirectRepeater) {
|
||||||
|
color = Colors.green;
|
||||||
|
} else if (isSecoundDirectRepeater) {
|
||||||
|
color = Colors.yellow;
|
||||||
|
} else if (isThirdDirectRepeater) {
|
||||||
|
color = Colors.red;
|
||||||
|
} else if (path.wasFloodDiscovery) {
|
||||||
|
color = Colors.blue;
|
||||||
|
}
|
||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
backgroundColor: path.wasFloodDiscovery
|
backgroundColor: color,
|
||||||
? Colors.blue
|
|
||||||
: Colors.green,
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'${path.hopCount}',
|
'${path.hopCount}',
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
|
|||||||
@@ -134,23 +134,18 @@ class _PathManagementDialog extends StatelessWidget {
|
|||||||
final currentContact = _resolveContact(connector);
|
final currentContact = _resolveContact(connector);
|
||||||
final paths = pathService.getRecentPaths(currentContact.publicKeyHex);
|
final paths = pathService.getRecentPaths(currentContact.publicKeyHex);
|
||||||
|
|
||||||
final RepeatersList = List.of(connector.directRepeaters)
|
final repeatersList = List.of(connector.directRepeaters)
|
||||||
..sort((a, b) => b.lastUpdated.compareTo(a.lastUpdated));
|
..sort((a, b) => b.ranking.compareTo(a.ranking));
|
||||||
|
|
||||||
final topSNRRepeaters = List.of(RepeatersList)
|
final directRepeater = repeatersList.isEmpty
|
||||||
..sort((a, b) => b.snr.compareTo(a.snr));
|
|
||||||
|
|
||||||
final topThreeRepeaters = topSNRRepeaters.take(3).toList();
|
|
||||||
|
|
||||||
final directRepeater = topThreeRepeaters.isEmpty
|
|
||||||
? null
|
? null
|
||||||
: topThreeRepeaters.first;
|
: repeatersList.first;
|
||||||
final secondDirectRepeater = topThreeRepeaters.length < 2
|
final secondDirectRepeater = repeatersList.length < 2
|
||||||
? null
|
? null
|
||||||
: topThreeRepeaters.elementAt(1);
|
: repeatersList.elementAt(1);
|
||||||
final thirdDirectRepeater = topThreeRepeaters.length < 3
|
final thirdDirectRepeater = repeatersList.length < 3
|
||||||
? null
|
? null
|
||||||
: topThreeRepeaters.elementAt(2);
|
: repeatersList.elementAt(2);
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(l10n.chat_pathManagement),
|
title: Text(l10n.chat_pathManagement),
|
||||||
@@ -206,6 +201,7 @@ class _PathManagementDialog extends StatelessWidget {
|
|||||||
path.pathBytes.isNotEmpty &&
|
path.pathBytes.isNotEmpty &&
|
||||||
thirdDirectRepeater.pubkeyFirstByte ==
|
thirdDirectRepeater.pubkeyFirstByte ==
|
||||||
path.pathBytes.first;
|
path.pathBytes.first;
|
||||||
|
|
||||||
Color color = Colors.grey;
|
Color color = Colors.grey;
|
||||||
if (isDirectRepeater) {
|
if (isDirectRepeater) {
|
||||||
color = Colors.green;
|
color = Colors.green;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class _SNRIndicatorState extends State<SNRIndicator> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final directRepeaters = widget.connector.directRepeaters;
|
final directRepeaters = widget.connector.directRepeaters;
|
||||||
final directBestRepeaters = List.of(directRepeaters)
|
final directBestRepeaters = List.of(directRepeaters)
|
||||||
..sort((a, b) => (b.snr).compareTo(a.snr));
|
..sort((a, b) => (b.ranking).compareTo(a.ranking));
|
||||||
final directRepeater = directBestRepeaters.isEmpty
|
final directRepeater = directBestRepeaters.isEmpty
|
||||||
? null
|
? null
|
||||||
: directBestRepeaters.first;
|
: directBestRepeaters.first;
|
||||||
|
|||||||
Reference in New Issue
Block a user