feat(mesh): Meshtastic RSSI/SNR + peer-location map wiring (backlog #14/#15, part 1)

Backend: parse_mesh_packet now decodes MeshPacket.rx_snr (field 8, float) and
rx_rssi (field 12, int32), and a new POSITION_APP branch decodes Position.
latitude_i/longitude_i (fields 1/2, sfixed32) -- all field numbers confirmed
against the canonical meshtastic/protobufs mesh.proto, not guessed. Threaded
through ParsedContact -> refresh_contacts -> MeshPeer (mirroring how
pkc_capable was wired for #17), so mesh.peers now surfaces real rssi/snr/lat/
lon instead of always-null. Fixed a real bug found along the way:
update_node_info's unconditional contact replace would have silently wiped
any already-tracked signal/position data on the next NodeInfo packet -- now
preserves it.

Frontend: mesh.ts's updateNodePositionsFromPeers() feeds real position data
into the SAME nodePositions map MeshMap.vue already renders from (parallel to
the existing Coordinate/Alert-message path) -- MeshMap.vue itself needed zero
changes, it was already built for this.

105/105 mesh tests pass (4 new: rx_snr/rx_rssi decode, position decode +
incomplete-field handling, full packet_to_inbound_frame integration).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-30 22:52:42 -04:00
co-authored by Claude Sonnet 5
parent dfca007949
commit 02b6b52a8c
8 changed files with 247 additions and 29 deletions
+17
View File
@@ -396,6 +396,16 @@ pub struct ParsedContact {
/// NodeInfo public key, so the firmware delivers DMs PKC-encrypted). Meshcore
/// contacts leave it `false` — their E2E status is tracked per-message.
pub pkc_capable: bool,
/// Signal strength (dBm) / signal-to-noise ratio (dB) of the most recently
/// heard packet from this contact. Meshtastic-only today (from
/// `MeshPacket.rx_rssi`/`.rx_snr`); other transports leave these `None`.
pub rssi: Option<i16>,
pub snr: Option<f32>,
/// Last known position, from a Meshtastic `POSITION_APP` broadcast
/// (`Position.latitude_i`/`.longitude_i`, degrees). `None` until the
/// contact has shared one.
pub lat: Option<f64>,
pub lon: Option<f64>,
}
/// Parse RESP_CONTACT (0x03) response.
@@ -440,6 +450,13 @@ pub fn parse_contact(data: &[u8]) -> Result<ParsedContact> {
flags,
// Meshcore tracks E2E per message, not per contact.
pkc_capable: false,
// Meshcore's own contact format does carry lat/lon at a fixed offset
// (see the format comment above) but wiring that up is out of scope
// for this Meshtastic-specific backlog item.
rssi: None,
snr: None,
lat: None,
lon: None,
})
}