feat(#439): read per-message RSSI from reserved2 and show it on the Path screen

Firmware #465 now writes RX RSSI into reserved2 (byte[3]) of the v3
contact-msg-recv frame as a clamped int8 dBm, 0 when unset
(MyMesh.cpp:550-551). Read it in the parse instead of skipping: gate on
!= 0 (RSSI is always negative for a real RX, so 0 = no data), null for
device-composed outgoing messages.

reserved1 (byte[2]) is untouched — that's #429's outgoing flag; RSSI
lives in byte[3] after the res1/res2 collision fix (#464/#465).

The Message.rssi field, persistence, param-passing, and the (hidden-
while-null) RSSI row on the Packet Path screen were all staged in #438,
so this is just the wire read + 3 gate tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/460/head
Strycher 2 months ago
parent 9278a75d93
commit 8920cf928f

@ -5884,17 +5884,21 @@ class MeshCoreConnector extends ChangeNotifier {
// Companion radio layout:
// [code][snr][res1][res2][prefix x6][path_len][txt_type][timestamp x4][extra?][text...]
// Firmware writes snr as (int8)(snr_dB * 4), so dB = byte / 4.0
// (MyMesh.cpp:512). res1 bit0 = device-composed outgoing flag (#429);
// res2 will carry RSSI once firmware ships it (#439/#456) — not read yet.
// snr: (int8)(snr_dB * 4), so dB = byte / 4.0 (MyMesh.cpp:512).
// res1 bit0 = device-composed outgoing flag (#429).
// res2 = RX RSSI as clamped int8 dBm, or 0 when unset (older firmware or
// no-RX text) — gate on != 0 (MyMesh.cpp:550-551, #465). RSSI is always
// negative for a real RX, so 0 unambiguously means "no data".
double? snr;
bool isOutgoing = false;
int? rssi;
if (code == respCodeContactMsgRecvV3) {
snr = reader.readInt8() / 4.0;
// reserved1 bit0 = outgoing flag: set for a message composed on the
// device itself, so it renders as sent-by-me (#429 part B).
isOutgoing = (reader.readByte() & 0x01) != 0;
reader.skipBytes(1); // reserved2 (RSSI pending — #439/#456)
final rssiByte = reader.readInt8(); // reserved2 = RX RSSI (#439/#465)
rssi = rssiByte != 0 ? rssiByte : null;
}
final senderPrefix = reader.readBytes(6);
@ -5969,6 +5973,7 @@ class MeshCoreConnector extends ChangeNotifier {
// path-type / rxTime are meaningless — null them like rxTime (#429/#438).
rxTime: isOutgoing ? null : DateTime.now(),
snr: isOutgoing ? null : snr,
rssi: isOutgoing ? null : rssi,
isFloodRoute: isOutgoing ? null : isFloodRoute,
);
} catch (e) {

@ -72,4 +72,25 @@ void main() {
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);
});
});
}

Loading…
Cancel
Save

Powered by TurnKey Linux.