diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index ca1680a58..081ebac9e 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -642,6 +642,21 @@ def onConnected(interface): telemetryType=telemType, ) + if args.request_nodeinfo: + if args.dest == BROADCAST_ADDR: + meshtastic.util.our_exit("Warning: Must use a destination node ID.") + else: + channelIndex = mt_config.channel_index or 0 + if checkChannel(interface, channelIndex): + print( + f"Sending NodeInfo request to {args.dest} on channelIndex:{channelIndex} (this could take a while)" + ) + interface.requestNodeInfo( + destinationId=args.dest, + wantResponse=True, + channelIndex=channelIndex, + ) + if args.request_position: if args.dest == BROADCAST_ADDR: meshtastic.util.our_exit("Warning: Must use a destination node ID.") @@ -2267,6 +2282,14 @@ def addRemoteActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar metavar="TYPE", ) + group.add_argument( + "--request-nodeinfo", + help="Request NodeInfo (name, hardware model, etc) from a node. " + "You need to pass the destination ID as an argument with '--dest'. " + "For repeaters, the nodeNum is required.", + action="store_true", + ) + group.add_argument( "--request-position", help="Request the position from a node. " diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 1a7dd6eae..4845cd8a1 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -582,6 +582,35 @@ def sendData( p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit, pkiEncrypted=pkiEncrypted, publicKey=publicKey) return p + def requestNodeInfo( + self, + destinationId: Union[int, str] = BROADCAST_ADDR, + wantResponse: bool = False, + channelIndex: int = 0, + hopLimit: Optional[int] = None, + ): + """Send a NodeInfo request and optionally ask for a response""" + u = mesh_pb2.User() + + if wantResponse: + onResponse = self.onResponseNodeInfo + else: + onResponse = None + + d = self.sendData( + u, + destinationId=destinationId, + portNum=portnums_pb2.PortNum.NODEINFO_APP, + wantResponse=wantResponse, + onResponse=onResponse, + channelIndex=channelIndex, + hopLimit=hopLimit, + ) + + if wantResponse: + self.waitForNodeInfo() + return d + def sendPosition( self, latitude: float = 0.0, @@ -634,6 +663,16 @@ def sendPosition( self.waitForPosition() return d + def onResponseNodeInfo(self, p: dict): + """on response for NodeInfo""" + if p["decoded"]["portnum"] == "NODEINFO_APP": + self._acknowledgment.receivedNodeInfo = True + if "user" in p["decoded"]: + print("NodeInfo received:") + for key, value in p["decoded"]["user"].items(): + if key != "raw": + print(f"{key}: {value}") + def onResponsePosition(self, p): """on response for position""" if p["decoded"]["portnum"] == "POSITION_APP": @@ -1067,6 +1106,12 @@ def waitForTelemetry(self): if not success: raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry") + def waitForNodeInfo(self): + """Wait for NodeInfo""" + success = self._timeout.waitForNodeInfo(self._acknowledgment) + if not success: + raise MeshInterface.MeshInterfaceError("Timed out waiting for NodeInfo") + def waitForPosition(self): """Wait for position""" success = self._timeout.waitForPosition(self._acknowledgment) diff --git a/meshtastic/util.py b/meshtastic/util.py index 7faa2d6c0..e127f930f 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -266,6 +266,16 @@ def waitForWaypoint(self, acknowledgment) -> bool: time.sleep(self.sleepInterval) return False + def waitForNodeInfo(self, acknowledgment) -> bool: + """Block until NodeInfo response is received. Returns True if NodeInfo response has been received.""" + self.reset() + while time.time() < self.expireTime: + if getattr(acknowledgment, "receivedNodeInfo", None): + acknowledgment.reset() + return True + time.sleep(self.sleepInterval) + return False + class Acknowledgment: "A class that records which type of acknowledgment was just received, if any." @@ -278,6 +288,7 @@ def __init__(self) -> None: self.receivedTelemetry = False self.receivedPosition = False self.receivedWaypoint = False + self.receivedNodeInfo = False def reset(self) -> None: """reset""" @@ -288,6 +299,7 @@ def reset(self) -> None: self.receivedTelemetry = False self.receivedPosition = False self.receivedWaypoint = False + self.receivedNodeInfo = False class DeferredExecution: