Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion meshtastic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,5 +183,6 @@ def _receiveInfoUpdate(iface, asDict):
portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol("telemetry", telemetry_pb2.Telemetry),
portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol("remotehw", remote_hardware_pb2.HardwareMessage),
portnums_pb2.PortNum.SIMULATOR_APP: KnownProtocol("simulator", mesh_pb2.Compressed)
portnums_pb2.PortNum.SIMULATOR_APP: KnownProtocol("simulator", mesh_pb2.Compressed),
portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol("traceroute", mesh_pb2.RouteDiscovery)
}
15 changes: 14 additions & 1 deletion meshtastic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ def onConnected(interface):
interface.sendData(payload, args.dest, portNum=portnums_pb2.PortNum.REPLY_APP,
wantAck=True, wantResponse=True)

if args.traceroute:
loraConfig = getattr(interface.localNode.localConfig, 'lora')
hopLimit = getattr(loraConfig, 'hop_limit')
dest = str(args.traceroute)
print(f"Sending traceroute request to {dest} (this could take a while)")
interface.sendTraceRoute(dest, hopLimit)

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.")
Expand Down Expand Up @@ -889,7 +896,7 @@ def initParser():
"--ch-set", help=("Set a channel parameter. To see channel settings available:'--ch-set all all --ch-index 0'. "
"Can set the 'psk' using this command. To disable encryption on primary channel:'--ch-set psk none --ch-index 0'. "
"To set encryption with a new random key on second channel:'--ch-set psk random --ch-index 1'. "
"To set encryption back to the default:'--ch-set default --ch-index 0'. To set encryption with your "
"To set encryption back to the default:'--ch-set psk default --ch-index 0'. To set encryption with your "
"own key: '--ch-set psk 0x1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b1a1a1a1a2b2b2b2b --ch-index 0'."),
nargs=2, action='append')

Expand Down Expand Up @@ -935,6 +942,12 @@ def initParser():
parser.add_argument(
"--sendping", help="Send a ping message (which requests a reply)", action="store_true")

parser.add_argument(
"--traceroute", help="Traceroute from connected node to a destination. " \
"You need pass the destination ID as argument, like " \
"this: '--traceroute !ba4bf9d0' " \
"Only nodes that have the encryption key can be traced.")

parser.add_argument(
"--reboot", help="Tell the destination node to reboot", action="store_true")

Expand Down
45 changes: 32 additions & 13 deletions meshtastic/mesh_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def __init__(self, debugOut=None, noProto=False):
self.currentPacketId = random.randint(0, 0xffffffff)
self.nodesByNum = None
self.configId = None
self.defaultHopLimit = 3
self.gotResponse = False # used in gpio read
self.mask = None # used in gpio read and gpio watch

Expand Down Expand Up @@ -176,7 +175,6 @@ def sendText(self, text: AnyStr,
destinationId=BROADCAST_ADDR,
wantAck=False,
wantResponse=False,
hopLimit=None,
onResponse=None,
channelIndex=0):
"""Send a utf8 string to some other node, if the node has a display it
Expand All @@ -198,21 +196,17 @@ def sendText(self, text: AnyStr,
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
if hopLimit is None:
hopLimit = self.defaultHopLimit

return self.sendData(text.encode("utf-8"), destinationId,
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
wantAck=wantAck,
wantResponse=wantResponse,
hopLimit=hopLimit,
onResponse=onResponse,
channelIndex=channelIndex)

def sendData(self, data, destinationId=BROADCAST_ADDR,
portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False,
wantResponse=False,
hopLimit=None,
onResponse=None,
channelIndex=0):
"""Send a data packet to some other node
Expand All @@ -237,8 +231,6 @@ def sendData(self, data, destinationId=BROADCAST_ADDR,
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
if hopLimit is None:
hopLimit = self.defaultHopLimit

if getattr(data, "SerializeToString", None):
logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
Expand All @@ -261,8 +253,7 @@ def sendData(self, data, destinationId=BROADCAST_ADDR,

if onResponse is not None:
self._addResponseHandler(meshPacket.id, onResponse)
p = self._sendPacket(meshPacket, destinationId,
wantAck=wantAck, hopLimit=hopLimit)
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
return p

def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
Expand Down Expand Up @@ -300,21 +291,42 @@ def sendPosition(self, latitude=0.0, longitude=0.0, altitude=0, timeSec=0,
portNum=portnums_pb2.PortNum.POSITION_APP,
wantAck=wantAck,
wantResponse=wantResponse)

def sendTraceRoute(self, dest, hopLimit):
r = mesh_pb2.RouteDiscovery()
self.sendData(r, destinationId=dest, portNum=portnums_pb2.PortNum.TRACEROUTE_APP,
wantResponse=True, onResponse=self.onResponseTraceRoute)
# extend timeout based on number of nodes, limit by configured hopLimit
waitFactor = min(len(self.nodes)-1, hopLimit)
self.waitForTraceRoute(waitFactor)

def onResponseTraceRoute(self, p):
routeDiscovery = mesh_pb2.RouteDiscovery()
routeDiscovery.ParseFromString(p["decoded"]["payload"])
asDict = google.protobuf.json_format.MessageToDict(routeDiscovery)

print("Route traced:")
routeStr = self._nodeNumToId(p["to"])
if "route" in asDict:
for nodeNum in asDict["route"]:
routeStr += " --> " + self._nodeNumToId(nodeNum)
routeStr += " --> " + self._nodeNumToId(p["from"])
print(routeStr)

self._acknowledgment.receivedTraceRoute = True

def _addResponseHandler(self, requestId, callback):
self.responseHandlers[requestId] = ResponseHandler(callback)

def _sendPacket(self, meshPacket,
destinationId=BROADCAST_ADDR,
wantAck=False, hopLimit=None):
wantAck=False):
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don't want this - use sendData instead.

Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
if hopLimit is None:
hopLimit = self.defaultHopLimit

# We allow users to talk to the local node before we've completed the full connection flow...
if(self.myInfo is not None and destinationId != self.myInfo.my_node_num):
Expand Down Expand Up @@ -348,6 +360,8 @@ def _sendPacket(self, meshPacket,

meshPacket.to = nodeNum
meshPacket.want_ack = wantAck
loraConfig = getattr(self.localNode.localConfig, 'lora')
hopLimit = getattr(loraConfig, 'hop_limit')
meshPacket.hop_limit = hopLimit

# if the user hasn't set an ID for this packet (likely and recommended),
Expand All @@ -374,6 +388,11 @@ def waitForAckNak(self):
if not success:
raise Exception("Timed out waiting for an acknowledgment")

def waitForTraceRoute(self, waitFactor):
success = self._timeout.waitForTraceRoute(waitFactor, self._acknowledgment)
if not success:
raise Exception("Timed out waiting for traceroute")

def getMyNodeInfo(self):
"""Get info about my node."""
if self.myInfo is None:
Expand Down
13 changes: 13 additions & 0 deletions meshtastic/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,17 +169,30 @@ def waitForAckNak(self, acknowledgment, attrs=('receivedAck', 'receivedNak', 're
time.sleep(self.sleepInterval)
return False

def waitForTraceRoute(self, waitFactor, acknowledgment, attr='receivedTraceRoute'):
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
self.expireTimeout *= waitFactor
self.reset()
while time.time() < self.expireTime:
if getattr(acknowledgment, attr, 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."
def __init__(self):
self.receivedAck = False
self.receivedNak = False
self.receivedImplAck = False
self.receivedTraceRoute = False

def reset(self):
self.receivedAck = False
self.receivedNak = False
self.receivedImplAck = False
self.receivedTraceRoute = False

class DeferredExecution():
"""A thread that accepts closures to run, and runs them as they are received"""
Expand Down