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",