Add LPCNet voice mode option

This commit is contained in:
Janez T
2026-03-11 20:47:17 +01:00
parent e31bfac2ac
commit 1b6ff59af3
4 changed files with 390 additions and 24 deletions

View File

@@ -1,5 +1,6 @@
import 'dart:typed_data';
import 'package:geolocator/geolocator.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:latlong2/latlong.dart';
import 'package:meshcore_sar_app/models/contact.dart';
@@ -522,6 +523,58 @@ void main() {
isEmpty,
);
});
test('infers a fallback location 100m from first-hop repeater', () {
final repeaterKey = Uint8List.fromList([
0xAA,
0xBB,
0x10,
0x11,
0x12,
0x13,
...List<int>.generate(26, (index) => index + 20),
]);
provider.addOrUpdateContact(
createContact(
key: repeaterKey,
type: ContactType.repeater,
name: 'Relay Alpha',
),
);
final targetKey = createPublicKey(120);
final route = ContactRouteCodec.parse('AABB,CCDD');
provider.addOrUpdateContact(
Contact(
publicKey: targetKey,
type: ContactType.chat,
flags: 0,
outPathLen: route.signedEncodedPathLen,
outPath: route.paddedPathBytes,
advName: 'No GPS',
lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000,
advLat: 0,
advLon: 0,
lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000,
),
);
final updated = provider.findContactByKey(targetKey)!;
final inferred = updated.displayLocation;
final repeater = provider.findContactByKey(repeaterKey)!;
final repeaterLocation = repeater.displayLocation;
expect(inferred, isNotNull);
expect(repeaterLocation, isNotNull);
final distanceMeters = Geolocator.distanceBetween(
repeaterLocation!.latitude,
repeaterLocation.longitude,
inferred!.latitude,
inferred.longitude,
);
expect(distanceMeters, closeTo(100.0, 8.0));
});
});
group('ContactsProvider.updateFastGps', () {

View File

@@ -0,0 +1,101 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_client/meshcore_client.dart';
import 'package:meshcore_sar_app/models/message_reception_details.dart';
import 'package:meshcore_sar_app/services/message_storage_service.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
SharedPreferences.setMockInitialValues({});
});
test(
'retains path bytes for stored unread message when reception sidecar is missing',
() async {
final storage = MessageStorageService();
final message = Message(
id: 'msg-1',
messageType: MessageType.contact,
senderPublicKeyPrefix: Uint8List.fromList([1, 2, 3, 4, 5, 6]),
pathLen: 2,
textType: MessageTextType.plain,
senderTimestamp: 1700000000,
text: 'Unread message',
receivedAt: DateTime.fromMillisecondsSinceEpoch(1700000000500),
isRead: false,
);
await storage.saveMessages(
[message],
messageReceptionDetails: {
message.id: MessageReceptionDetails(
capturedAt: DateTime.fromMillisecondsSinceEpoch(1700000000600),
pathBytes: const [0xAA, 0xBB, 0xCC],
),
},
);
final prefs = await SharedPreferences.getInstance();
await prefs.remove('stored_message_reception_details');
final restoredDetails = await storage.loadMessageReceptionDetails();
expect(restoredDetails.keys, contains(message.id));
expect(restoredDetails[message.id]?.pathBytes, [0xAA, 0xBB, 0xCC]);
},
);
test('retains embedded reception details when sidecar is missing', () async {
final storage = MessageStorageService();
final message = Message(
id: 'msg-2',
messageType: MessageType.channel,
senderPublicKeyPrefix: Uint8List.fromList([6, 5, 4, 3, 2, 1]),
channelIdx: 2,
pathLen: 3,
textType: MessageTextType.plain,
senderTimestamp: 1700000100,
text: 'Room update',
receivedAt: DateTime.fromMillisecondsSinceEpoch(1700000100500),
isRead: false,
);
await storage.saveMessages(
[message],
messageReceptionDetails: {
message.id: MessageReceptionDetails(
capturedAt: DateTime.fromMillisecondsSinceEpoch(1700000100600),
packetLoggedAt: DateTime.fromMillisecondsSinceEpoch(1700000100400),
rssiDbm: -91,
snrDb: 7.25,
pathBytes: const [0xAA, 0xBB, 0xCC, 0xDD],
senderToReceiptMs: 1200,
estimatedTransmitMs: 800,
postTransmitDelayMs: 400,
),
},
);
final prefs = await SharedPreferences.getInstance();
await prefs.remove('stored_message_reception_details');
final restoredDetails = await storage.loadMessageReceptionDetails();
final restored = restoredDetails[message.id];
expect(restored, isNotNull);
expect(restored!.pathBytes, [0xAA, 0xBB, 0xCC, 0xDD]);
expect(restored.rssiDbm, -91);
expect(restored.snrDb, 7.25);
expect(restored.senderToReceiptMs, 1200);
expect(restored.estimatedTransmitMs, 800);
expect(restored.postTransmitDelayMs, 400);
expect(
restored.packetLoggedAt,
DateTime.fromMillisecondsSinceEpoch(1700000100400),
);
});
}