mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: RSSI trilateration from multiple repeaters
When a contact has no GPS, estimate their position from RSSI observations through multiple repeaters: - 1 repeater: offset by RSSI distance in deterministic direction - 2 repeaters: weighted midpoint between circle intersections - 3+ repeaters: weighted centroid with inverse-square weighting (closer repeater observations dominate) Observations stored per-repeater (latest wins), max 8 per contact, expire after 30 minutes. Each incoming message updates the estimate.
This commit is contained in:
@@ -8,6 +8,7 @@ import '../models/message_contact_location.dart';
|
||||
import '../services/cayenne_lpp_parser.dart';
|
||||
import '../services/contact_storage_service.dart';
|
||||
import '../utils/fast_gps_packet.dart';
|
||||
import '../utils/rssi_location_estimator.dart';
|
||||
import '../utils/key_comparison.dart';
|
||||
|
||||
class PendingAdvert {
|
||||
@@ -129,6 +130,7 @@ class ContactsProvider with ChangeNotifier {
|
||||
final List<SavedContactGroup> _savedContactGroups = <SavedContactGroup>[];
|
||||
final Map<String, PendingAdvert> _pendingAdverts = {};
|
||||
final Map<String, LatLng> _estimatedLocations = {};
|
||||
final Map<String, List<RssiObservation>> _rssiObservations = {};
|
||||
final ContactStorageService _storageService = ContactStorageService();
|
||||
bool _isInitialized = false;
|
||||
bool _isPersisting = false;
|
||||
@@ -516,6 +518,44 @@ class ContactsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Record an RSSI observation and re-trilaterate the contact's position.
|
||||
///
|
||||
/// Keeps up to 5 observations per unique repeater (latest wins).
|
||||
/// With multiple repeaters, trilateration produces a better estimate.
|
||||
void addRssiObservation({
|
||||
required String contactPublicKeyHex,
|
||||
required List<int> contactPublicKey,
|
||||
required RssiObservation observation,
|
||||
}) {
|
||||
final observations = _rssiObservations.putIfAbsent(
|
||||
contactPublicKeyHex,
|
||||
() => [],
|
||||
);
|
||||
|
||||
// Replace existing observation from the same repeater, or add new
|
||||
final repeaterKey =
|
||||
'${observation.repeaterLocation.latitude},${observation.repeaterLocation.longitude}';
|
||||
observations.removeWhere((o) =>
|
||||
'${o.repeaterLocation.latitude},${o.repeaterLocation.longitude}' ==
|
||||
repeaterKey);
|
||||
observations.add(observation);
|
||||
|
||||
// Keep at most 8 observations (most recent per repeater)
|
||||
if (observations.length > 8) {
|
||||
observations.sort((a, b) => b.observedAt.compareTo(a.observedAt));
|
||||
observations.removeRange(8, observations.length);
|
||||
}
|
||||
|
||||
final estimated = RssiLocationEstimator.trilaterate(
|
||||
observations: observations,
|
||||
contactPublicKey: contactPublicKey,
|
||||
);
|
||||
if (estimated != null) {
|
||||
_estimatedLocations[contactPublicKeyHex] = estimated;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Effective location: own GPS/advert > estimated from RSSI.
|
||||
LatLng? effectiveLocationFor(Contact contact) {
|
||||
return contact.displayLocation ?? _estimatedLocations[contact.publicKeyHex];
|
||||
|
||||
Reference in New Issue
Block a user