This commit is contained in:
Kevin Hester
2021-03-27 16:39:33 +08:00
parent d80c61c8bd
commit 9963587eb4
9 changed files with 228 additions and 115 deletions

View File

@@ -51,7 +51,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
syntax='proto3',
serialized_options=b'\n\023com.geeksville.meshB\013AdminProtosH\003',
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\x1a\rchannel.proto\"\xe1\x02\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x42\t\n\x07variantB$\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03\x62\x06proto3'
serialized_pb=b'\n\x0b\x61\x64min.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\x1a\rchannel.proto\"\xfb\x02\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x42\t\n\x07variantB$\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03\x62\x06proto3'
,
dependencies=[mesh__pb2.DESCRIPTOR,radioconfig__pb2.DESCRIPTOR,channel__pb2.DESCRIPTOR,])
@@ -136,6 +136,13 @@ _ADMINMESSAGE = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='reboot_seconds', full_name='AdminMessage.reboot_seconds', index=10,
number=35, type=5, cpp_type=1, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
@@ -154,7 +161,7 @@ _ADMINMESSAGE = _descriptor.Descriptor(
fields=[]),
],
serialized_start=62,
serialized_end=415,
serialized_end=441,
)
_ADMINMESSAGE.fields_by_name['set_radio'].message_type = radioconfig__pb2._RADIOCONFIG
@@ -192,6 +199,9 @@ _ADMINMESSAGE.fields_by_name['confirm_set_radio'].containing_oneof = _AD
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['exit_simulator'])
_ADMINMESSAGE.fields_by_name['exit_simulator'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
_ADMINMESSAGE.oneofs_by_name['variant'].fields.append(
_ADMINMESSAGE.fields_by_name['reboot_seconds'])
_ADMINMESSAGE.fields_by_name['reboot_seconds'].containing_oneof = _ADMINMESSAGE.oneofs_by_name['variant']
DESCRIPTOR.message_types_by_name['AdminMessage'] = _ADMINMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
@@ -264,6 +274,10 @@ DESCRIPTOR._options = None
<dd>
<div class="desc"><p>Field AdminMessage.get_radio_response</p></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.reboot_seconds"><code class="name">var <span class="ident">reboot_seconds</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.reboot_seconds</p></div>
</dd>
<dt id="meshtastic.admin_pb2.AdminMessage.set_channel"><code class="name">var <span class="ident">set_channel</span></code></dt>
<dd>
<div class="desc"><p>Field AdminMessage.set_channel</p></div>
@@ -305,6 +319,7 @@ DESCRIPTOR._options = None
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_channel_response" href="#meshtastic.admin_pb2.AdminMessage.get_channel_response">get_channel_response</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_radio_request" href="#meshtastic.admin_pb2.AdminMessage.get_radio_request">get_radio_request</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.get_radio_response" href="#meshtastic.admin_pb2.AdminMessage.get_radio_response">get_radio_response</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.reboot_seconds" href="#meshtastic.admin_pb2.AdminMessage.reboot_seconds">reboot_seconds</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.set_channel" href="#meshtastic.admin_pb2.AdminMessage.set_channel">set_channel</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.set_owner" href="#meshtastic.admin_pb2.AdminMessage.set_owner">set_owner</a></code></li>
<li><code><a title="meshtastic.admin_pb2.AdminMessage.set_radio" href="#meshtastic.admin_pb2.AdminMessage.set_radio">set_radio</a></code></li>

View File

@@ -184,6 +184,7 @@ OUR_APP_VERSION = 20200
publishingThread = DeferredExecution(&#34;publishing&#34;)
class ResponseHandler(NamedTuple):
&#34;&#34;&#34;A pending response callback, waiting for a response to one of our messages&#34;&#34;&#34;
# requestId: int - used only as a key
@@ -214,7 +215,7 @@ def pskToString(psk: bytes):
&#34;&#34;&#34;Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string&#34;&#34;&#34;
if len(psk) == 0:
return &#34;unencrypted&#34;
elif len(psk) == 1:
elif len(psk) == 1:
b = psk[0]
if b == 0:
return &#34;unencrypted&#34;
@@ -225,6 +226,7 @@ def pskToString(psk: bytes):
else:
return &#34;secret&#34;
class Node:
&#34;&#34;&#34;A model of a (local or remote) node in the mesh
@@ -244,19 +246,20 @@ class Node:
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
cStr = stripnl(MessageToJson(c.settings))
print(f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll = False)
adminURL = self.getURL(includeAll = True)
print(
f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f&#34;\nPrimary channel URL: {publicURL}&#34;)
if adminURL != publicURL:
print(f&#34;Complete URL (includes all channels): {adminURL}&#34;)
def showInfo(self):
&#34;&#34;&#34;Show human readable description of our node&#34;&#34;&#34;
print(f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
print(
f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
self.showChannels()
def requestConfig(self):
&#34;&#34;&#34;
Send regular MeshPackets to ask for settings and channels
@@ -282,7 +285,7 @@ class Node:
self._sendAdmin(p)
logging.debug(&#34;Wrote config&#34;)
def writeChannel(self, channelIndex, adminIndex = 0):
def writeChannel(self, channelIndex, adminIndex=0):
&#34;&#34;&#34;Write the current (edited) channel to the device&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
@@ -302,7 +305,7 @@ class Node:
adminIndex = self.iface.localNode._getAdminChannelIndex()
self.channels.pop(channelIndex)
self._fixupChannels() # expand back to 8 channels
self._fixupChannels() # expand back to 8 channels
index = channelIndex
while index &lt; self.iface.myInfo.max_channels:
@@ -311,7 +314,8 @@ class Node:
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index &gt;= adminIndex:
adminIndex = 0 # We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
# We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
&#34;&#34;&#34;Try to find the named channel or return None&#34;&#34;&#34;
@@ -419,7 +423,7 @@ class Node:
&#34;&#34;&#34;A closure to handle the response packet&#34;&#34;&#34;
self.radioConfig = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_radio_response
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._requestChannel(0) # now start fetching channels
self._requestChannel(0) # now start fetching channels
return self._sendAdmin(p,
wantResponse=True,
@@ -432,14 +436,24 @@ class Node:
p = admin_pb2.AdminMessage()
p.exit_simulator = True
return self._sendAdmin(p)
return self._sendAdmin(p)
def reboot(self, secs: int = 10):
&#34;&#34;&#34;
Tell the node to reboot
&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f&#34;Telling node to reboot in {secs} seconds&#34;)
return self._sendAdmin(p)
def _fixupChannels(self):
&#34;&#34;&#34;Fixup indexes and add disabled channels as needed&#34;&#34;&#34;
# Add extra disabled channels as needed
for index, ch in enumerate(self.channels):
ch.index = index # fixup indexes
ch.index = index # fixup indexes
self._fillChannels()
@@ -493,11 +507,11 @@ class Node:
onResponse=onResponse)
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
onResponse=None,
onResponse=None,
adminIndex=0):
&#34;&#34;&#34;Send an admin message to the specified node (or the local node if destNodeNum is zero)&#34;&#34;&#34;
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex()
return self.iface.sendData(p, self.nodeNum,
@@ -563,10 +577,11 @@ class MeshInterface:
if nodeId == LOCAL_ADDR:
return self.localNode
else:
logging.info(&#34;Requesting configuration from remote node (this could take a while)&#34;)
logging.info(
&#34;Requesting configuration from remote node (this could take a while)&#34;)
n = Node(self, nodeId)
n.requestConfig()
if not n.waitForConfig(maxsecs = 60):
if not n.waitForConfig(maxsecs=60):
raise Exception(&#34;Timed out waiting for node config&#34;)
return n
@@ -693,7 +708,8 @@ class MeshInterface:
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
elif destinationId.startswith(&#34;!&#34;): # A simple hex style nodeid - we can parse this without needing the DB
# A simple hex style nodeid - we can parse this without needing the DB
elif destinationId.startswith(&#34;!&#34;):
nodeNum = int(destinationId[1:], 16)
else:
node = self.nodes.get(destinationId)
@@ -717,7 +733,8 @@ class MeshInterface:
def waitForConfig(self):
&#34;&#34;&#34;Block until radio config is received. Returns True if config has been received.&#34;&#34;&#34;
success = waitForSet(self, attrs=(&#39;myInfo&#39;, &#39;nodes&#39;)) and self.localNode.waitForConfig()
success = waitForSet(self, attrs=(&#39;myInfo&#39;, &#39;nodes&#39;)
) and self.localNode.waitForConfig()
if not success:
raise Exception(&#34;Timed out waiting for interface config&#34;)
@@ -787,7 +804,7 @@ class MeshInterface:
startConfig = mesh_pb2.ToRadio()
self.configId = random.randint(0, 0xffffffff)
startConfig.want_config_id = self.configId
startConfig.want_config_id = self.configId
self._sendToRadio(startConfig)
def _sendDisconnect(self):
@@ -856,7 +873,7 @@ class MeshInterface:
if &#34;user&#34; in node: # Some nodes might not have user/ids assigned yet
self.nodes[node[&#34;user&#34;][&#34;id&#34;]] = node
publishingThread.queueWork(lambda: pub.sendMessage(&#34;meshtastic.node.updated&#34;,
node=node, interface=self))
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
# we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id
logging.debug(f&#34;Config complete ID {self.configId}&#34;)
@@ -943,7 +960,7 @@ class MeshInterface:
asDict[&#34;fromId&#34;] = self._nodeNumToId(asDict[&#34;from&#34;])
except Exception as ex:
logging.warn(f&#34;Not populating fromId {ex}&#34;)
try:
try:
asDict[&#34;toId&#34;] = self._nodeNumToId(asDict[&#34;to&#34;])
except Exception as ex:
logging.warn(f&#34;Not populating toId {ex}&#34;)
@@ -1040,7 +1057,7 @@ class BLEInterface(MeshInterface):
self.device.char_write(TORADIO_UUID, b)
def close(self):
MeshInterface.close(self)
MeshInterface.close(self)
self.adapter.stop()
def _readFromRadio(self):
@@ -1051,6 +1068,7 @@ class BLEInterface(MeshInterface):
if not wasEmpty:
self._handleFromRadio(b)
class StreamInterface(MeshInterface):
&#34;&#34;&#34;Interface class for meshtastic devices over a stream link (serial, TCP, etc)&#34;&#34;&#34;
@@ -1109,7 +1127,7 @@ class StreamInterface(MeshInterface):
def _writeBytes(self, b):
&#34;&#34;&#34;Write an array of bytes to our stream and flush&#34;&#34;&#34;
if self.stream: # ignore writes when stream is closed
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
@@ -1129,7 +1147,7 @@ class StreamInterface(MeshInterface):
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing stream&#34;)
MeshInterface.close(self)
MeshInterface.close(self)
# pyserial cancel_read doesn&#39;t seem to work, therefore we ask the reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
@@ -1268,7 +1286,7 @@ class TCPInterface(StreamInterface):
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing TCP stream&#34;)
StreamInterface.close(self)
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread. Therefore we force the shutdown by closing
# the socket here
self._wantExit = True
@@ -1427,7 +1445,7 @@ protocols = {
&#34;&#34;&#34;Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string&#34;&#34;&#34;
if len(psk) == 0:
return &#34;unencrypted&#34;
elif len(psk) == 1:
elif len(psk) == 1:
b = psk[0]
if b == 0:
return &#34;unencrypted&#34;
@@ -1502,7 +1520,7 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
self.device.char_write(TORADIO_UUID, b)
def close(self):
MeshInterface.close(self)
MeshInterface.close(self)
self.adapter.stop()
def _readFromRadio(self):
@@ -1643,10 +1661,11 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
if nodeId == LOCAL_ADDR:
return self.localNode
else:
logging.info(&#34;Requesting configuration from remote node (this could take a while)&#34;)
logging.info(
&#34;Requesting configuration from remote node (this could take a while)&#34;)
n = Node(self, nodeId)
n.requestConfig()
if not n.waitForConfig(maxsecs = 60):
if not n.waitForConfig(maxsecs=60):
raise Exception(&#34;Timed out waiting for node config&#34;)
return n
@@ -1773,7 +1792,8 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
nodeNum = BROADCAST_NUM
elif destinationId == LOCAL_ADDR:
nodeNum = self.myInfo.my_node_num
elif destinationId.startswith(&#34;!&#34;): # A simple hex style nodeid - we can parse this without needing the DB
# A simple hex style nodeid - we can parse this without needing the DB
elif destinationId.startswith(&#34;!&#34;):
nodeNum = int(destinationId[1:], 16)
else:
node = self.nodes.get(destinationId)
@@ -1797,7 +1817,8 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
def waitForConfig(self):
&#34;&#34;&#34;Block until radio config is received. Returns True if config has been received.&#34;&#34;&#34;
success = waitForSet(self, attrs=(&#39;myInfo&#39;, &#39;nodes&#39;)) and self.localNode.waitForConfig()
success = waitForSet(self, attrs=(&#39;myInfo&#39;, &#39;nodes&#39;)
) and self.localNode.waitForConfig()
if not success:
raise Exception(&#34;Timed out waiting for interface config&#34;)
@@ -1867,7 +1888,7 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
startConfig = mesh_pb2.ToRadio()
self.configId = random.randint(0, 0xffffffff)
startConfig.want_config_id = self.configId
startConfig.want_config_id = self.configId
self._sendToRadio(startConfig)
def _sendDisconnect(self):
@@ -1936,7 +1957,7 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
if &#34;user&#34; in node: # Some nodes might not have user/ids assigned yet
self.nodes[node[&#34;user&#34;][&#34;id&#34;]] = node
publishingThread.queueWork(lambda: pub.sendMessage(&#34;meshtastic.node.updated&#34;,
node=node, interface=self))
node=node, interface=self))
elif fromRadio.config_complete_id == self.configId:
# we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id
logging.debug(f&#34;Config complete ID {self.configId}&#34;)
@@ -2023,7 +2044,7 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
asDict[&#34;fromId&#34;] = self._nodeNumToId(asDict[&#34;from&#34;])
except Exception as ex:
logging.warn(f&#34;Not populating fromId {ex}&#34;)
try:
try:
asDict[&#34;toId&#34;] = self._nodeNumToId(asDict[&#34;to&#34;])
except Exception as ex:
logging.warn(f&#34;Not populating toId {ex}&#34;)
@@ -2168,10 +2189,11 @@ noProto &ndash; If True, don't try to run our protocol on the link - just be a d
if nodeId == LOCAL_ADDR:
return self.localNode
else:
logging.info(&#34;Requesting configuration from remote node (this could take a while)&#34;)
logging.info(
&#34;Requesting configuration from remote node (this could take a while)&#34;)
n = Node(self, nodeId)
n.requestConfig()
if not n.waitForConfig(maxsecs = 60):
if not n.waitForConfig(maxsecs=60):
raise Exception(&#34;Timed out waiting for node config&#34;)
return n</code></pre>
</details>
@@ -2366,7 +2388,8 @@ wantResponse &ndash; True if you want the service on the other side to send an a
</summary>
<pre><code class="python">def waitForConfig(self):
&#34;&#34;&#34;Block until radio config is received. Returns True if config has been received.&#34;&#34;&#34;
success = waitForSet(self, attrs=(&#39;myInfo&#39;, &#39;nodes&#39;)) and self.localNode.waitForConfig()
success = waitForSet(self, attrs=(&#39;myInfo&#39;, &#39;nodes&#39;)
) and self.localNode.waitForConfig()
if not success:
raise Exception(&#34;Timed out waiting for interface config&#34;)</code></pre>
</details>
@@ -2404,19 +2427,20 @@ wantResponse &ndash; True if you want the service on the other side to send an a
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
cStr = stripnl(MessageToJson(c.settings))
print(f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll = False)
adminURL = self.getURL(includeAll = True)
print(
f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f&#34;\nPrimary channel URL: {publicURL}&#34;)
if adminURL != publicURL:
print(f&#34;Complete URL (includes all channels): {adminURL}&#34;)
def showInfo(self):
&#34;&#34;&#34;Show human readable description of our node&#34;&#34;&#34;
print(f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
print(
f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
self.showChannels()
def requestConfig(self):
&#34;&#34;&#34;
Send regular MeshPackets to ask for settings and channels
@@ -2442,7 +2466,7 @@ wantResponse &ndash; True if you want the service on the other side to send an a
self._sendAdmin(p)
logging.debug(&#34;Wrote config&#34;)
def writeChannel(self, channelIndex, adminIndex = 0):
def writeChannel(self, channelIndex, adminIndex=0):
&#34;&#34;&#34;Write the current (edited) channel to the device&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
@@ -2462,7 +2486,7 @@ wantResponse &ndash; True if you want the service on the other side to send an a
adminIndex = self.iface.localNode._getAdminChannelIndex()
self.channels.pop(channelIndex)
self._fixupChannels() # expand back to 8 channels
self._fixupChannels() # expand back to 8 channels
index = channelIndex
while index &lt; self.iface.myInfo.max_channels:
@@ -2471,7 +2495,8 @@ wantResponse &ndash; True if you want the service on the other side to send an a
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index &gt;= adminIndex:
adminIndex = 0 # We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
# We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
adminIndex = 0
def getChannelByName(self, name):
&#34;&#34;&#34;Try to find the named channel or return None&#34;&#34;&#34;
@@ -2579,7 +2604,7 @@ wantResponse &ndash; True if you want the service on the other side to send an a
&#34;&#34;&#34;A closure to handle the response packet&#34;&#34;&#34;
self.radioConfig = p[&#34;decoded&#34;][&#34;admin&#34;][&#34;raw&#34;].get_radio_response
logging.debug(&#34;Received radio config, now fetching channels...&#34;)
self._requestChannel(0) # now start fetching channels
self._requestChannel(0) # now start fetching channels
return self._sendAdmin(p,
wantResponse=True,
@@ -2592,14 +2617,24 @@ wantResponse &ndash; True if you want the service on the other side to send an a
p = admin_pb2.AdminMessage()
p.exit_simulator = True
return self._sendAdmin(p)
return self._sendAdmin(p)
def reboot(self, secs: int = 10):
&#34;&#34;&#34;
Tell the node to reboot
&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f&#34;Telling node to reboot in {secs} seconds&#34;)
return self._sendAdmin(p)
def _fixupChannels(self):
&#34;&#34;&#34;Fixup indexes and add disabled channels as needed&#34;&#34;&#34;
# Add extra disabled channels as needed
for index, ch in enumerate(self.channels):
ch.index = index # fixup indexes
ch.index = index # fixup indexes
self._fillChannels()
@@ -2653,11 +2688,11 @@ wantResponse &ndash; True if you want the service on the other side to send an a
onResponse=onResponse)
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False,
onResponse=None,
onResponse=None,
adminIndex=0):
&#34;&#34;&#34;Send an admin message to the specified node (or the local node if destNodeNum is zero)&#34;&#34;&#34;
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex()
return self.iface.sendData(p, self.nodeNum,
@@ -2689,7 +2724,7 @@ wantResponse &ndash; True if you want the service on the other side to send an a
adminIndex = self.iface.localNode._getAdminChannelIndex()
self.channels.pop(channelIndex)
self._fixupChannels() # expand back to 8 channels
self._fixupChannels() # expand back to 8 channels
index = channelIndex
while index &lt; self.iface.myInfo.max_channels:
@@ -2698,7 +2733,8 @@ wantResponse &ndash; True if you want the service on the other side to send an a
# if we are updating the local node, we might end up *moving* the admin channel index as we are writing
if (self.iface.localNode == self) and index &gt;= adminIndex:
adminIndex = 0 # We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again</code></pre>
# We&#39;ve now passed the old location for admin index (and writen it), so we can start finding it by name again
adminIndex = 0</code></pre>
</details>
</dd>
<dt id="meshtastic.Node.exitSimulator"><code class="name flex">
@@ -2717,7 +2753,7 @@ wantResponse &ndash; True if you want the service on the other side to send an a
p = admin_pb2.AdminMessage()
p.exit_simulator = True
return self._sendAdmin(p) </code></pre>
return self._sendAdmin(p)</code></pre>
</details>
</dd>
<dt id="meshtastic.Node.getChannelByName"><code class="name flex">
@@ -2776,6 +2812,26 @@ wantResponse &ndash; True if you want the service on the other side to send an a
return f&#34;https://www.meshtastic.org/d/#{s}&#34;.replace(&#34;=&#34;, &#34;&#34;)</code></pre>
</details>
</dd>
<dt id="meshtastic.Node.reboot"><code class="name flex">
<span>def <span class="ident">reboot</span></span>(<span>self, secs: int = 10)</span>
</code></dt>
<dd>
<div class="desc"><p>Tell the node to reboot</p></div>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def reboot(self, secs: int = 10):
&#34;&#34;&#34;
Tell the node to reboot
&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f&#34;Telling node to reboot in {secs} seconds&#34;)
return self._sendAdmin(p)</code></pre>
</details>
</dd>
<dt id="meshtastic.Node.requestConfig"><code class="name flex">
<span>def <span class="ident">requestConfig</span></span>(<span>self)</span>
</code></dt>
@@ -2892,9 +2948,10 @@ wantResponse &ndash; True if you want the service on the other side to send an a
for c in self.channels:
if c.role != channel_pb2.Channel.Role.DISABLED:
cStr = stripnl(MessageToJson(c.settings))
print(f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll = False)
adminURL = self.getURL(includeAll = True)
print(
f&#34; {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}&#34;)
publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True)
print(f&#34;\nPrimary channel URL: {publicURL}&#34;)
if adminURL != publicURL:
print(f&#34;Complete URL (includes all channels): {adminURL}&#34;)</code></pre>
@@ -2911,7 +2968,8 @@ wantResponse &ndash; True if you want the service on the other side to send an a
</summary>
<pre><code class="python">def showInfo(self):
&#34;&#34;&#34;Show human readable description of our node&#34;&#34;&#34;
print(f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
print(
f&#34;Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n&#34;)
self.showChannels()</code></pre>
</details>
</dd>
@@ -2938,7 +2996,7 @@ wantResponse &ndash; True if you want the service on the other side to send an a
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def writeChannel(self, channelIndex, adminIndex = 0):
<pre><code class="python">def writeChannel(self, channelIndex, adminIndex=0):
&#34;&#34;&#34;Write the current (edited) channel to the device&#34;&#34;&#34;
p = admin_pb2.AdminMessage()
@@ -3155,7 +3213,7 @@ debugOut {stream} &ndash; If a stream is provided, any debug serial output from
def _writeBytes(self, b):
&#34;&#34;&#34;Write an array of bytes to our stream and flush&#34;&#34;&#34;
if self.stream: # ignore writes when stream is closed
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
self.stream.flush()
@@ -3175,7 +3233,7 @@ debugOut {stream} &ndash; If a stream is provided, any debug serial output from
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing stream&#34;)
MeshInterface.close(self)
MeshInterface.close(self)
# pyserial cancel_read doesn&#39;t seem to work, therefore we ask the reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
@@ -3267,7 +3325,7 @@ debugOut {stream} &ndash; If a stream is provided, any debug serial output from
<pre><code class="python">def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing stream&#34;)
MeshInterface.close(self)
MeshInterface.close(self)
# pyserial cancel_read doesn&#39;t seem to work, therefore we ask the reader thread to close things for us
self._wantExit = True
if self._rxThread != threading.current_thread():
@@ -3355,7 +3413,7 @@ hostname {string} &ndash; Hostname/IP address of the device to connect to</p></d
def close(self):
&#34;&#34;&#34;Close a connection to the device&#34;&#34;&#34;
logging.debug(&#34;Closing TCP stream&#34;)
StreamInterface.close(self)
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread. Therefore we force the shutdown by closing
# the socket here
self._wantExit = True
@@ -3473,6 +3531,7 @@ hostname {string} &ndash; Hostname/IP address of the device to connect to</p></d
<li><code><a title="meshtastic.Node.getChannelByName" href="#meshtastic.Node.getChannelByName">getChannelByName</a></code></li>
<li><code><a title="meshtastic.Node.getDisabledChannel" href="#meshtastic.Node.getDisabledChannel">getDisabledChannel</a></code></li>
<li><code><a title="meshtastic.Node.getURL" href="#meshtastic.Node.getURL">getURL</a></code></li>
<li><code><a title="meshtastic.Node.reboot" href="#meshtastic.Node.reboot">reboot</a></code></li>
<li><code><a title="meshtastic.Node.requestConfig" href="#meshtastic.Node.requestConfig">requestConfig</a></code></li>
<li><code><a title="meshtastic.Node.setOwner" href="#meshtastic.Node.setOwner">setOwner</a></code></li>
<li><code><a title="meshtastic.Node.setURL" href="#meshtastic.Node.setURL">setURL</a></code></li>

View File

File diff suppressed because one or more lines are too long

View File

@@ -51,7 +51,7 @@ sendingInterface = None
def onReceive(packet, interface):
&#34;&#34;&#34;Callback invoked when a packet arrives&#34;&#34;&#34;
if sendingInterface == interface:
pass
pass
# print(&#34;Ignoring sending interface&#34;)
else:
# print(f&#34;From {interface.stream.port}: {packet}&#34;)
@@ -93,7 +93,8 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
else:
toNode = toInterface.myInfo.my_node_num
logging.debug(f&#34;Sending test wantAck={wantAck} packet from {fromNode} to {toNode}&#34;)
logging.debug(
f&#34;Sending test wantAck={wantAck} packet from {fromNode} to {toNode}&#34;)
global sendingInterface
sendingInterface = fromInterface
if not asBinary:
@@ -128,7 +129,7 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
logging.info(
f&#34;Test succeeded {numSuccess} successes {numFail} failures so far&#34;)
#if numFail &gt;= 3:
# if numFail &gt;= 3:
# for i in interfaces:
# i.close()
# return
@@ -140,10 +141,12 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
return numFail
def testThread(numTests=50):
logging.info(&#34;Found devices, starting tests...&#34;)
runTests(numTests, wantAck=True)
runTests(numTests, wantAck=False, maxFailures=5) # Allow a few dropped packets
# Allow a few dropped packets
runTests(numTests, wantAck=False, maxFailures=5)
def onConnection(topic=pub.AUTO_TOPIC):
@@ -180,6 +183,7 @@ def testAll():
for i in interfaces:
i.close()
def testSimulator():
&#34;&#34;&#34;
Assume that someone has launched meshtastic-native as a simulated node.
@@ -254,7 +258,7 @@ def testSimulator():
<pre><code class="python">def onReceive(packet, interface):
&#34;&#34;&#34;Callback invoked when a packet arrives&#34;&#34;&#34;
if sendingInterface == interface:
pass
pass
# print(&#34;Ignoring sending interface&#34;)
else:
# print(f&#34;From {interface.stream.port}: {packet}&#34;)
@@ -309,7 +313,7 @@ def testSimulator():
logging.info(
f&#34;Test succeeded {numSuccess} successes {numFail} failures so far&#34;)
#if numFail &gt;= 3:
# if numFail &gt;= 3:
# for i in interfaces:
# i.close()
# return
@@ -409,7 +413,8 @@ toInterface {[type]} &ndash; [description]</p>
else:
toNode = toInterface.myInfo.my_node_num
logging.debug(f&#34;Sending test wantAck={wantAck} packet from {fromNode} to {toNode}&#34;)
logging.debug(
f&#34;Sending test wantAck={wantAck} packet from {fromNode} to {toNode}&#34;)
global sendingInterface
sendingInterface = fromInterface
if not asBinary:
@@ -469,7 +474,8 @@ python3 -c 'from meshtastic.test import testSimulator; testSimulator()'</p></div
<pre><code class="python">def testThread(numTests=50):
logging.info(&#34;Found devices, starting tests...&#34;)
runTests(numTests, wantAck=True)
runTests(numTests, wantAck=False, maxFailures=5) # Allow a few dropped packets</code></pre>
# Allow a few dropped packets
runTests(numTests, wantAck=False, maxFailures=5)</code></pre>
</details>
</dd>
</dl>

View File

@@ -30,7 +30,9 @@
import serial
import serial.tools.list_ports
from queue import Queue
import threading, sys, logging
import threading
import sys
import logging
&#34;&#34;&#34;Some devices such as a seger jlink we never want to accidentally open&#34;&#34;&#34;
blacklistVids = dict.fromkeys([0x1366])

View File

@@ -152,7 +152,7 @@ def printNodes(nodes, myId):
batt = formatFloat(node['position'].get("batteryLevel"), "{:.2f}", "%")
snr = formatFloat(node.get("snr"), "{:.2f}", " dB")
LH = getLH(node.get("lastHeard"))
timeAgo = getTimeAgo(node['position'].get("time"))
timeAgo = getTimeAgo(node.get("lastHeard"))
tableData.append({"N": 0, "User": node['user']['longName'],
"AKA": node['user']['shortName'], "ID": node['user']['id'],
"Position": lat+", "+lon+", "+alt,

View File

File diff suppressed because one or more lines are too long

2
proto

Submodule proto updated: f9c4f87581...1f56681b2b

View File

@@ -12,7 +12,7 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work
setup(
name="meshtastic",
version="1.2.15",
version="1.2.16",
description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description,
long_description_content_type="text/markdown",