From e8ba5581f6bdb393d40722cc0ccf2175f87368e2 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Tue, 16 Apr 2024 15:43:24 -0700 Subject: [PATCH] Add a `--request-position` argument to request positions from nodes --- meshtastic/__main__.py | 17 +++++++++++- meshtastic/mesh_interface.py | 50 +++++++++++++++++++++++++++++++----- meshtastic/util.py | 12 +++++++++ 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index c98bc2a9..7c178f21 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -435,6 +435,13 @@ def onConnected(interface): print(f"Sending telemetry request to {args.dest} (this could take a while)") interface.sendTelemetry(destinationId=args.dest, wantResponse=True) + if args.request_position: + if args.dest == BROADCAST_ADDR: + meshtastic.util.our_exit("Warning: Must use a destination node ID.") + else: + print(f"Sending position request to {args.dest} (this could take a while)") + interface.sendPosition(destinationId=args.dest, wantResponse=True) + if args.gpio_wrb or args.gpio_rd or args.gpio_watch: if args.dest == BROADCAST_ADDR: meshtastic.util.our_exit("Warning: Must use a destination node ID.") @@ -1299,7 +1306,15 @@ def initParser(): group.add_argument( "--request-telemetry", help="Request telemetry from a node. " - "You need pass the destination ID as argument with '--dest'. " + "You need to pass the destination ID as argument with '--dest'. " + "For repeaters, the nodeNum is required.", + action="store_true", + ) + + group.add_argument( + "--request-position", + help="Request the position from a nade. " + "You need to pass the destination ID as an argument with '--dest'. " "For repeaters, the nodeNum is required.", action="store_true", ) diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 891de46a..aee0868b 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -381,13 +381,50 @@ def sendPosition( p.time = timeSec logging.debug(f"p.time:{p.time}") - return self.sendData( + if wantResponse: + onResponse = self.onResponsePosition + else: + onResponse = None + + d = self.sendData( p, destinationId, portNum=portnums_pb2.PortNum.POSITION_APP, wantAck=wantAck, wantResponse=wantResponse, + onResponse=onResponse, ) + if wantResponse: + self.waitForPosition() + return d + + def onResponsePosition(self, p): + """on response for position""" + if p["decoded"]["portnum"] == 'POSITION_APP': + self._acknowledgment.receivedPosition = True + position = mesh_pb2.Position() + position.ParseFromString(p["decoded"]["payload"]) + + ret = "Position received: " + if position.latitude_i != 0 and position.longitude_i != 0: + ret += f"({position.latitude_i * 10**-7}, {position.longitude_i * 10**-7})" + else: + ret += "(unknown)" + if position.altitude != 0: + ret += f" {position.altitude}m" + + if position.precision_bits not in [0,32]: + ret += f" precision:{position.precision_bits}" + elif position.precision_bits == 32: + ret += " full precision" + elif position.precision_bits == 0: + ret += " position disabled" + + print(ret) + + elif p["decoded"]["portnum"] == 'ROUTING_APP': + if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE': + our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.") def sendTraceRoute(self, dest: Union[int, str], hopLimit: int): """Send the trace route""" @@ -445,11 +482,6 @@ def sendTelemetry(self, destinationId=BROADCAST_ADDR, wantResponse=False): else: onResponse = None - if destinationId.startswith("!"): - destinationId = int(destinationId[1:], 16) - else: - destinationId = int(destinationId) - self.sendData( r, destinationId=destinationId, @@ -573,6 +605,12 @@ def waitForTelemetry(self): if not success: raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry") + def waitForPosition(self): + """Wait for position""" + success = self._timeout.waitForPosition(self._acknowledgment) + if not success: + raise MeshInterface.MeshInterfaceError("Timed out waiting for position") + def getMyNodeInfo(self) -> Optional[Dict]: """Get info about my node.""" if self.myInfo is None or self.nodesByNum is None: diff --git a/meshtastic/util.py b/meshtastic/util.py index f5b0724c..76b4b9e5 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -206,6 +206,16 @@ def waitForTelemetry(self, acknowledgment) -> bool: time.sleep(self.sleepInterval) return False + def waitForPosition(self, acknowledgment) -> bool: + """Block until position response is received. Returns True if position response has been received.""" + self.reset() + while time.time() < self.expireTime: + if getattr(acknowledgment, "receivedPosition", 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." @@ -216,6 +226,7 @@ def __init__(self): self.receivedImplAck = False self.receivedTraceRoute = False self.receivedTelemetry = False + self.receivedPosition = False def reset(self): """reset""" @@ -224,6 +235,7 @@ def reset(self): self.receivedImplAck = False self.receivedTraceRoute = False self.receivedTelemetry = False + self.receivedPosition = False class DeferredExecution: