From 0588952e43ac9475ab1f861908ffe8b8f6dce38c Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 3 May 2021 16:17:21 +0800 Subject: [PATCH] 1.2.32 --- docs/meshtastic/index.html | 94 ++++++++++++++++++++++++++++++-------- docs/meshtastic/test.html | 8 ++-- setup.py | 2 +- 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/docs/meshtastic/index.html b/docs/meshtastic/index.html index e44ae16..c77e70e 100644 --- a/docs/meshtastic/index.html +++ b/docs/meshtastic/index.html @@ -231,12 +231,15 @@ class MeshInterface: self.responseHandlers = {} # A map from request ID to the handler self.failure = None # If we've encountered a fatal exception it will be kept here self._timeout = Timeout() + self.heartbeatTimer = None random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it self.currentPacketId = random.randint(0, 0xffffffff) - self._startConfig() def close(self): """Shutdown this interface""" + if self.heartbeatTimer: + self.heartbeatTimer.cancel() + self._sendDisconnect() def __enter__(self): @@ -521,6 +524,22 @@ class MeshInterface: publishingThread.queueWork(lambda: pub.sendMessage( "meshtastic.connection.lost", interface=self)) + def _startHeartbeat(self): + """We need to send a heartbeat message to the device every X seconds""" + def callback(): + self.heartbeatTimer = None + prefs = self.localNode.radioConfig.preferences + i = prefs.phone_timeout_secs / 2 + logging.debug(f"Sending heartbeat, interval {i}") + if i != 0: + self.heartbeatTimer = threading.Timer(i, callback) + self.heartbeatTimer.start() + p = mesh_pb2.ToRadio() + self._sendToRadio(p) + + callback() # run our periodic callback now, it will make another timer if necessary + + def _connected(self): """Called by this class to tell clients we are now fully connected to a node """ @@ -529,6 +548,7 @@ class MeshInterface: # for the local interface if not self.isConnected.is_set(): self.isConnected.set() + self._startHeartbeat() publishingThread.queueWork(lambda: pub.sendMessage( "meshtastic.connection.established", interface=self)) @@ -576,7 +596,7 @@ class MeshInterface: fromRadio = mesh_pb2.FromRadio() fromRadio.ParseFromString(fromRadioBytes) asDict = google.protobuf.json_format.MessageToDict(fromRadio) - # logging.debug(f"Received from radio: {fromRadio}") + #logging.debug(f"Received from radio: {fromRadio}") if fromRadio.HasField("my_info"): self.myInfo = fromRadio.my_info self.localNode.nodeNum = self.myInfo.my_node_num @@ -788,7 +808,7 @@ class BLEInterface(MeshInterface): def _sendToRadioImpl(self, toRadio): """Send a ToRadio protobuf to the device""" - # logging.debug(f"Sending: {stripnl(toRadio)}") + #logging.debug(f"Sending: {stripnl(toRadio)}") b = toRadio.SerializeToString() self.device.char_write(TORADIO_UUID, b) @@ -844,11 +864,16 @@ class StreamInterface(MeshInterface): start the reading thread later. """ - # Send some bogus UART characters to force a sleeping device to wake - self._writeBytes(bytes([START1, START1, START1, START1])) + # Send some bogus UART characters to force a sleeping device to wake, and if the reading statemachine was parsing a bad packet make sure + # we write enought start bytes to force it to resync + p = bytearray([START1] * 32) + self._writeBytes(p) time.sleep(0.1) # wait 100ms to give device time to start running self._rxThread.start() + + self._startConfig() + if not self.noProto: # Wait for the db download if using the protocol self._waitConnected() @@ -918,15 +943,15 @@ class StreamInterface(MeshInterface): elif ptr == 1: # looking for START2 if c != START2: self._rxBuf = empty # failed to find start2 - elif ptr >= HEADER_LEN: # we've at least got a header + elif ptr >= HEADER_LEN - 1: # we've at least got a header # big endian length follos header packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3] - if ptr == HEADER_LEN: # we _just_ finished reading the header, validate length + if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length if packetlen > MAX_TO_FROM_RADIO_SIZE: self._rxBuf = empty # length ws out out bounds, restart - if len(self._rxBuf) != 0 and ptr + 1 == packetlen + HEADER_LEN: + if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN: try: self._handleFromRadio(self._rxBuf[HEADER_LEN:]) except Exception as ex: @@ -1216,7 +1241,7 @@ noProto – If True, don't try to run our protocol on the link - just be a d def _sendToRadioImpl(self, toRadio): """Send a ToRadio protobuf to the device""" - # logging.debug(f"Sending: {stripnl(toRadio)}") + #logging.debug(f"Sending: {stripnl(toRadio)}") b = toRadio.SerializeToString() self.device.char_write(TORADIO_UUID, b) @@ -1333,12 +1358,15 @@ noProto – If True, don't try to run our protocol on the link - just be a d self.responseHandlers = {} # A map from request ID to the handler self.failure = None # If we've encountered a fatal exception it will be kept here self._timeout = Timeout() + self.heartbeatTimer = None random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it self.currentPacketId = random.randint(0, 0xffffffff) - self._startConfig() def close(self): """Shutdown this interface""" + if self.heartbeatTimer: + self.heartbeatTimer.cancel() + self._sendDisconnect() def __enter__(self): @@ -1623,6 +1651,22 @@ noProto – If True, don't try to run our protocol on the link - just be a d publishingThread.queueWork(lambda: pub.sendMessage( "meshtastic.connection.lost", interface=self)) + def _startHeartbeat(self): + """We need to send a heartbeat message to the device every X seconds""" + def callback(): + self.heartbeatTimer = None + prefs = self.localNode.radioConfig.preferences + i = prefs.phone_timeout_secs / 2 + logging.debug(f"Sending heartbeat, interval {i}") + if i != 0: + self.heartbeatTimer = threading.Timer(i, callback) + self.heartbeatTimer.start() + p = mesh_pb2.ToRadio() + self._sendToRadio(p) + + callback() # run our periodic callback now, it will make another timer if necessary + + def _connected(self): """Called by this class to tell clients we are now fully connected to a node """ @@ -1631,6 +1675,7 @@ noProto – If True, don't try to run our protocol on the link - just be a d # for the local interface if not self.isConnected.is_set(): self.isConnected.set() + self._startHeartbeat() publishingThread.queueWork(lambda: pub.sendMessage( "meshtastic.connection.established", interface=self)) @@ -1678,7 +1723,7 @@ noProto – If True, don't try to run our protocol on the link - just be a d fromRadio = mesh_pb2.FromRadio() fromRadio.ParseFromString(fromRadioBytes) asDict = google.protobuf.json_format.MessageToDict(fromRadio) - # logging.debug(f"Received from radio: {fromRadio}") + #logging.debug(f"Received from radio: {fromRadio}") if fromRadio.HasField("my_info"): self.myInfo = fromRadio.my_info self.localNode.nodeNum = self.myInfo.my_node_num @@ -1879,6 +1924,9 @@ noProto – If True, don't try to run our protocol on the link - just be a d
def close(self):
     """Shutdown this interface"""
+    if self.heartbeatTimer:
+        self.heartbeatTimer.cancel()
+
     self._sendDisconnect()
@@ -2377,11 +2425,16 @@ debugOut {stream} – If a stream is provided, any debug serial output from start the reading thread later. """ - # Send some bogus UART characters to force a sleeping device to wake - self._writeBytes(bytes([START1, START1, START1, START1])) + # Send some bogus UART characters to force a sleeping device to wake, and if the reading statemachine was parsing a bad packet make sure + # we write enought start bytes to force it to resync + p = bytearray([START1] * 32) + self._writeBytes(p) time.sleep(0.1) # wait 100ms to give device time to start running self._rxThread.start() + + self._startConfig() + if not self.noProto: # Wait for the db download if using the protocol self._waitConnected() @@ -2451,15 +2504,15 @@ debugOut {stream} – If a stream is provided, any debug serial output from elif ptr == 1: # looking for START2 if c != START2: self._rxBuf = empty # failed to find start2 - elif ptr >= HEADER_LEN: # we've at least got a header + elif ptr >= HEADER_LEN - 1: # we've at least got a header # big endian length follos header packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3] - if ptr == HEADER_LEN: # we _just_ finished reading the header, validate length + if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length if packetlen > MAX_TO_FROM_RADIO_SIZE: self._rxBuf = empty # length ws out out bounds, restart - if len(self._rxBuf) != 0 and ptr + 1 == packetlen + HEADER_LEN: + if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN: try: self._handleFromRadio(self._rxBuf[HEADER_LEN:]) except Exception as ex: @@ -2533,11 +2586,16 @@ start the reading thread later.

start the reading thread later. """ - # Send some bogus UART characters to force a sleeping device to wake - self._writeBytes(bytes([START1, START1, START1, START1])) + # Send some bogus UART characters to force a sleeping device to wake, and if the reading statemachine was parsing a bad packet make sure + # we write enought start bytes to force it to resync + p = bytearray([START1] * 32) + self._writeBytes(p) time.sleep(0.1) # wait 100ms to give device time to start running self._rxThread.start() + + self._startConfig() + if not self.noProto: # Wait for the db download if using the protocol self._waitConnected() diff --git a/docs/meshtastic/test.html b/docs/meshtastic/test.html index ce9a9c1..daf768e 100644 --- a/docs/meshtastic/test.html +++ b/docs/meshtastic/test.html @@ -124,11 +124,11 @@ def runTests(numTests=50, wantAck=False, maxFailures=0): if not success: numFail = numFail + 1 logging.error( - f"Test failed, expected packet not received ({numFail} failures so far)") + f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)") else: numSuccess = numSuccess + 1 logging.info( - f"Test succeeded {numSuccess} successes {numFail} failures so far") + f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far") # if numFail >= 3: # for i in interfaces: @@ -314,11 +314,11 @@ def testSimulator(): if not success: numFail = numFail + 1 logging.error( - f"Test failed, expected packet not received ({numFail} failures so far)") + f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)") else: numSuccess = numSuccess + 1 logging.info( - f"Test succeeded {numSuccess} successes {numFail} failures so far") + f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far") # if numFail >= 3: # for i in interfaces: diff --git a/setup.py b/setup.py index b5ec913..bdd261d 100644 --- a/setup.py +++ b/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.31", + version="1.2.32", description="Python API & client shell for talking to Meshtastic devices", long_description=long_description, long_description_content_type="text/markdown",