From 82554a1f186853f7fc63627194d49719c867c85d Mon Sep 17 00:00:00 2001 From: David Andrzejewski Date: Sat, 15 Feb 2025 02:23:13 -0500 Subject: [PATCH] Add optional parameter to specify what fields to show with --nodes --- .vscode/launch.json | 17 ++++++++++++++++ meshtastic/__main__.py | 13 +++++++++++- meshtastic/mesh_interface.py | 38 +++++++++++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c179060..4103a6a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -245,6 +245,23 @@ "module": "meshtastic", "justMyCode": true, "args": ["--debug", "--nodes"] + }, + { + "name": "meshtastic nodes table", + "type": "debugpy", + "request": "launch", + "module": "meshtastic", + "justMyCode": true, + "args": ["--nodes"] + }, + { + "name": "meshtastic nodes table with show-fields", + "type": "debugpy", + "request": "launch", + "module": "meshtastic", + "justMyCode": true, + "args": ["--nodes", "--show-fields", "AKA,Pubkey,Role,Role,Role,Latitude,Latitude,deviceMetrics.voltage"] } + ] } diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 8f2aaf9..85c906a 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -921,7 +921,11 @@ def onConnected(interface): if args.dest != BROADCAST_ADDR: print("Showing node list of a remote node is not supported.") return - interface.showNodes() + interface.showNodes(True, args.show_fields) + + if args.show_fields and not args.nodes: + print("--show-fields can only be used with --nodes") + return if args.qr or args.qr_all: closeNow = True @@ -1626,6 +1630,13 @@ def addLocalActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars help="Print Node List in a pretty formatted table", action="store_true", ) + + group.add_argument( + "--show-fields", + help="Specify fields to show (comma-separated) when using --nodes", + type=lambda s: s.split(','), + default=None + ) return parser diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 57545c6..035f75c 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -222,7 +222,7 @@ class MeshInterface: # pylint: disable=R0902 return infos def showNodes( - self, includeSelf: bool = True + self, includeSelf: bool = True, showFields: Optional[List[str]] = None ) -> str: # pylint: disable=W0613 """Show table summary of nodes in mesh""" @@ -246,6 +246,23 @@ class MeshInterface: # pylint: disable=R0902 return None # not handling a timestamp from the future return _timeago(delta_secs) + def getNestedValue(node_dict: Dict[str, Any], key_path: str) -> Any: + keys = key_path.split(".") + value = node_dict + for key in keys: + if isinstance(value, dict): + value = value.get(key) + else: + return None + return value + + if showFields is None or showFields.count == 0: + # The default set of fields to show (e.g., the status quo) + showFields = ["N", "User", "ID", "AKA", "Hardware", "Pubkey", "Role", "Latitude", "Longitude", "Altitude", "Battery", "Channel util.", "Tx air util.", "SNR", "Hops", "Channel", "LastHeard", "Since"] + else: + # Always at least include the row number. + showFields.insert(0, "N") + rows: List[Dict[str, Any]] = [] if self.nodesByNum: logging.debug(f"self.nodes:{self.nodes}") @@ -287,11 +304,12 @@ class MeshInterface: # pylint: disable=R0902 if metrics: batteryLevel = metrics.get("batteryLevel") if batteryLevel is not None: - if batteryLevel == 0: + if batteryLevel in (0, 101): # Note: for boards without battery pin or PMU, 101% battery means 'the board is using external power' batteryString = "Powered" else: batteryString = str(batteryLevel) + "%" row.update({"Battery": batteryString}) + row.update( { "Channel util.": formatFloat( @@ -313,7 +331,21 @@ class MeshInterface: # pylint: disable=R0902 } ) - rows.append(row) + # This allows the user to specify fields that wouldn't otherwise be included. + extraFields = {} + for field in showFields: + if field in row: + # We already have it, move along. + continue + elif "." in field: + extraFields[field] = getNestedValue(node, field) + else: + extraFields[field] = node.get(field) + + # Filter out any field in the data set that was not specified. + filteredData = {key: value for key, value in row.items() if key in showFields} + filteredData.update(extraFields) + rows.append(filteredData) rows.sort(key=lambda r: r.get("LastHeard") or "0000", reverse=True) for i, row in enumerate(rows):