fix: Dedup packets across sources, translate numeric port numbers to names

Packet dedup: track seen packet IDs across all sources. Same packet from
Meshview + MeshMonitor counted once, not twice. Fixes inflated counts.
Portnum: numeric values (3, 4, 71) mapped to names (Position, NodeInfo, Neighbors).
LLM prompt: guidance on normal vs abnormal packet rates per type.
This commit is contained in:
K7ZVX 2026-05-07 03:07:56 +00:00
commit a80ed6cc7c
3 changed files with 39 additions and 2 deletions

View file

@ -458,12 +458,20 @@ class MeshDataStore:
- Deliverability (source overlap) - Deliverability (source overlap)
Does NOT rebuild the full node model. Does NOT rebuild the full node model.
""" """
# Recount packets per node from all sources # Recount packets per node from all sources (deduped by packet ID)
packet_counts: dict[int, int] = {} packet_counts: dict[int, int] = {}
packets_by_type: dict[int, dict[str, int]] = {} packets_by_type: dict[int, dict[str, int]] = {}
seen_packet_ids: set = set()
for name, source in self._sources.items(): for name, source in self._sources.items():
for pkt in (source.packets if hasattr(source, 'packets') else []): for pkt in (source.packets if hasattr(source, 'packets') else []):
# Dedup: skip packets already seen from another source
pkt_id = pkt.get("packet_id") or pkt.get("id")
if pkt_id is not None:
if pkt_id in seen_packet_ids:
continue
seen_packet_ids.add(pkt_id)
from_node = pkt.get("from_node") or pkt.get("from_node_id") or pkt.get("from") from_node = pkt.get("from_node") or pkt.get("from_node_id") or pkt.get("from")
if from_node is None: if from_node is None:
continue continue
@ -478,7 +486,7 @@ class MeshDataStore:
packet_counts[from_node] = packet_counts.get(from_node, 0) + 1 packet_counts[from_node] = packet_counts.get(from_node, 0) + 1
portnum = pkt.get("portnum") or pkt.get("portnum_name") or pkt.get("type") or "UNKNOWN" portnum = str(pkt.get("portnum") or pkt.get("portnum_name") or pkt.get("type") or "UNKNOWN")
if from_node not in packets_by_type: if from_node not in packets_by_type:
packets_by_type[from_node] = {} packets_by_type[from_node] = {}
packets_by_type[from_node][portnum] = packets_by_type[from_node].get(portnum, 0) + 1 packets_by_type[from_node][portnum] = packets_by_type[from_node].get(portnum, 0) + 1

View file

@ -18,6 +18,7 @@ logger = logging.getLogger(__name__)
# Portnum display names (from Meshtastic protobufs) # Portnum display names (from Meshtastic protobufs)
PORTNUM_DISPLAY = { PORTNUM_DISPLAY = {
# String names (from some APIs)
"TEXT_MESSAGE_APP": "Text", "TEXT_MESSAGE_APP": "Text",
"POSITION_APP": "Position", "POSITION_APP": "Position",
"NODEINFO_APP": "NodeInfo", "NODEINFO_APP": "NodeInfo",
@ -35,6 +36,29 @@ PORTNUM_DISPLAY = {
"REMOTE_HARDWARE_APP": "RemoteHW", "REMOTE_HARDWARE_APP": "RemoteHW",
"ATAK_PLUGIN": "ATAK", "ATAK_PLUGIN": "ATAK",
"ATAK_FORWARDER": "ATAK", "ATAK_FORWARDER": "ATAK",
# Numeric port numbers (from other APIs)
"0": "Unknown",
"1": "Text",
"3": "Position",
"4": "NodeInfo",
"5": "Routing",
"6": "Admin",
"7": "Waypoint",
"8": "Audio",
"33": "Reply",
"34": "IP Tunnel",
"64": "Serial",
"65": "Store&Fwd",
"66": "RangeTest",
"67": "Telemetry",
"68": "Sensor",
"69": "PaxCounter",
"70": "Traceroute",
"71": "Neighbors",
"72": "ATAK",
"73": "MapReport",
"256": "Private",
"257": "ATAK Fwd",
} }
def _clean_portnum(portnum) -> str: def _clean_portnum(portnum) -> str:

View file

@ -113,6 +113,11 @@ RESPONSE STYLE:
- ABSOLUTELY NO markdown. No asterisks, no bold, no bullet points with * or -, no numbered lists with 1. 2. 3. Just plain text sentences. - ABSOLUTELY NO markdown. No asterisks, no bold, no bullet points with * or -, no numbered lists with 1. 2. 3. Just plain text sentences.
- NEVER say "Want me to keep going?" the message system adds this automatically when needed. If you say it yourself, users see it twice. - NEVER say "Want me to keep going?" the message system adds this automatically when needed. If you say it yourself, users see it twice.
- When explaining "X/Y gateways" (like 7/7), explain that it means the node is visible to X out of Y data sources (Meshview and MeshMonitor instances that monitor the mesh). It does NOT mean infrastructure routers or regional gateways. - When explaining "X/Y gateways" (like 7/7), explain that it means the node is visible to X out of Y data sources (Meshview and MeshMonitor instances that monitor the mesh). It does NOT mean infrastructure routers or regional gateways.
- When reporting packet types, ALWAYS use the name (Position, NodeInfo, Telemetry) not the number.
- Normal position interval: 15-30 minutes (48-96 packets/day). 400+ Position packets in 24h means aggressive position interval, wasting airtime. Tell the user.
- Normal NodeInfo: every 2-3 hours (8-12/day). 50+ is excessive.
- Normal NeighborInfo: every 6 hours (4/day). 20+ is aggressive.
- If a node has high packet volume, explain WHAT the packets are and WHETHER the rate is abnormal compared to normal intervals.
QUESTION TYPES: QUESTION TYPES:
- "How's the mesh?" -> Lead with composite score. Highlight 1-2 biggest issues by name. Summarize each region briefly. - "How's the mesh?" -> Lead with composite score. Highlight 1-2 biggest issues by name. Summarize each region briefly.