diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 432e75c..10f4799 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -241,8 +241,7 @@ def onConnected(interface): if args.sendtext: closeNow = True print(f"Sending text message {args.sendtext} to {args.destOrAll}") - interface.sendText(args.sendtext, args.destOrAll, - wantAck=True) + interface.sendText(args.sendtext, args.destOrAll, wantAck=True) if args.sendping: print(f"Sending ping message {args.sendtext} to {args.destOrAll}") @@ -259,8 +258,7 @@ def onConnected(interface): for wrpair in (args.gpio_wrb or []): bitmask |= 1 << int(wrpair[0]) bitval |= int(wrpair[1]) << int(wrpair[0]) - print( - f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}") + print(f"Writing GPIO mask 0x{bitmask:x} with value 0x{bitval:x} to {args.dest}") rhc.writeGPIOs(args.dest, bitmask, bitval) closeNow = True @@ -275,6 +273,7 @@ def onConnected(interface): sys.exit(0) # Just force an exit (FIXME - ugly) rhc.readGPIOs(args.dest, bitmask, onResponse) + time.sleep(10) if args.gpio_watch: bitmask = int(args.gpio_watch, 16) @@ -752,13 +751,13 @@ def initParser(): action="store_true") parser.add_argument( - "--gpio-wrb", nargs=2, help="Set a particlar GPIO # to 1 or 0", action='append') + "--gpio-wrb", nargs=2, help="Set a particular GPIO # to 1 or 0", action='append') parser.add_argument( - "--gpio-rd", help="Read from a GPIO mask") + "--gpio-rd", help="Read from a GPIO mask (ex: '0x10')") parser.add_argument( - "--gpio-watch", help="Start watching a GPIO mask for changes") + "--gpio-watch", help="Start watching a GPIO mask for changes (ex: '0x10')") parser.add_argument( "--no-time", help="Suppress sending the current time to the mesh", action="store_true") diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index e4ba510..e7469d4 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -21,10 +21,6 @@ from . import portnums_pb2, mesh_pb2 from .util import stripnl, Timeout, our_exit from .__init__ import LOCAL_ADDR, BROADCAST_NUM, BROADCAST_ADDR, ResponseHandler, publishingThread, OUR_APP_VERSION, protocols - -defaultHopLimit = 3 - - class MeshInterface: """Interface class for meshtastic devices @@ -56,6 +52,7 @@ class MeshInterface: self.currentPacketId = random.randint(0, 0xffffffff) self.nodesByNum = None self.configId = None + self.defaultHopLimit = 3 def close(self): """Shutdown this interface""" @@ -162,7 +159,7 @@ class MeshInterface: destinationId=BROADCAST_ADDR, wantAck=False, wantResponse=False, - hopLimit=defaultHopLimit, + hopLimit=None, onResponse=None, channelIndex=0): """Send a utf8 string to some other node, if the node has a display it @@ -184,6 +181,9 @@ class MeshInterface: 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, @@ -195,7 +195,7 @@ class MeshInterface: def sendData(self, data, destinationId=BROADCAST_ADDR, portNum=portnums_pb2.PortNum.PRIVATE_APP, wantAck=False, wantResponse=False, - hopLimit=defaultHopLimit, + hopLimit=None, onResponse=None, channelIndex=0): """Send a data packet to some other node @@ -219,6 +219,9 @@ class MeshInterface: 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)}") data = data.SerializeToString() @@ -280,13 +283,15 @@ class MeshInterface: def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, - wantAck=False, hopLimit=defaultHopLimit): + wantAck=False, hopLimit=None): """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): @@ -365,7 +370,7 @@ class MeshInterface: def _waitConnected(self): """Block until the initial node db download is complete, or timeout and raise an exception""" - if not self.isConnected.wait(10.0): # timeout after 10 seconds + if not self.isConnected.wait(15.0): # timeout after x seconds raise Exception("Timed out waiting for connection completion") # If we failed while connecting, raise the connection to the client diff --git a/meshtastic/node.py b/meshtastic/node.py index 837c06a..e571b2a 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -1,6 +1,7 @@ """Node class """ +import re import logging import base64 from google.protobuf.json_format import MessageToJson @@ -20,7 +21,7 @@ class Node: self.nodeNum = nodeNum self.radioConfig = None self.channels = None - self._timeout = Timeout(maxSecs=60) + self._timeout = Timeout(maxSecs=300) self.partialChannels = None self.noProto = noProto @@ -49,6 +50,7 @@ class Node: def requestConfig(self): """Send regular MeshPackets to ask for settings and channels.""" + logging.debug(f"requestConfig for nodeNum:{self.nodeNum}") self.radioConfig = None self.channels = None self.partialChannels = [] # We keep our channels in a temp array until finished @@ -134,6 +136,7 @@ class Node: def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None): """Set device owner name""" + logging.debug(f"in setOwner nodeNum:{self.nodeNum}") nChars = 3 minChars = 2 if long_name is not None: @@ -227,19 +230,39 @@ class Node: def onResponse(p): """A closure to handle the response packet""" errorFound = False - if 'routing' in p["decoded"]: - if p["decoded"]["routing"]["errorReason"] != "NONE": - errorFound = True - print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}') + if 'routing' in p['decoded']: + if 'errorReason' in p['decoded']: + if p['decoded']['routing']['errorReason'] != 'NONE': + errorFound = True + print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}') if errorFound is False: - self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response - logging.debug("Received radio config, now fetching channels...") - self._timeout.reset() # We made foreward progress - self._requestChannel(0) # now start fetching channels + logging.debug(f'p:{p}') + if "decoded" in p: + if "admin" in p["decoded"]: + if "raw" in p["decoded"]["admin"]: + self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response + logging.debug("Received radio config, now fetching channels...") + self._timeout.reset() # We made foreward progress + self._requestChannel(0) # now start fetching channels + else: + print("Warning: There was no 'raw' in packet in onResponse() in _requestSettings()") + print(f"p:{p}") + #else: + #print("Warning: There was no 'admin' in packet in onResponse() in _requestSettings()") + #print(f"p:{p}") + else: + print("Warning: There was no 'decoded' in packet in onResponse() in _requestSettings()") + print(f"p:{p}") # Show progress message for super slow operations if self != self.iface.localNode: - print("Requesting preferences from remote node (this could take a while)") + print("Requesting preferences from remote node.") + print("Be sure:") + print(" 1. There is a SECONDARY channel named 'admin'.") + print(" 2. The '--seturl' was used to configure.") + print(" 3. All devices have the same modem config. (i.e., '--ch-longfast')") + print(" 4. All devices have been rebooted after all of the above. (optional, but recommended)") + print("Note: This could take a while (it requests remote channel configs, then writes config)") return self._sendAdmin(p, wantResponse=True, onResponse=onResponse) @@ -290,25 +313,49 @@ class Node: # Show progress message for super slow operations if self != self.iface.localNode: - logging.info(f"Requesting channel {channelNum} info from remote node (this could take a while)") + print(f"Requesting channel {channelNum} info from remote node (this could take a while)") + logging.debug(f"Requesting channel {channelNum} info from remote node (this could take a while)") else: logging.debug(f"Requesting channel {channelNum}") def onResponse(p): """A closure to handle the response packet for requesting a channel""" - c = p["decoded"]["admin"]["raw"].get_channel_response - self.partialChannels.append(c) - self._timeout.reset() # We made foreward progress - logging.debug(f"Received channel {stripnl(c)}") - index = c.index - # for stress testing, we can always download all channels - fastChannelDownload = True + quitEarly = False - # Once we see a response that has NO settings, assume - # we are at the end of channels and stop fetching - quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload + index = len(self.partialChannels) + logging.debug(f'index:{index}') + logging.debug(f' p:{p}') + if 'decoded' in p: + if 'admin' in p['decoded']: + if 'raw' in p['decoded']['admin']: + c = p["decoded"]["admin"]["raw"].get_channel_response + # Once we see a response that has NO settings, assume + # we are at the end of channels and stop fetching + tmp = f"{stripnl(c)}" + logging.debug(f'tmp:{tmp}:') + if re.search(r'settings { }', tmp): + quitEarly = True + logging.debug(f'Set quitEarly') + else: + self.partialChannels.append(c) + logging.debug(f"Received channel {stripnl(c)}") + #index = c.index + self._timeout.reset() # We made foreward progress + else: + print("Warning: There was no 'raw' in packet to onResponse() in _requestChannel.") + print(f"p:{p}") + else: + #print("Warning: There was no 'admin' in packet to onResponse() in _requestChannel.") + #print(f"p:{p}") + pass + else: + print("Warning: There was no 'decoded' in packet to onResponse() in _requestChannel.") + print(f"p:{p}") + + # TODO: is myInfo.max_channels the same as the remote's max channels? + logging.debug(f'index:{index} self.iface.myInfo.max_channels:{self.iface.myInfo.max_channels}') if quitEarly or index >= self.iface.myInfo.max_channels - 1: logging.debug("Finished downloading channels")