From 8a13b60d23ba13ab0fae0a39ef725d74b31b2ee5 Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 07:38:28 -0600 Subject: [PATCH 1/7] Small cleanup in rx handler --- message_handlers/rx_handler.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/message_handlers/rx_handler.py b/message_handlers/rx_handler.py index 1104496..7c129a9 100644 --- a/message_handlers/rx_handler.py +++ b/message_handlers/rx_handler.py @@ -1,5 +1,5 @@ from meshtastic import BROADCAST_NUM -from utilities.utils import get_node_list, decimal_to_hex, get_nodeNum +from utilities.utils import get_node_list, decimal_to_hex, get_nodeNum, get_name_from_number import globals from ui.curses_ui import draw_packetlog_win, draw_node_list, draw_messages_window, draw_channel_list, add_notification from db_handler import save_message_to_db, maybe_store_nodeinfo_in_db @@ -51,18 +51,12 @@ def on_receive(packet, interface): # Add received message to the messages list message_from_id = packet['from'] - message_from_string = "" - for node in globals.interface.nodes.values(): - if message_from_id == node['num']: - message_from_string = node["user"]["shortName"] + ":" # Get the name using the node ID - break - else: - message_from_string = str(decimal_to_hex(message_from_id)) # If long name not found, use the ID as string - - if globals.channel_list[channel_number] in globals.all_messages: - globals.all_messages[globals.channel_list[channel_number]].append((f"{globals.message_prefix} {message_from_string} ", message_string)) - else: - globals.all_messages[globals.channel_list[channel_number]] = [(f"{globals.message_prefix} {message_from_string} ", message_string)] + message_from_string = get_name_from_number(message_from_id, type='short') + ":" + + if globals.channel_list[channel_number] not in globals.all_messages: + globals.all_messages[globals.channel_list[channel_number]] = [] + + globals.all_messages[globals.channel_list[channel_number]].append((f"{globals.message_prefix} {message_from_string} ", message_string)) draw_channel_list() draw_messages_window() From 96493e59738882d8759642e26c8949dd67decf19 Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 07:49:01 -0600 Subject: [PATCH 2/7] Add traceroute support --- message_handlers/tx_handler.py | 79 +++++++++++++++++++++++++++++++++- ui/curses_ui.py | 9 ++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/message_handlers/tx_handler.py b/message_handlers/tx_handler.py index 0a50903..a301cda 100644 --- a/message_handlers/tx_handler.py +++ b/message_handlers/tx_handler.py @@ -1,7 +1,9 @@ from meshtastic import BROADCAST_NUM from db_handler import save_message_to_db, update_ack_nak -from utilities.utils import get_nodeNum +from meshtastic.protobuf import mesh_pb2, portnums_pb2 +from utilities.utils import get_nodeNum, get_name_from_number import globals +import google.protobuf.json_format ack_naks = {} @@ -35,6 +37,70 @@ def onAckNak(packet): draw_messages_window() +def on_response_traceroute(packet): + """on response for trace route""" + from ui.curses_ui import draw_channel_list, draw_messages_window, add_notification + + UNK_SNR = -128 # Value representing unknown SNR + + route_discovery = mesh_pb2.RouteDiscovery() + route_discovery.ParseFromString(packet["decoded"]["payload"]) + msg_dict = google.protobuf.json_format.MessageToDict(route_discovery) + + msg_str = "Route traced towards destination:\n" + + route_str = get_name_from_number(packet["to"]) or f"{packet['to']:08x}" # Start with destination of response + + # SNR list should have one more entry than the route, as the final destination adds its SNR also + lenTowards = 0 if "route" not in msg_dict else len(msg_dict["route"]) + snrTowardsValid = "snrTowards" in msg_dict and len(msg_dict["snrTowards"]) == lenTowards + 1 + if lenTowards > 0: # Loop through hops in route and add SNR if available + for idx, node_num in enumerate(msg_dict["route"]): + route_str += " --> " + (get_name_from_number(node_num) or f"{node_num:08x}") \ + + " (" + (str(msg_dict["snrTowards"][idx] / 4) if snrTowardsValid and msg_dict["snrTowards"][idx] != UNK_SNR else "?") + "dB)" + + # End with origin of response + route_str += " --> " + (get_name_from_number(packet["from"]) or f"{packet['from']:08x}") \ + + " (" + (str(msg_dict["snrTowards"][-1] / 4) if snrTowardsValid and msg_dict["snrTowards"][-1] != UNK_SNR else "?") + "dB)" + + msg_str += route_str + "\n" # Print the route towards destination + + # Only if hopStart is set and there is an SNR entry (for the origin) it's valid, even though route might be empty (direct connection) + lenBack = 0 if "routeBack" not in msg_dict else len(msg_dict["routeBack"]) + backValid = "hopStart" in packet and "snrBack" in msg_dict and len(msg_dict["snrBack"]) == lenBack + 1 + if backValid: + msg_str += "Route traced back to us:\n" + route_str = get_name_from_number(packet["from"]) or f"{packet['from']:08x}" # Start with origin of response + + if lenBack > 0: # Loop through hops in routeBack and add SNR if available + for idx, node_num in enumerate(msg_dict["routeBack"]): + route_str += " --> " + (get_name_from_number(node_num) or f"{node_num:08x}") \ + + " (" + (str(msg_dict["snrBack"][idx] / 4) if msg_dict["snrBack"][idx] != UNK_SNR else "?") + "dB)" + + # End with destination of response (us) + route_str += " --> " + (get_name_from_number(packet["to"]) or f"{p['to']:08x}") \ + + " (" + (str(msg_dict["snrBack"][-1] / 4) if msg_dict["snrBack"][-1] != UNK_SNR else "?") + "dB)" + + msg_str += route_str + "\n" # Print the route back to us + + if(packet['from'] not in globals.channel_list): + globals.channel_list.append(packet['from']) + + channel_number = globals.channel_list.index(packet['from']) + + if globals.channel_list[channel_number] != globals.channel_list[globals.selected_channel]: + add_notification(channel_number) + + message_from_string = get_name_from_number(packet['from'], type='short') + ":\n" + + if globals.channel_list[channel_number] not in globals.all_messages: + globals.all_messages[globals.channel_list[channel_number]] = [] + globals.all_messages[globals.channel_list[channel_number]].append((f"{globals.message_prefix} {message_from_string}", msg_str)) + + draw_channel_list() + draw_messages_window() + save_message_to_db(globals.channel_list[channel_number], packet['from'], msg_str) + def send_message(message, destination=BROADCAST_NUM, channel=0): myid = globals.myNodeNum @@ -65,3 +131,14 @@ def send_message(message, destination=BROADCAST_NUM, channel=0): ack_naks[sent_message_data.id] = {'channel' : channel_id, 'messageIndex' : len(globals.all_messages[channel_id]) - 1, 'timestamp' : timestamp } +def send_traceroute(): + r = mesh_pb2.RouteDiscovery() + globals.interface.sendData( + r, + destinationId=globals.node_list[globals.selected_node], + portNum=portnums_pb2.PortNum.TRACEROUTE_APP, + wantResponse=True, + onResponse=on_response_traceroute, + channelIndex=0, + hopLimit=3, + ) diff --git a/ui/curses_ui.py b/ui/curses_ui.py index 615cc8e..394284a 100644 --- a/ui/curses_ui.py +++ b/ui/curses_ui.py @@ -3,8 +3,7 @@ import textwrap import globals from utilities.utils import get_name_from_number, get_channels from settings import settings -from message_handlers.tx_handler import send_message - +from message_handlers.tx_handler import send_message, send_traceroute def add_notification(channel_number): handle_notification(channel_number, add=True) @@ -324,7 +323,11 @@ def main_ui(stdscr): # Check for Esc elif char == 27: break - + + # Check for Ctrl + t + elif char == 20: + send_traceroute() + elif char == curses.KEY_ENTER or char == 10 or char == 13: if globals.current_window == 2: node_list = globals.node_list From 02c104dcd5d84dcd52e31402d4be20113d7065b7 Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 07:59:22 -0600 Subject: [PATCH 3/7] Remove unused imports --- db_handler.py | 2 +- message_handlers/rx_handler.py | 2 +- message_handlers/tx_handler.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db_handler.py b/db_handler.py index 5427772..a8b323b 100644 --- a/db_handler.py +++ b/db_handler.py @@ -1,7 +1,7 @@ import sqlite3 import globals import time -from utilities.utils import get_nodeNum, get_name_from_number +from utilities.utils import get_name_from_number def get_table_name(channel): # Construct the table name diff --git a/message_handlers/rx_handler.py b/message_handlers/rx_handler.py index 7c129a9..de6730a 100644 --- a/message_handlers/rx_handler.py +++ b/message_handlers/rx_handler.py @@ -1,5 +1,5 @@ from meshtastic import BROADCAST_NUM -from utilities.utils import get_node_list, decimal_to_hex, get_nodeNum, get_name_from_number +from utilities.utils import get_node_list, decimal_to_hex, get_name_from_number import globals from ui.curses_ui import draw_packetlog_win, draw_node_list, draw_messages_window, draw_channel_list, add_notification from db_handler import save_message_to_db, maybe_store_nodeinfo_in_db diff --git a/message_handlers/tx_handler.py b/message_handlers/tx_handler.py index a301cda..26fa094 100644 --- a/message_handlers/tx_handler.py +++ b/message_handlers/tx_handler.py @@ -1,7 +1,7 @@ from meshtastic import BROADCAST_NUM from db_handler import save_message_to_db, update_ack_nak from meshtastic.protobuf import mesh_pb2, portnums_pb2 -from utilities.utils import get_nodeNum, get_name_from_number +from utilities.utils import get_name_from_number import globals import google.protobuf.json_format From 4c4c0d553eadf0110a8b1c2fe50038f37d2c110a Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 12:07:29 -0600 Subject: [PATCH 4/7] Add dialog confirmation when traceroute sent --- ui/curses_ui.py | 4 ++++ ui/dialog.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 ui/dialog.py diff --git a/ui/curses_ui.py b/ui/curses_ui.py index 394284a..47f88fc 100644 --- a/ui/curses_ui.py +++ b/ui/curses_ui.py @@ -4,6 +4,7 @@ import globals from utilities.utils import get_name_from_number, get_channels from settings import settings from message_handlers.tx_handler import send_message, send_traceroute +import ui.dialog def add_notification(channel_number): handle_notification(channel_number, add=True) @@ -327,6 +328,9 @@ def main_ui(stdscr): # Check for Ctrl + t elif char == 20: send_traceroute() + curses.curs_set(0) # Hide cursor + ui.dialog.dialog(stdscr, "Traceroute Sent", "Results will appear in messages window") + curses.curs_set(1) # Show cursor again elif char == curses.KEY_ENTER or char == 10 or char == 13: if globals.current_window == 2: diff --git a/ui/dialog.py b/ui/dialog.py new file mode 100644 index 0000000..c8bbb07 --- /dev/null +++ b/ui/dialog.py @@ -0,0 +1,34 @@ +import curses + +def dialog(stdscr, title, message): + height, width = stdscr.getmaxyx() + + # Calculate dialog dimensions + dialog_width = max(len(title) + 4, len(message) + 4) + dialog_height = 5 + x = (width - dialog_width) // 2 + y = (height - dialog_height) // 2 + + # Create dialog window + win = curses.newwin(dialog_height, dialog_width, y, x) + win.border(0) + + # Add title + win.addstr(0, 2, title) + + # Add message + win.addstr(2, 2, message) + + # Add button + win.addstr(dialog_height - 2, (dialog_width - 4) // 2, " Ok ") + + # Refresh dialog window + win.refresh() + + # Get user input + while True: + char = win.getch() + if char == curses.KEY_ENTER or char == 10 or char == 13: + win.clear() + win.refresh() + return From ad81d34551adacee05795014ab8154ddc0665a49 Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 12:44:19 -0600 Subject: [PATCH 5/7] typo Co-authored-by: pdxlocations <117498748+pdxlocations@users.noreply.github.com> --- message_handlers/tx_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/message_handlers/tx_handler.py b/message_handlers/tx_handler.py index 26fa094..1fa3de1 100644 --- a/message_handlers/tx_handler.py +++ b/message_handlers/tx_handler.py @@ -78,7 +78,7 @@ def on_response_traceroute(packet): + " (" + (str(msg_dict["snrBack"][idx] / 4) if msg_dict["snrBack"][idx] != UNK_SNR else "?") + "dB)" # End with destination of response (us) - route_str += " --> " + (get_name_from_number(packet["to"]) or f"{p['to']:08x}") \ + route_str += " --> " + (get_name_from_number(packet["to"]) or f"{packet['to']:08x}") \ + " (" + (str(msg_dict["snrBack"][-1] / 4) if msg_dict["snrBack"][-1] != UNK_SNR else "?") + "dB)" msg_str += route_str + "\n" # Print the route back to us From 68f13585b6767ad55ae3d7bf60da3290af584e8d Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 12:51:31 -0600 Subject: [PATCH 6/7] Make traceroute less verbose --- message_handlers/tx_handler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/message_handlers/tx_handler.py b/message_handlers/tx_handler.py index 1fa3de1..a45ae67 100644 --- a/message_handlers/tx_handler.py +++ b/message_handlers/tx_handler.py @@ -47,20 +47,20 @@ def on_response_traceroute(packet): route_discovery.ParseFromString(packet["decoded"]["payload"]) msg_dict = google.protobuf.json_format.MessageToDict(route_discovery) - msg_str = "Route traced towards destination:\n" + msg_str = "Traceroute to:\n" - route_str = get_name_from_number(packet["to"]) or f"{packet['to']:08x}" # Start with destination of response + route_str = get_name_from_number(packet["to"], 'short') or f"{packet['to']:08x}" # Start with destination of response # SNR list should have one more entry than the route, as the final destination adds its SNR also lenTowards = 0 if "route" not in msg_dict else len(msg_dict["route"]) snrTowardsValid = "snrTowards" in msg_dict and len(msg_dict["snrTowards"]) == lenTowards + 1 if lenTowards > 0: # Loop through hops in route and add SNR if available for idx, node_num in enumerate(msg_dict["route"]): - route_str += " --> " + (get_name_from_number(node_num) or f"{node_num:08x}") \ + route_str += " --> " + (get_name_from_number(node_num, 'short') or f"{node_num:08x}") \ + " (" + (str(msg_dict["snrTowards"][idx] / 4) if snrTowardsValid and msg_dict["snrTowards"][idx] != UNK_SNR else "?") + "dB)" # End with origin of response - route_str += " --> " + (get_name_from_number(packet["from"]) or f"{packet['from']:08x}") \ + route_str += " --> " + (get_name_from_number(packet["from"], 'short') or f"{packet['from']:08x}") \ + " (" + (str(msg_dict["snrTowards"][-1] / 4) if snrTowardsValid and msg_dict["snrTowards"][-1] != UNK_SNR else "?") + "dB)" msg_str += route_str + "\n" # Print the route towards destination @@ -69,16 +69,16 @@ def on_response_traceroute(packet): lenBack = 0 if "routeBack" not in msg_dict else len(msg_dict["routeBack"]) backValid = "hopStart" in packet and "snrBack" in msg_dict and len(msg_dict["snrBack"]) == lenBack + 1 if backValid: - msg_str += "Route traced back to us:\n" - route_str = get_name_from_number(packet["from"]) or f"{packet['from']:08x}" # Start with origin of response + msg_str += "Back:\n" + route_str = get_name_from_number(packet["from"], 'short') or f"{packet['from']:08x}" # Start with origin of response if lenBack > 0: # Loop through hops in routeBack and add SNR if available for idx, node_num in enumerate(msg_dict["routeBack"]): - route_str += " --> " + (get_name_from_number(node_num) or f"{node_num:08x}") \ + route_str += " --> " + (get_name_from_number(node_num, 'short') or f"{node_num:08x}") \ + " (" + (str(msg_dict["snrBack"][idx] / 4) if msg_dict["snrBack"][idx] != UNK_SNR else "?") + "dB)" # End with destination of response (us) - route_str += " --> " + (get_name_from_number(packet["to"]) or f"{packet['to']:08x}") \ + route_str += " --> " + (get_name_from_number(packet["to"], 'short') or f"{packet['to']:08x}") \ + " (" + (str(msg_dict["snrBack"][-1] / 4) if msg_dict["snrBack"][-1] != UNK_SNR else "?") + "dB)" msg_str += route_str + "\n" # Print the route back to us From 2a7317e61252839eaf0b9882946fade5c4028011 Mon Sep 17 00:00:00 2001 From: Russell Schmidt Date: Thu, 16 Jan 2025 13:06:36 -0600 Subject: [PATCH 7/7] Add warning about 30 second traceroute interval --- ui/curses_ui.py | 2 +- ui/dialog.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/curses_ui.py b/ui/curses_ui.py index 47f88fc..f114928 100644 --- a/ui/curses_ui.py +++ b/ui/curses_ui.py @@ -329,7 +329,7 @@ def main_ui(stdscr): elif char == 20: send_traceroute() curses.curs_set(0) # Hide cursor - ui.dialog.dialog(stdscr, "Traceroute Sent", "Results will appear in messages window") + ui.dialog.dialog(stdscr, "Traceroute Sent", "Results will appear in messages window.\nNote: Traceroute is limited to once every 30 seconds.") curses.curs_set(1) # Show cursor again elif char == curses.KEY_ENTER or char == 10 or char == 13: diff --git a/ui/dialog.py b/ui/dialog.py index c8bbb07..52ff128 100644 --- a/ui/dialog.py +++ b/ui/dialog.py @@ -4,8 +4,12 @@ def dialog(stdscr, title, message): height, width = stdscr.getmaxyx() # Calculate dialog dimensions - dialog_width = max(len(title) + 4, len(message) + 4) - dialog_height = 5 + max_line_lengh = 0 + message_lines = message.splitlines() + for l in message_lines: + max_line_length = max(len(l), max_line_lengh) + dialog_width = max(len(title) + 4, max_line_length + 4) + dialog_height = len(message_lines) + 4 x = (width - dialog_width) // 2 y = (height - dialog_height) // 2 @@ -17,7 +21,8 @@ def dialog(stdscr, title, message): win.addstr(0, 2, title) # Add message - win.addstr(2, 2, message) + for i, l in enumerate(message_lines): + win.addstr(2 + i, 2, l) # Add button win.addstr(dialog_height - 2, (dialog_width - 4) // 2, " Ok ")