fix: always display friendly node names (incoming & outgoing) by adding a name‑resolution helper and updating on_receive
This commit is contained in:
+92
-30
@@ -26,8 +26,8 @@ class MeshtasticCLI:
|
|||||||
|
|
||||||
def __init__(self, debug_mode=False):
|
def __init__(self, debug_mode=False):
|
||||||
self.interface = None
|
self.interface = None
|
||||||
self.nodes = {}
|
self.nodes = {} # node_id -> info dict
|
||||||
self.messages = []
|
self.messages = [] # stored message history
|
||||||
self.exit_flag = False
|
self.exit_flag = False
|
||||||
self.selected_node = None
|
self.selected_node = None
|
||||||
self.debug_mode = debug_mode
|
self.debug_mode = debug_mode
|
||||||
@@ -81,6 +81,39 @@ class MeshtasticCLI:
|
|||||||
# what we want when background threads misbehave.
|
# what we want when background threads misbehave.
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# Helper – return a printable name for a node (friendly or short id)
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
def _friendly_name(self, node_id: int) -> str:
|
||||||
|
"""
|
||||||
|
Resolve a node id to a friendly name (if we have one) or the short
|
||||||
|
hex representation ``!deadbeef``.
|
||||||
|
"""
|
||||||
|
node = self.nodes.get(node_id)
|
||||||
|
if node:
|
||||||
|
# If we already stored a proper name, use it; otherwise fall back to short id.
|
||||||
|
if node.get("name") and node["name"] != node.get("short_id"):
|
||||||
|
return node["name"]
|
||||||
|
return node["short_id"]
|
||||||
|
|
||||||
|
# Look directly at the library’s node table (may have the name already)
|
||||||
|
if hasattr(self.interface, "nodesByNum"):
|
||||||
|
nbyn = self.interface.nodesByNum
|
||||||
|
if node_id in nbyn:
|
||||||
|
user = nbyn[node_id].get("user", {})
|
||||||
|
name = user.get("longName") or user.get("shortName")
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
|
||||||
|
# If we have never seen this node, ask the mesh for its info.
|
||||||
|
try:
|
||||||
|
if hasattr(self.interface, "getRemoteNodeInfo"):
|
||||||
|
self.interface.getRemoteNodeInfo(node_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return f"!{node_id:08x}"
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
# INTERACTION METHODS (unchanged apart from exiting via exit_program)
|
# INTERACTION METHODS (unchanged apart from exiting via exit_program)
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
@@ -184,54 +217,70 @@ class MeshtasticCLI:
|
|||||||
print(f"Connection failed: {e}")
|
print(f"Connection failed: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# CALLBACK – PROCESS INCOMING PACKETS
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
def on_receive(self, packet, interface=None):
|
def on_receive(self, packet, interface=None):
|
||||||
"""Callback for received packets."""
|
"""Callback for received packets."""
|
||||||
if self.debug_mode:
|
if self.debug_mode:
|
||||||
print(f"\n[DEBUG] Packet received: {packet.keys() if isinstance(packet, dict) else type(packet)}")
|
print(f"\n[DEBUG] Packet received: {packet.keys() if isinstance(packet, dict) else type(packet)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Store node info
|
# ------------------------------------------------------------
|
||||||
|
# 1️⃣ Keep our node table up‑to‑date (but *don’t* overwrite a
|
||||||
|
# friendly name with a raw hex id)
|
||||||
|
# ------------------------------------------------------------
|
||||||
if 'from' in packet:
|
if 'from' in packet:
|
||||||
node_id = packet['from']
|
node_id = packet['from']
|
||||||
node_short_id = f"!{node_id:08x}"
|
node_short_id = f"!{node_id:08x}"
|
||||||
|
|
||||||
# Get the best available name
|
# Gather the best name we can find right now
|
||||||
if 'fromId' in packet and packet['fromId']:
|
node_name_candidate = None
|
||||||
node_name = packet['fromId']
|
# a) fromId field (explicit friendly name)
|
||||||
|
if packet.get('fromId'):
|
||||||
|
node_name_candidate = packet['fromId']
|
||||||
else:
|
else:
|
||||||
node_name = node_short_id
|
# b) look at the library’s node database
|
||||||
if hasattr(self.interface, 'nodesByNum') and node_id in self.interface.nodesByNum:
|
if hasattr(self.interface, 'nodesByNum') and node_id in self.interface.nodesByNum:
|
||||||
node_info = self.interface.nodesByNum[node_id]
|
user = self.interface.nodesByNum[node_id].get('user', {})
|
||||||
user_info = node_info.get('user', {})
|
node_name_candidate = user.get('longName') or user.get('shortName')
|
||||||
if 'longName' in user_info and user_info['longName']:
|
|
||||||
node_name = user_info['longName']
|
|
||||||
elif 'shortName' in user_info and user_info['shortName']:
|
|
||||||
node_name = user_info['shortName']
|
|
||||||
|
|
||||||
|
# Update our local cache – preserve an existing friendly name
|
||||||
if node_id not in self.nodes:
|
if node_id not in self.nodes:
|
||||||
self.nodes[node_id] = {
|
self.nodes[node_id] = {
|
||||||
'id': node_id,
|
'id': node_id,
|
||||||
'last_seen': datetime.now(),
|
'last_seen': datetime.now(),
|
||||||
'name': node_name,
|
'name': node_name_candidate or node_short_id,
|
||||||
'short_id': node_short_id
|
'short_id': node_short_id
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
self.nodes[node_id]['last_seen'] = datetime.now()
|
self.nodes[node_id]['last_seen'] = datetime.now()
|
||||||
if 'fromId' in packet and packet['fromId']:
|
# Replace the stored name *only* if we have a new friendly name
|
||||||
self.nodes[node_id]['name'] = packet['fromId']
|
if node_name_candidate and node_name_candidate != node_short_id:
|
||||||
|
self.nodes[node_id]['name'] = node_name_candidate
|
||||||
|
|
||||||
# Store and display messages
|
# If we still only have a short id, ask the mesh for full info
|
||||||
|
if self.nodes[node_id]['name'] == node_short_id:
|
||||||
|
try:
|
||||||
|
if hasattr(self.interface, 'getRemoteNodeInfo'):
|
||||||
|
self.interface.getRemoteNodeInfo(node_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 2️⃣ Extract and display text messages
|
||||||
|
# ------------------------------------------------------------
|
||||||
if 'decoded' in packet:
|
if 'decoded' in packet:
|
||||||
decoded = packet['decoded']
|
decoded = packet['decoded']
|
||||||
if self.debug_mode:
|
if self.debug_mode:
|
||||||
print(f"[DEBUG] Decoded packet: portnum={decoded.get('portnum')}, has text={('text' in decoded)}")
|
print(f"[DEBUG] Decoded packet: portnum={decoded.get('portnum')}, has text={('text' in decoded)}")
|
||||||
|
|
||||||
# Check if it's a text message
|
# Identify a text message (covers several API variations)
|
||||||
if (decoded.get('portnum') == 'TEXT_MESSAGE_APP' or
|
if (decoded.get('portnum') == 'TEXT_MESSAGE_APP' or
|
||||||
decoded.get('portnum') == 1 or
|
decoded.get('portnum') == 1 or
|
||||||
'text' in decoded):
|
'text' in decoded):
|
||||||
|
|
||||||
# Extract text – handle both string and bytes
|
# ----------- extract the actual text -----------------
|
||||||
text_content = decoded.get('text', '')
|
text_content = decoded.get('text', '')
|
||||||
if isinstance(text_content, bytes):
|
if isinstance(text_content, bytes):
|
||||||
text_content = text_content.decode('utf-8', errors='ignore')
|
text_content = text_content.decode('utf-8', errors='ignore')
|
||||||
@@ -243,36 +292,49 @@ class MeshtasticCLI:
|
|||||||
text_content = payload.get('text', '')
|
text_content = payload.get('text', '')
|
||||||
|
|
||||||
if text_content:
|
if text_content:
|
||||||
from_name = packet.get('fromId', 'Unknown')
|
# ----------- resolve friendly names -----------------
|
||||||
from_short = f"!{packet.get('from', 0):08x}"
|
src_id = packet.get('from')
|
||||||
to_name = packet.get('toId', 'Broadcast')
|
src_name = self._friendly_name(src_id) if src_id is not None else "Unknown"
|
||||||
to_short = (f"!{packet.get('to', 0):08x}"
|
src_short = f"!{src_id:08x}" if src_id is not None else "??"
|
||||||
if packet.get('to') != 0xffffffff else 'Broadcast')
|
|
||||||
|
|
||||||
|
dst_raw = packet.get('to')
|
||||||
|
if dst_raw is None or dst_raw == 0xffffffff:
|
||||||
|
dst_name = "Broadcast"
|
||||||
|
dst_short = "Broadcast"
|
||||||
|
else:
|
||||||
|
dst_name = self._friendly_name(dst_raw)
|
||||||
|
dst_short = f"!{dst_raw:08x}"
|
||||||
|
|
||||||
|
# ---------- store the message in history -------------
|
||||||
msg = {
|
msg = {
|
||||||
'from': from_name,
|
'from': src_name,
|
||||||
'from_short': from_short,
|
'from_short': src_short,
|
||||||
'to': to_name,
|
'to': dst_name,
|
||||||
'to_short': to_short,
|
'to_short': dst_short,
|
||||||
'text': text_content,
|
'text': text_content,
|
||||||
'time': datetime.now()
|
'time': datetime.now()
|
||||||
}
|
}
|
||||||
self.messages.append(msg)
|
self.messages.append(msg)
|
||||||
# Print new messages in real‑time
|
|
||||||
|
# ---------- live display if we are in the chat UI ---
|
||||||
if self.current_mode == "messaging":
|
if self.current_mode == "messaging":
|
||||||
time_str = msg['time'].strftime('%Y-%m-%d %H:%M:%S')
|
time_str = msg['time'].strftime('%Y-%m-%d %H:%M:%S')
|
||||||
print(f"\n[{time_str}] {msg['from']} ({msg['from_short']}) -> "
|
print(f"\n[{time_str}] {msg['from']} ({msg['from_short']}) -> "
|
||||||
f"{msg['to']} ({msg['to_short']}): {msg['text']}")
|
f"{msg['to']} ({msg['to_short']}): {msg['text']}")
|
||||||
print("> ", end='', flush=True)
|
print("> ", end='', flush=True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.debug_mode:
|
if self.debug_mode:
|
||||||
print(f"[DEBUG] Error processing packet: {e}")
|
print(f"[DEBUG] Error processing packet: {e}")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
|
# UI HELPERS
|
||||||
|
# ------------------------------------------------------------------------
|
||||||
def list_recent_nodes(self):
|
def list_recent_nodes(self):
|
||||||
"""List nodes seen in the last hour."""
|
"""List nodes seen in the last hour."""
|
||||||
one_hour_ago = datetime.now() - timedelta(hours=1)
|
one_hour_ago = datetime.now() - timedelta(hours=1)
|
||||||
recent_nodes = {k: v for k, v in self.nodes.items()
|
recent_nodes = {k: v for k, v in self.nodes.items()
|
||||||
if v['last_seen'] > one_hour_ago}
|
if v['last_seen'] > one_hour_ago}
|
||||||
|
|
||||||
if not recent_nodes:
|
if not recent_nodes:
|
||||||
print("\nNo nodes detected in the last hour.")
|
print("\nNo nodes detected in the last hour.")
|
||||||
|
|||||||
Reference in New Issue
Block a user