Skip to content

Commit ce3065b

Browse files
authored
Merge pull request #457 from GUVWAF/reqTelemetry
Add `request-telemetry` option
2 parents 95bfc0b + d6ee815 commit ce3065b

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

meshtastic/__main__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,13 @@ def onConnected(interface):
412412
print(f"Sending traceroute request to {dest} (this could take a while)")
413413
interface.sendTraceRoute(dest, hopLimit)
414414

415+
if args.request_telemetry:
416+
if args.dest == BROADCAST_ADDR:
417+
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
418+
else:
419+
print(f"Sending telemetry request to {args.dest} (this could take a while)")
420+
interface.sendTelemetry(destinationId=args.dest, wantResponse=True)
421+
415422
if args.gpio_wrb or args.gpio_rd or args.gpio_watch:
416423
if args.dest == BROADCAST_ADDR:
417424
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
@@ -1188,6 +1195,14 @@ def initParser():
11881195
"Only nodes that have the encryption key can be traced.",
11891196
)
11901197

1198+
parser.add_argument(
1199+
"--request-telemetry",
1200+
help="Request telemetry from a node. "
1201+
"You need pass the destination ID as argument with '--dest'. "
1202+
"For repeaters, the nodeNum is required.",
1203+
action="store_true",
1204+
)
1205+
11911206
parser.add_argument(
11921207
"--ack",
11931208
help="Use in combination with --sendtext to wait for an acknowledgment.",

meshtastic/mesh_interface.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from tabulate import tabulate
1919

2020
import meshtastic.node
21-
from meshtastic import mesh_pb2, portnums_pb2
21+
from meshtastic import mesh_pb2, portnums_pb2, telemetry_pb2
2222
from meshtastic.__init__ import (
2323
BROADCAST_ADDR,
2424
BROADCAST_NUM,
@@ -409,6 +409,70 @@ def onResponseTraceRoute(self, p):
409409

410410
self._acknowledgment.receivedTraceRoute = True
411411

412+
def sendTelemetry(self, destinationId=BROADCAST_ADDR, wantResponse=False):
413+
"""Send telemetry and optionally ask for a response"""
414+
r = telemetry_pb2.Telemetry()
415+
416+
node = next(n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum)
417+
if node is not None:
418+
metrics = node.get("deviceMetrics")
419+
if metrics:
420+
batteryLevel = metrics.get("batteryLevel")
421+
if batteryLevel is not None:
422+
r.device_metrics.battery_level = batteryLevel
423+
voltage = metrics.get("voltage")
424+
if voltage is not None:
425+
r.device_metrics.voltage = voltage
426+
channel_utilization = metrics.get("channelUtilization")
427+
if channel_utilization is not None:
428+
r.device_metrics.channel_utilization = channel_utilization
429+
air_util_tx = metrics.get("airUtilTx")
430+
if air_util_tx is not None:
431+
r.device_metrics.air_util_tx = air_util_tx
432+
433+
if wantResponse:
434+
onResponse = self.onResponseTelemetry
435+
else:
436+
onResponse = None
437+
438+
if destinationId.startswith("!"):
439+
destinationId = int(destinationId[1:], 16)
440+
else:
441+
destinationId = int(destinationId)
442+
443+
self.sendData(
444+
r,
445+
destinationId=destinationId,
446+
portNum=portnums_pb2.PortNum.TELEMETRY_APP,
447+
wantResponse=wantResponse,
448+
onResponse=onResponse,
449+
)
450+
if wantResponse:
451+
self.waitForTelemetry()
452+
453+
def onResponseTelemetry(self, p):
454+
"""on response for telemetry"""
455+
if p["decoded"]["portnum"] == 'TELEMETRY_APP':
456+
self._acknowledgment.receivedTelemetry = True
457+
telemetry = telemetry_pb2.Telemetry()
458+
telemetry.ParseFromString(p["decoded"]["payload"])
459+
460+
print("Telemetry received:")
461+
if telemetry.device_metrics.battery_level is not None:
462+
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
463+
if telemetry.device_metrics.voltage is not None:
464+
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
465+
if telemetry.device_metrics.channel_utilization is not None:
466+
print(
467+
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
468+
)
469+
if telemetry.device_metrics.air_util_tx is not None:
470+
print(f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%")
471+
472+
elif p["decoded"]["portnum"] == 'ROUTING_APP':
473+
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
474+
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
475+
412476
def _addResponseHandler(self, requestId, callback):
413477
self.responseHandlers[requestId] = ResponseHandler(callback)
414478

@@ -491,6 +555,12 @@ def waitForTraceRoute(self, waitFactor):
491555
success = self._timeout.waitForTraceRoute(waitFactor, self._acknowledgment)
492556
if not success:
493557
raise Exception("Timed out waiting for traceroute")
558+
559+
def waitForTelemetry(self):
560+
"""Wait for telemetry"""
561+
success = self._timeout.waitForTelemetry(self._acknowledgment)
562+
if not success:
563+
raise Exception("Timed out waiting for telemetry")
494564

495565
def getMyNodeInfo(self):
496566
"""Get info about my node."""

meshtastic/util.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,16 @@ def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute
192192
return True
193193
time.sleep(self.sleepInterval)
194194
return False
195-
195+
196+
def waitForTelemetry(self, acknowledgment):
197+
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
198+
self.reset()
199+
while time.time() < self.expireTime:
200+
if getattr(acknowledgment, "receivedTelemetry", None):
201+
acknowledgment.reset()
202+
return True
203+
time.sleep(self.sleepInterval)
204+
return False
196205

197206
class Acknowledgment:
198207
"A class that records which type of acknowledgment was just received, if any."
@@ -203,13 +212,15 @@ def __init__(self):
203212
self.receivedNak = False
204213
self.receivedImplAck = False
205214
self.receivedTraceRoute = False
215+
self.receivedTelemetry = False
206216

207217
def reset(self):
208218
"""reset"""
209219
self.receivedAck = False
210220
self.receivedNak = False
211221
self.receivedImplAck = False
212222
self.receivedTraceRoute = False
223+
self.receivedTelemetry = False
213224

214225

215226
class DeferredExecution:

0 commit comments

Comments
 (0)