From 3fec03bb9bcab07c69f922dbb36c279fd1d335dd Mon Sep 17 00:00:00 2001 From: nomdetom Date: Tue, 28 Jul 2026 11:01:34 +0100 Subject: [PATCH 1/2] Add explicit presence for RSSI: MeshPacket.rx_rssi optional, NodeInfoLite.rssi storage field MeshPacket.rx_rssi was proto3 singular, so an unset value and a genuine 0 dBm reading were byte-identical on the wire (0 is a legal SX126x return, and SX127x's offset+register formula can even go positive). Mark it optional so absence survives encoding, mirroring the precedent already set by NodeInfo.hops_away. NodeInfoLite had no RSSI field at all, so nothing survived a device reboot for replayed history packets to restore - every replay showed RSSI 0 regardless of the real measurement. Add NodeInfoLite.rssi (tag 20, sint32, next free tag) with the same "0 is not automatically unknown" caveat; presence is tracked via NODEINFO_BITFIELD_HAS_RSSI on the firmware side rather than a second wire-level bool, to avoid a RAM cost per stored node. Companion firmware change: meshtastic/firmware, phantom-node-fix-perhaps branch, .notes/plans/plan-executed-snr-rssi-replay-fidelity.md. --- meshtastic/deviceonly.options | 3 +++ meshtastic/deviceonly.proto | 13 ++++++++++++- meshtastic/mesh.proto | 7 ++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/meshtastic/deviceonly.options b/meshtastic/deviceonly.options index 162545704..ece192d48 100644 --- a/meshtastic/deviceonly.options +++ b/meshtastic/deviceonly.options @@ -11,6 +11,9 @@ *NodeInfoLite.channel int_size:8 *NodeInfoLite.hops_away int_size:8 *NodeInfoLite.next_hop int_size:8 +# Real range is roughly -164..+98 (SX127x low-band offset .. positive-RSSI edge case, see +# phantom-zero-hop-direct-nodes bug notes) - int8 can't hold it, int16 comfortably can. +*NodeInfoLite.rssi int_size:16 # Flattened user fields. long_name was 40 on UserLite; trimmed to 25 here so the # slim header gives back ~15 B/node of RAM. Names that exceeded 24 chars on the diff --git a/meshtastic/deviceonly.proto b/meshtastic/deviceonly.proto index ec28b59d4..15d4b0a5e 100644 --- a/meshtastic/deviceonly.proto +++ b/meshtastic/deviceonly.proto @@ -187,10 +187,21 @@ message NodeInfoLite { /* * Q4-encoded SNR: dB × 4, sint32 zigzag. Matches RouteDiscovery convention. - * Encode: snr_q4 = (int32_t)(snr * 4.0f). Decode: snr = snr_q4 / 4.0f. + * Encode: snr_q4 = (int32_t)lroundf(snr * 4.0f). Decode: snr = snr_q4 / 4.0f. * float snr is always zeroed on disk; this field carries all persisted SNR. + * A stored 0 does not by itself mean "unknown" here - see NODEINFO_BITFIELD_HAS_SNR in + * src/mesh/NodeDB.h for the presence bit that disambiguates a genuine 0 dB reading from + * "never measured". */ sint32 snr_q4 = 19; + + /* + * Last received RSSI (dBm) for this node, restored into replayed packets so history sent to + * the phone after a reconnect isn't misrepresented as a live zero-signal reception. A stored + * 0 does not by itself mean "unknown" - RSSI can legitimately read 0 dBm or, on some radios, + * positive. See NODEINFO_BITFIELD_HAS_RSSI in src/mesh/NodeDB.h for the presence bit. + */ + sint32 rssi = 20; } /* diff --git a/meshtastic/mesh.proto b/meshtastic/mesh.proto index b09a15b0a..6ffbdbe7d 100644 --- a/meshtastic/mesh.proto +++ b/meshtastic/mesh.proto @@ -1848,8 +1848,13 @@ message MeshPacket { /* * rssi of received packet. Only sent to phone for dispay purposes. + * Explicit presence: rssi 0 is a legitimate reading on some radios (SX126x can report exactly + * 0 dBm; SX127x's formula can even go positive), so implicit-presence proto3 made an unset + * value indistinguishable from a measured one. has_rx_rssi disambiguates; a replayed packet + * built from history the device never restored an RSSI for should leave this field absent + * rather than emitting 0. */ - int32 rx_rssi = 12; + optional int32 rx_rssi = 12; /* * Describe if this message is delayed From 94cdec45ae729650d5a1b0cea356cad12747983b Mon Sep 17 00:00:00 2001 From: nomdetom Date: Tue, 28 Jul 2026 14:42:24 +0100 Subject: [PATCH 2/2] remove rssi & improve notes --- meshtastic/deviceonly.options | 3 --- meshtastic/deviceonly.proto | 8 -------- meshtastic/mesh.proto | 10 ++++++---- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/meshtastic/deviceonly.options b/meshtastic/deviceonly.options index ece192d48..162545704 100644 --- a/meshtastic/deviceonly.options +++ b/meshtastic/deviceonly.options @@ -11,9 +11,6 @@ *NodeInfoLite.channel int_size:8 *NodeInfoLite.hops_away int_size:8 *NodeInfoLite.next_hop int_size:8 -# Real range is roughly -164..+98 (SX127x low-band offset .. positive-RSSI edge case, see -# phantom-zero-hop-direct-nodes bug notes) - int8 can't hold it, int16 comfortably can. -*NodeInfoLite.rssi int_size:16 # Flattened user fields. long_name was 40 on UserLite; trimmed to 25 here so the # slim header gives back ~15 B/node of RAM. Names that exceeded 24 chars on the diff --git a/meshtastic/deviceonly.proto b/meshtastic/deviceonly.proto index 15d4b0a5e..7e025c14c 100644 --- a/meshtastic/deviceonly.proto +++ b/meshtastic/deviceonly.proto @@ -194,14 +194,6 @@ message NodeInfoLite { * "never measured". */ sint32 snr_q4 = 19; - - /* - * Last received RSSI (dBm) for this node, restored into replayed packets so history sent to - * the phone after a reconnect isn't misrepresented as a live zero-signal reception. A stored - * 0 does not by itself mean "unknown" - RSSI can legitimately read 0 dBm or, on some radios, - * positive. See NODEINFO_BITFIELD_HAS_RSSI in src/mesh/NodeDB.h for the presence bit. - */ - sint32 rssi = 20; } /* diff --git a/meshtastic/mesh.proto b/meshtastic/mesh.proto index 6ffbdbe7d..008cc1f63 100644 --- a/meshtastic/mesh.proto +++ b/meshtastic/mesh.proto @@ -1849,10 +1849,8 @@ message MeshPacket { /* * rssi of received packet. Only sent to phone for dispay purposes. * Explicit presence: rssi 0 is a legitimate reading on some radios (SX126x can report exactly - * 0 dBm; SX127x's formula can even go positive), so implicit-presence proto3 made an unset - * value indistinguishable from a measured one. has_rx_rssi disambiguates; a replayed packet - * built from history the device never restored an RSSI for should leave this field absent - * rather than emitting 0. + * 0 dBm; SX127x's formula can even go positive). has_rx_rssi disambiguates; a replayed packet + * built from history should leave this field absent rather than emitting 0. */ optional int32 rx_rssi = 12; @@ -1869,6 +1867,10 @@ message MeshPacket { /* * Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. * When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. + * hop_start == 0 does not necessarily mean a direct (0-hop) neighbor: firmware prior to 2.3.0 + * never populated this field, so a receiver can only trust hop_start == 0 as genuine once it has + * decoded the packet and confirmed the sender's bitfield is present (added in 2.5.0). Until then, + * or for a sender that never sets that bitfield, treat hop_start == 0 as unknown, not direct. */ uint32 hop_start = 15;