fix: retain last valid gps data

ref:
This commit is contained in:
Janez T
2026-02-28 14:29:46 +01:00
parent 880cee5477
commit d587b1eeb1
2 changed files with 133 additions and 6 deletions

View File

@@ -335,12 +335,9 @@ class ContactsProvider with ChangeNotifier {
debugPrint(' ✅ Parsed new telemetry'); debugPrint(' ✅ Parsed new telemetry');
debugPrint(' New telemetry timestamp: ${telemetry.timestamp}'); debugPrint(' New telemetry timestamp: ${telemetry.timestamp}');
// Ignore placeholder GPS coordinates (0,0) so we don't overwrite a final incomingGps = telemetry.gpsLocation;
// previously known/saved position with invalid telemetry data. if (_isInvalidTelemetryGps(incomingGps)) {
if (_isInvalidTelemetryGps(telemetry.gpsLocation)) { // Always sanitize invalid placeholder GPS to null first.
debugPrint(
' ⚠️ Ignoring invalid telemetry GPS coordinates: ${telemetry.gpsLocation}',
);
telemetry = ContactTelemetry( telemetry = ContactTelemetry(
gpsLocation: null, gpsLocation: null,
batteryPercentage: telemetry.batteryPercentage, batteryPercentage: telemetry.batteryPercentage,
@@ -353,6 +350,25 @@ class ContactsProvider with ChangeNotifier {
); );
} }
// Keep last valid GPS for router/chat/room contacts when current
// telemetry does not provide a valid GPS fix.
if (_shouldRetainLastValidGps(contact, telemetry.gpsLocation)) {
debugPrint(
' ⚠️ Retaining last valid GPS. Incoming telemetry GPS is invalid/missing: $incomingGps',
);
final previousGps = _getValidGpsOrNull(contact.telemetry?.gpsLocation);
telemetry = ContactTelemetry(
gpsLocation: previousGps,
batteryPercentage: telemetry.batteryPercentage,
batteryMilliVolts: telemetry.batteryMilliVolts,
temperature: telemetry.temperature,
timestamp: telemetry.timestamp,
humidity: telemetry.humidity,
pressure: telemetry.pressure,
extraSensorData: telemetry.extraSensorData,
);
}
// Update contact with new telemetry AND last seen time // Update contact with new telemetry AND last seen time
// lastAdvert is Unix timestamp in seconds // lastAdvert is Unix timestamp in seconds
final currentTimestamp = (DateTime.now().millisecondsSinceEpoch / 1000) final currentTimestamp = (DateTime.now().millisecondsSinceEpoch / 1000)
@@ -390,6 +406,29 @@ class ContactsProvider with ChangeNotifier {
return lat.abs() < epsilon && lon.abs() < epsilon; return lat.abs() < epsilon && lon.abs() < epsilon;
} }
LatLng? _getValidGpsOrNull(LatLng? location) {
if (location == null || _isInvalidTelemetryGps(location)) {
return null;
}
return location;
}
bool _shouldRetainLastValidGps(Contact contact, LatLng? incomingGps) {
final isSupportedType =
contact.isChat || contact.isRepeater || contact.isRoom;
if (!isSupportedType) {
return false;
}
final hasPreviousValidGps =
_getValidGpsOrNull(contact.telemetry?.gpsLocation) != null;
if (!hasPreviousValidGps) {
return false;
}
return incomingGps == null;
}
/// Find contact by public key prefix (6 bytes) /// Find contact by public key prefix (6 bytes)
Contact? _findContactByPrefix(Uint8List prefix) { Contact? _findContactByPrefix(Uint8List prefix) {
if (prefix.length < 6) return null; if (prefix.length < 6) return null;

View File

@@ -9,6 +9,29 @@ import 'package:shared_preferences/shared_preferences.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
Uint8List createPublicKey(int seed) {
return Uint8List.fromList(List<int>.generate(32, (index) => seed + index));
}
Contact createContact({
required Uint8List key,
required ContactType type,
String? name,
}) {
return Contact(
publicKey: key,
type: type,
flags: 0,
outPathLen: 0,
outPath: Uint8List(64),
advName: name ?? 'Test Contact',
lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000,
advLat: (46.0569 * 1e6).toInt(),
advLon: (14.5058 * 1e6).toInt(),
lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000,
);
}
group('ContactsProvider.updateTelemetry', () { group('ContactsProvider.updateTelemetry', () {
late ContactsProvider provider; late ContactsProvider provider;
late Uint8List publicKey; late Uint8List publicKey;
@@ -106,5 +129,70 @@ void main() {
expect(updated.displayLocation!.latitude, closeTo(45.0001, 0.0001)); expect(updated.displayLocation!.latitude, closeTo(45.0001, 0.0001));
expect(updated.displayLocation!.longitude, closeTo(13.9999, 0.0001)); expect(updated.displayLocation!.longitude, closeTo(13.9999, 0.0001));
}); });
test(
'retains last valid gps for chat/repeater/room when telemetry gps is invalid or missing',
() {
final contactTypes = <ContactType>[
ContactType.chat,
ContactType.repeater,
ContactType.room,
];
for (var i = 0; i < contactTypes.length; i++) {
final scopedProvider = ContactsProvider();
final scopedKey = createPublicKey(16 + i);
final contactType = contactTypes[i];
scopedProvider.addOrUpdateContact(
createContact(
key: scopedKey,
type: contactType,
name: 'Contact ${contactType.name}',
),
);
final validGps = CayenneLppParser.createGpsData(
latitude: 45.1234,
longitude: 13.8765,
);
scopedProvider.updateTelemetry(scopedKey.sublist(0, 6), validGps);
// No GPS frame should keep previous valid GPS.
final batteryOnly = CayenneLppParser.createBatteryData(3.8);
scopedProvider.updateTelemetry(scopedKey.sublist(0, 6), batteryOnly);
var updated = scopedProvider.findContactByKey(scopedKey)!;
expect(updated.telemetry, isNotNull);
expect(updated.telemetry!.gpsLocation, isNotNull);
expect(
updated.telemetry!.gpsLocation!.latitude,
closeTo(45.1234, 0.0001),
);
expect(
updated.telemetry!.gpsLocation!.longitude,
closeTo(13.8765, 0.0001),
);
// Invalid 0,0 GPS frame should also keep previous valid GPS.
final invalidGps = CayenneLppParser.createGpsData(
latitude: 0.0,
longitude: 0.0,
);
scopedProvider.updateTelemetry(scopedKey.sublist(0, 6), invalidGps);
updated = scopedProvider.findContactByKey(scopedKey)!;
expect(updated.telemetry, isNotNull);
expect(updated.telemetry!.gpsLocation, isNotNull);
expect(
updated.telemetry!.gpsLocation!.latitude,
closeTo(45.1234, 0.0001),
);
expect(
updated.telemetry!.gpsLocation!.longitude,
closeTo(13.8765, 0.0001),
);
}
},
);
}); });
} }