From 8d0aa7849e2793dd23f3db8e097dbd6b5dd6aa66 Mon Sep 17 00:00:00 2001 From: Janez T Date: Sat, 28 Feb 2026 14:17:52 +0100 Subject: [PATCH] fix: ignore zeroed telemetry gps coords Fixes #4\nRefs #5\n\nref: --- lib/providers/contacts_provider.dart | 33 ++++++- test/providers/contacts_provider_test.dart | 110 +++++++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 test/providers/contacts_provider_test.dart diff --git a/lib/providers/contacts_provider.dart b/lib/providers/contacts_provider.dart index c6321a6..710f1ee 100644 --- a/lib/providers/contacts_provider.dart +++ b/lib/providers/contacts_provider.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; +import 'package:latlong2/latlong.dart'; import '../models/contact.dart'; import '../services/cayenne_lpp_parser.dart'; import '../services/contact_storage_service.dart'; @@ -330,10 +331,28 @@ class ContactsProvider with ChangeNotifier { try { // Parse Cayenne LPP data - final telemetry = CayenneLppParser.parse(lppData); + var telemetry = CayenneLppParser.parse(lppData); debugPrint(' ✅ Parsed new telemetry'); debugPrint(' New telemetry timestamp: ${telemetry.timestamp}'); + // Ignore placeholder GPS coordinates (0,0) so we don't overwrite a + // previously known/saved position with invalid telemetry data. + if (_isInvalidTelemetryGps(telemetry.gpsLocation)) { + debugPrint( + ' ⚠️ Ignoring invalid telemetry GPS coordinates: ${telemetry.gpsLocation}', + ); + telemetry = ContactTelemetry( + gpsLocation: null, + 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 // lastAdvert is Unix timestamp in seconds final currentTimestamp = (DateTime.now().millisecondsSinceEpoch / 1000) @@ -359,6 +378,18 @@ class ContactsProvider with ChangeNotifier { } } + bool _isInvalidTelemetryGps(LatLng? location) { + if (location == null) return false; + final lat = location.latitude; + final lon = location.longitude; + + if (!lat.isFinite || !lon.isFinite) return true; + + // Many devices report "0000" placeholder GPS as (0.0, 0.0). + const epsilon = 1e-7; + return lat.abs() < epsilon && lon.abs() < epsilon; + } + /// Find contact by public key prefix (6 bytes) Contact? _findContactByPrefix(Uint8List prefix) { if (prefix.length < 6) return null; diff --git a/test/providers/contacts_provider_test.dart b/test/providers/contacts_provider_test.dart new file mode 100644 index 0000000..0253e68 --- /dev/null +++ b/test/providers/contacts_provider_test.dart @@ -0,0 +1,110 @@ +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_sar_app/models/contact.dart'; +import 'package:meshcore_sar_app/providers/contacts_provider.dart'; +import 'package:meshcore_sar_app/services/cayenne_lpp_parser.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('ContactsProvider.updateTelemetry', () { + late ContactsProvider provider; + late Uint8List publicKey; + + setUp(() { + SharedPreferences.setMockInitialValues({}); + provider = ContactsProvider(); + publicKey = Uint8List.fromList([ + 0xAA, + 0xBB, + 0xCC, + 0xDD, + 0xEE, + 0xFF, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + 0x0F, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0x1A, + ]); + + provider.addOrUpdateContact( + Contact( + publicKey: publicKey, + type: ContactType.chat, + flags: 0, + outPathLen: 0, + outPath: Uint8List(64), + advName: 'Test Contact', + lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000, + advLat: (46.0569 * 1e6).toInt(), + advLon: (14.5058 * 1e6).toInt(), + lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000, + ), + ); + }); + + test('ignores telemetry GPS at 0,0 and keeps advert location', () { + final lppData = CayenneLppParser.createGpsData( + latitude: 0.0, + longitude: 0.0, + ); + + provider.updateTelemetry(publicKey.sublist(0, 6), lppData); + final updated = provider.findContactByKey(publicKey)!; + + expect(updated.telemetry, isNotNull); + expect(updated.telemetry!.gpsLocation, isNull); + expect(updated.displayLocation, isNotNull); + expect(updated.displayLocation!.latitude, closeTo(46.0569, 0.000001)); + expect(updated.displayLocation!.longitude, closeTo(14.5058, 0.000001)); + }); + + test('keeps valid telemetry GPS and uses it for display location', () { + final lppData = CayenneLppParser.createGpsData( + latitude: 45.0001, + longitude: 13.9999, + ); + + provider.updateTelemetry(publicKey.sublist(0, 6), lppData); + final updated = provider.findContactByKey(publicKey)!; + + expect(updated.telemetry, isNotNull); + expect(updated.telemetry!.gpsLocation, isNotNull); + expect( + updated.telemetry!.gpsLocation!.latitude, + closeTo(45.0001, 0.0001), + ); + expect( + updated.telemetry!.gpsLocation!.longitude, + closeTo(13.9999, 0.0001), + ); + expect(updated.displayLocation, isNotNull); + expect(updated.displayLocation!.latitude, closeTo(45.0001, 0.0001)); + expect(updated.displayLocation!.longitude, closeTo(13.9999, 0.0001)); + }); + }); +}