You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
meshcore-client/test/models/message_snr_test.dart

97 lines
3.1 KiB

import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_open/models/message.dart';
void main() {
Uint8List key(int b) => Uint8List.fromList(List<int>.filled(32, b));
group('Message SNR/RSSI/route fields (#438)', () {
test('defaults are null (legacy/outgoing records)', () {
final m = Message(
senderKey: key(1),
text: 'hi',
timestamp: DateTime(2026),
isOutgoing: true,
);
expect(m.snr, isNull);
expect(m.rssi, isNull);
expect(m.isFloodRoute, isNull);
});
test('copyWith preserves fields when not overridden', () {
final m = Message(
senderKey: key(2),
text: 'hi',
timestamp: DateTime(2026),
isOutgoing: false,
snr: -3.25,
isFloodRoute: true,
);
final c = m.copyWith(retryCount: 1);
expect(c.snr, -3.25);
expect(c.isFloodRoute, isTrue);
expect(c.rssi, isNull);
});
test('copyWith overrides when provided', () {
final m = Message(
senderKey: key(3),
text: 'hi',
timestamp: DateTime(2026),
isOutgoing: false,
snr: 5.0,
isFloodRoute: false,
);
final c = m.copyWith(snr: 9.5, isFloodRoute: true, rssi: -80);
expect(c.snr, 9.5);
expect(c.isFloodRoute, isTrue);
expect(c.rssi, -80);
});
});
group('SNR wire scaling (#438)', () {
// Firmware sends (int8)(snr_dB * 4); the app recovers dB as byte / 4.0
// (MyMesh.cpp:512). A naive *4 would be 16x off and out of int8 range.
double snrFromByte(int b) => b.toSigned(8) / 4.0;
test('positive SNR', () => expect(snrFromByte(50), 12.5));
test('zero', () => expect(snrFromByte(0), 0.0));
test('negative SNR', () => expect(snrFromByte(-13), closeTo(-3.25, 1e-9)));
test('int8 extremes stay in a sane dB range', () {
expect(snrFromByte(127), closeTo(31.75, 1e-9));
expect(snrFromByte(-128), closeTo(-32.0, 1e-9));
});
});
group('flood vs direct discriminator (#438)', () {
// path_len == 0xFF => direct (routed); anything else => flood N hops.
bool isFlood(int rawPathLen) => rawPathLen != 0xFF;
test('0xFF is direct', () => expect(isFlood(0xFF), isFalse));
test('0 hops is flood, not direct', () => expect(isFlood(0), isTrue));
test('3 hops is flood', () => expect(isFlood(3), isTrue));
});
group('RSSI reserved2 gate (#439)', () {
// Firmware writes reserved2 (byte[3]) = clamped int8 RX RSSI, or 0 when
// unset (older firmware / no-RX text). RSSI is always negative for a real
// RX, so 0 unambiguously means "no data" -> null. (MyMesh.cpp:550-551)
int? rssiFromByte(int b) {
final v = b.toSigned(8);
return v != 0 ? v : null;
}
test('0 means no data -> null (older firmware / no RX)', () {
expect(rssiFromByte(0), isNull);
});
test('typical negative dBm is decoded signed', () {
expect(rssiFromByte(0xAB), -85); // 0xAB as int8 = -85 dBm
expect(rssiFromByte(-40), -40);
});
test('int8 floor (-128) shows, per firmware clamp', () {
expect(rssiFromByte(-128), -128);
});
});
}

Powered by TurnKey Linux.