mirror of
https://github.com/meshtastic/python.git
synced 2025-12-26 17:37:51 -05:00
1.2.16
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -184,6 +184,7 @@ OUR_APP_VERSION = 20200
|
||||
|
||||
publishingThread = DeferredExecution("publishing")
|
||||
|
||||
|
||||
class ResponseHandler(NamedTuple):
|
||||
"""A pending response callback, waiting for a response to one of our messages"""
|
||||
# requestId: int - used only as a key
|
||||
@@ -214,7 +215,7 @@ def pskToString(psk: bytes):
|
||||
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
|
||||
if len(psk) == 0:
|
||||
return "unencrypted"
|
||||
elif len(psk) == 1:
|
||||
elif len(psk) == 1:
|
||||
b = psk[0]
|
||||
if b == 0:
|
||||
return "unencrypted"
|
||||
@@ -225,6 +226,7 @@ def pskToString(psk: bytes):
|
||||
else:
|
||||
return "secret"
|
||||
|
||||
|
||||
class Node:
|
||||
"""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" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll = False)
|
||||
adminURL = self.getURL(includeAll = True)
|
||||
print(
|
||||
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll=False)
|
||||
adminURL = self.getURL(includeAll=True)
|
||||
print(f"\nPrimary channel URL: {publicURL}")
|
||||
if adminURL != publicURL:
|
||||
print(f"Complete URL (includes all channels): {adminURL}")
|
||||
|
||||
def showInfo(self):
|
||||
"""Show human readable description of our node"""
|
||||
print(f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
print(
|
||||
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
self.showChannels()
|
||||
|
||||
|
||||
def requestConfig(self):
|
||||
"""
|
||||
Send regular MeshPackets to ask for settings and channels
|
||||
@@ -282,7 +285,7 @@ class Node:
|
||||
self._sendAdmin(p)
|
||||
logging.debug("Wrote config")
|
||||
|
||||
def writeChannel(self, channelIndex, adminIndex = 0):
|
||||
def writeChannel(self, channelIndex, adminIndex=0):
|
||||
"""Write the current (edited) channel to the device"""
|
||||
|
||||
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 < 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 >= adminIndex:
|
||||
adminIndex = 0 # We've now passed the old location for admin index (and writen it), so we can start finding it by name again
|
||||
# We'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):
|
||||
"""Try to find the named channel or return None"""
|
||||
@@ -419,7 +423,7 @@ class Node:
|
||||
"""A closure to handle the response packet"""
|
||||
self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
|
||||
logging.debug("Received radio config, now fetching channels...")
|
||||
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):
|
||||
"""
|
||||
Tell the node to reboot
|
||||
"""
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_seconds = secs
|
||||
logging.info(f"Telling node to reboot in {secs} seconds")
|
||||
|
||||
return self._sendAdmin(p)
|
||||
|
||||
def _fixupChannels(self):
|
||||
"""Fixup indexes and add disabled channels as needed"""
|
||||
|
||||
# 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):
|
||||
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
||||
|
||||
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("Requesting configuration from remote node (this could take a while)")
|
||||
logging.info(
|
||||
"Requesting configuration from remote node (this could take a while)")
|
||||
n = Node(self, nodeId)
|
||||
n.requestConfig()
|
||||
if not n.waitForConfig(maxsecs = 60):
|
||||
if not n.waitForConfig(maxsecs=60):
|
||||
raise Exception("Timed out waiting for node config")
|
||||
return n
|
||||
|
||||
@@ -693,7 +708,8 @@ class MeshInterface:
|
||||
nodeNum = BROADCAST_NUM
|
||||
elif destinationId == LOCAL_ADDR:
|
||||
nodeNum = self.myInfo.my_node_num
|
||||
elif destinationId.startswith("!"): # 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("!"):
|
||||
nodeNum = int(destinationId[1:], 16)
|
||||
else:
|
||||
node = self.nodes.get(destinationId)
|
||||
@@ -717,7 +733,8 @@ class MeshInterface:
|
||||
|
||||
def waitForConfig(self):
|
||||
"""Block until radio config is received. Returns True if config has been received."""
|
||||
success = waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
|
||||
success = waitForSet(self, attrs=('myInfo', 'nodes')
|
||||
) and self.localNode.waitForConfig()
|
||||
if not success:
|
||||
raise Exception("Timed out waiting for interface config")
|
||||
|
||||
@@ -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 "user" in node: # Some nodes might not have user/ids assigned yet
|
||||
self.nodes[node["user"]["id"]] = node
|
||||
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
|
||||
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"Config complete ID {self.configId}")
|
||||
@@ -943,7 +960,7 @@ class MeshInterface:
|
||||
asDict["fromId"] = self._nodeNumToId(asDict["from"])
|
||||
except Exception as ex:
|
||||
logging.warn(f"Not populating fromId {ex}")
|
||||
try:
|
||||
try:
|
||||
asDict["toId"] = self._nodeNumToId(asDict["to"])
|
||||
except Exception as ex:
|
||||
logging.warn(f"Not populating toId {ex}")
|
||||
@@ -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):
|
||||
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
|
||||
|
||||
@@ -1109,7 +1127,7 @@ class StreamInterface(MeshInterface):
|
||||
|
||||
def _writeBytes(self, b):
|
||||
"""Write an array of bytes to our stream and flush"""
|
||||
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):
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing stream")
|
||||
MeshInterface.close(self)
|
||||
MeshInterface.close(self)
|
||||
# pyserial cancel_read doesn'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):
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing TCP stream")
|
||||
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 = {
|
||||
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
|
||||
if len(psk) == 0:
|
||||
return "unencrypted"
|
||||
elif len(psk) == 1:
|
||||
elif len(psk) == 1:
|
||||
b = psk[0]
|
||||
if b == 0:
|
||||
return "unencrypted"
|
||||
@@ -1502,7 +1520,7 @@ noProto – 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 – 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("Requesting configuration from remote node (this could take a while)")
|
||||
logging.info(
|
||||
"Requesting configuration from remote node (this could take a while)")
|
||||
n = Node(self, nodeId)
|
||||
n.requestConfig()
|
||||
if not n.waitForConfig(maxsecs = 60):
|
||||
if not n.waitForConfig(maxsecs=60):
|
||||
raise Exception("Timed out waiting for node config")
|
||||
return n
|
||||
|
||||
@@ -1773,7 +1792,8 @@ noProto – 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("!"): # 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("!"):
|
||||
nodeNum = int(destinationId[1:], 16)
|
||||
else:
|
||||
node = self.nodes.get(destinationId)
|
||||
@@ -1797,7 +1817,8 @@ noProto – If True, don't try to run our protocol on the link - just be a d
|
||||
|
||||
def waitForConfig(self):
|
||||
"""Block until radio config is received. Returns True if config has been received."""
|
||||
success = waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
|
||||
success = waitForSet(self, attrs=('myInfo', 'nodes')
|
||||
) and self.localNode.waitForConfig()
|
||||
if not success:
|
||||
raise Exception("Timed out waiting for interface config")
|
||||
|
||||
@@ -1867,7 +1888,7 @@ noProto – 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 – If True, don't try to run our protocol on the link - just be a d
|
||||
if "user" in node: # Some nodes might not have user/ids assigned yet
|
||||
self.nodes[node["user"]["id"]] = node
|
||||
publishingThread.queueWork(lambda: pub.sendMessage("meshtastic.node.updated",
|
||||
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"Config complete ID {self.configId}")
|
||||
@@ -2023,7 +2044,7 @@ noProto – If True, don't try to run our protocol on the link - just be a d
|
||||
asDict["fromId"] = self._nodeNumToId(asDict["from"])
|
||||
except Exception as ex:
|
||||
logging.warn(f"Not populating fromId {ex}")
|
||||
try:
|
||||
try:
|
||||
asDict["toId"] = self._nodeNumToId(asDict["to"])
|
||||
except Exception as ex:
|
||||
logging.warn(f"Not populating toId {ex}")
|
||||
@@ -2168,10 +2189,11 @@ noProto – 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("Requesting configuration from remote node (this could take a while)")
|
||||
logging.info(
|
||||
"Requesting configuration from remote node (this could take a while)")
|
||||
n = Node(self, nodeId)
|
||||
n.requestConfig()
|
||||
if not n.waitForConfig(maxsecs = 60):
|
||||
if not n.waitForConfig(maxsecs=60):
|
||||
raise Exception("Timed out waiting for node config")
|
||||
return n</code></pre>
|
||||
</details>
|
||||
@@ -2366,7 +2388,8 @@ wantResponse – True if you want the service on the other side to send an a
|
||||
</summary>
|
||||
<pre><code class="python">def waitForConfig(self):
|
||||
"""Block until radio config is received. Returns True if config has been received."""
|
||||
success = waitForSet(self, attrs=('myInfo', 'nodes')) and self.localNode.waitForConfig()
|
||||
success = waitForSet(self, attrs=('myInfo', 'nodes')
|
||||
) and self.localNode.waitForConfig()
|
||||
if not success:
|
||||
raise Exception("Timed out waiting for interface config")</code></pre>
|
||||
</details>
|
||||
@@ -2404,19 +2427,20 @@ wantResponse – 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" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll = False)
|
||||
adminURL = self.getURL(includeAll = True)
|
||||
print(
|
||||
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll=False)
|
||||
adminURL = self.getURL(includeAll=True)
|
||||
print(f"\nPrimary channel URL: {publicURL}")
|
||||
if adminURL != publicURL:
|
||||
print(f"Complete URL (includes all channels): {adminURL}")
|
||||
|
||||
def showInfo(self):
|
||||
"""Show human readable description of our node"""
|
||||
print(f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
print(
|
||||
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
self.showChannels()
|
||||
|
||||
|
||||
def requestConfig(self):
|
||||
"""
|
||||
Send regular MeshPackets to ask for settings and channels
|
||||
@@ -2442,7 +2466,7 @@ wantResponse – True if you want the service on the other side to send an a
|
||||
self._sendAdmin(p)
|
||||
logging.debug("Wrote config")
|
||||
|
||||
def writeChannel(self, channelIndex, adminIndex = 0):
|
||||
def writeChannel(self, channelIndex, adminIndex=0):
|
||||
"""Write the current (edited) channel to the device"""
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
@@ -2462,7 +2486,7 @@ wantResponse – 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 < self.iface.myInfo.max_channels:
|
||||
@@ -2471,7 +2495,8 @@ wantResponse – 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 >= adminIndex:
|
||||
adminIndex = 0 # We've now passed the old location for admin index (and writen it), so we can start finding it by name again
|
||||
# We'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):
|
||||
"""Try to find the named channel or return None"""
|
||||
@@ -2579,7 +2604,7 @@ wantResponse – True if you want the service on the other side to send an a
|
||||
"""A closure to handle the response packet"""
|
||||
self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response
|
||||
logging.debug("Received radio config, now fetching channels...")
|
||||
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 – 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):
|
||||
"""
|
||||
Tell the node to reboot
|
||||
"""
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_seconds = secs
|
||||
logging.info(f"Telling node to reboot in {secs} seconds")
|
||||
|
||||
return self._sendAdmin(p)
|
||||
|
||||
def _fixupChannels(self):
|
||||
"""Fixup indexes and add disabled channels as needed"""
|
||||
|
||||
# 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 – 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):
|
||||
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
||||
|
||||
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 – 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 < self.iface.myInfo.max_channels:
|
||||
@@ -2698,7 +2733,8 @@ wantResponse – 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 >= adminIndex:
|
||||
adminIndex = 0 # We've now passed the old location for admin index (and writen it), so we can start finding it by name again</code></pre>
|
||||
# We'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 – 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 – True if you want the service on the other side to send an a
|
||||
return f"https://www.meshtastic.org/d/#{s}".replace("=", "")</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):
|
||||
"""
|
||||
Tell the node to reboot
|
||||
"""
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_seconds = secs
|
||||
logging.info(f"Telling node to reboot in {secs} seconds")
|
||||
|
||||
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 – 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" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll = False)
|
||||
adminURL = self.getURL(includeAll = True)
|
||||
print(
|
||||
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}")
|
||||
publicURL = self.getURL(includeAll=False)
|
||||
adminURL = self.getURL(includeAll=True)
|
||||
print(f"\nPrimary channel URL: {publicURL}")
|
||||
if adminURL != publicURL:
|
||||
print(f"Complete URL (includes all channels): {adminURL}")</code></pre>
|
||||
@@ -2911,7 +2968,8 @@ wantResponse – True if you want the service on the other side to send an a
|
||||
</summary>
|
||||
<pre><code class="python">def showInfo(self):
|
||||
"""Show human readable description of our node"""
|
||||
print(f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
print(
|
||||
f"Preferences: {stripnl(MessageToJson(self.radioConfig.preferences))}\n")
|
||||
self.showChannels()</code></pre>
|
||||
</details>
|
||||
</dd>
|
||||
@@ -2938,7 +2996,7 @@ wantResponse – 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):
|
||||
"""Write the current (edited) channel to the device"""
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
@@ -3155,7 +3213,7 @@ debugOut {stream} – If a stream is provided, any debug serial output from
|
||||
|
||||
def _writeBytes(self, b):
|
||||
"""Write an array of bytes to our stream and flush"""
|
||||
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} – If a stream is provided, any debug serial output from
|
||||
def close(self):
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing stream")
|
||||
MeshInterface.close(self)
|
||||
MeshInterface.close(self)
|
||||
# pyserial cancel_read doesn'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} – If a stream is provided, any debug serial output from
|
||||
<pre><code class="python">def close(self):
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing stream")
|
||||
MeshInterface.close(self)
|
||||
MeshInterface.close(self)
|
||||
# pyserial cancel_read doesn'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} – Hostname/IP address of the device to connect to</p></d
|
||||
def close(self):
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing TCP stream")
|
||||
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} – 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>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -51,7 +51,7 @@ sendingInterface = None
|
||||
def onReceive(packet, interface):
|
||||
"""Callback invoked when a packet arrives"""
|
||||
if sendingInterface == interface:
|
||||
pass
|
||||
pass
|
||||
# print("Ignoring sending interface")
|
||||
else:
|
||||
# print(f"From {interface.stream.port}: {packet}")
|
||||
@@ -93,7 +93,8 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
|
||||
else:
|
||||
toNode = toInterface.myInfo.my_node_num
|
||||
|
||||
logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
logging.debug(
|
||||
f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
global sendingInterface
|
||||
sendingInterface = fromInterface
|
||||
if not asBinary:
|
||||
@@ -128,7 +129,7 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
|
||||
logging.info(
|
||||
f"Test succeeded {numSuccess} successes {numFail} failures so far")
|
||||
|
||||
#if numFail >= 3:
|
||||
# if numFail >= 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("Found devices, starting tests...")
|
||||
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():
|
||||
"""
|
||||
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):
|
||||
"""Callback invoked when a packet arrives"""
|
||||
if sendingInterface == interface:
|
||||
pass
|
||||
pass
|
||||
# print("Ignoring sending interface")
|
||||
else:
|
||||
# print(f"From {interface.stream.port}: {packet}")
|
||||
@@ -309,7 +313,7 @@ def testSimulator():
|
||||
logging.info(
|
||||
f"Test succeeded {numSuccess} successes {numFail} failures so far")
|
||||
|
||||
#if numFail >= 3:
|
||||
# if numFail >= 3:
|
||||
# for i in interfaces:
|
||||
# i.close()
|
||||
# return
|
||||
@@ -409,7 +413,8 @@ toInterface {[type]} – [description]</p>
|
||||
else:
|
||||
toNode = toInterface.myInfo.my_node_num
|
||||
|
||||
logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
logging.debug(
|
||||
f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
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("Found devices, starting tests...")
|
||||
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>
|
||||
|
||||
@@ -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
|
||||
|
||||
"""Some devices such as a seger jlink we never want to accidentally open"""
|
||||
blacklistVids = dict.fromkeys([0x1366])
|
||||
|
||||
@@ -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,
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
proto
2
proto
Submodule proto updated: f9c4f87581...1f56681b2b
2
setup.py
2
setup.py
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user