From c70d36d2cd81411bb284bd0193f57ae2fb5165da Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 19:24:26 -0800 Subject: [PATCH 01/10] revamp the serial connection to avoid Tbeam reboots --- .pylintrc | 2 +- meshtastic/serial_interface.py | 66 ++++++++++++++-------------------- meshtastic/stream_interface.py | 6 +++- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/.pylintrc b/.pylintrc index c7e6475..5deb236 100644 --- a/.pylintrc +++ b/.pylintrc @@ -23,7 +23,7 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admi # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" # -disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,consider-using-f-string,broad-except,no-else-return,unused-argument,global-statement,global-variable-not-assigned,too-many-boolean-expressions,no-else-raise,bare-except +disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,no-self-use,pointless-string-statement,too-few-public-methods,consider-using-f-string,broad-except,no-else-return,unused-argument,global-statement,global-variable-not-assigned,too-many-boolean-expressions,no-else-raise,bare-except,c-extension-no-member [BASIC] diff --git a/meshtastic/serial_interface.py b/meshtastic/serial_interface.py index 90b6922..464ba82 100644 --- a/meshtastic/serial_interface.py +++ b/meshtastic/serial_interface.py @@ -1,9 +1,8 @@ """ Serial interface class """ import logging -import platform -import os -import stat +import time +import termios import serial import meshtastic.util @@ -20,6 +19,7 @@ class SerialInterface(StreamInterface): devPath {string} -- A filepath to a device, i.e. /dev/ttyUSB0 (default: {None}) debugOut {stream} -- If a stream is provided, any debug serial output from the device will be emitted to that stream. (default: {None}) """ + self.noProto = noProto if devPath is None: ports = meshtastic.util.findPorts() @@ -35,43 +35,29 @@ class SerialInterface(StreamInterface): logging.debug(f"Connecting to {devPath}") - # Note: we provide None for port here, because we will be opening it later - self.stream = serial.Serial( - None, 921600, exclusive=True, timeout=0.5, write_timeout=0) + # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR + # see https://github.com/pyserial/pyserial/issues/124 + if not self.noProto: + with open(devPath, encoding='utf8') as f: + attrs = termios.tcgetattr(f) + attrs[2] = attrs[2] & ~termios.HUPCL + termios.tcsetattr(f, termios.TCSAFLUSH, attrs) + f.close() + time.sleep(0.1) - # rts=False Needed to prevent TBEAMs resetting on OSX, because rts is connected to reset - self.stream.port = devPath + self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5) + if not self.noProto: + self.stream.flush() + time.sleep(0.1) - # HACK: If the platform driving the serial port is unable to leave the RTS pin in high-impedance - # mode, set RTS to false so that the device platform won't be reset spuriously. - # Linux does this properly, so don't apply this hack on Linux (because it makes the reset button not work). - if self._hostPlatformAlwaysDrivesUartRts(): - self.stream.rts = False - self.stream.open() + StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) - StreamInterface.__init__( - self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) - - """true if platform driving the serial port is Windows Subsystem for Linux 1.""" - def _isWsl1(self): - # WSL1 identifies itself as Linux, but has a special char device at /dev/lxss for use with session control, - # e.g. /init. We should treat WSL1 as Windows for the RTS-driving hack because the underlying platfrom - # serial driver for the CP21xx still exhibits the buggy behavior. - # WSL2 is not covered here, as it does not (as of 2021-May-25) support the appropriate functionality to - # share or pass-through serial ports. - try: - # Claims to be Linux, but has /dev/lxss; must be WSL 1 - return platform.system() == 'Linux' and stat.S_ISCHR(os.stat('/dev/lxss').st_mode) - except: - # Couldn't stat /dev/lxss special device; not WSL1 - return False - - def _hostPlatformAlwaysDrivesUartRts(self): - # OS-X/Windows seems to have a bug in its CP21xx serial drivers. It ignores that we asked for no RTSCTS - # control and will always drive RTS either high or low (rather than letting the CP102 leave - # it as an open-collector floating pin). - # TODO: When WSL2 supports USB passthrough, this will get messier. If/when WSL2 gets virtual serial - # ports that "share" the Windows serial port (and thus the Windows drivers), this code will need to be - # updated to reflect that as well -- or if T-Beams get made with an alternate USB to UART bridge that has - # a less buggy driver. - return platform.system() != 'Linux' or self._isWsl1() + def close(self): + """Close a connection to the device""" + if not self.noProto: + self.stream.flush() + time.sleep(0.1) + self.stream.flush() + time.sleep(0.1) + logging.debug("Closing Serial stream") + StreamInterface.close(self) diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index a3c20b5..8b81f9c 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -62,7 +62,8 @@ class StreamInterface(MeshInterface): # because we want to ensure it is looking for START1) p = bytearray([START2] * 32) self._writeBytes(p) - time.sleep(0.1) # wait 100ms to give device time to start running + if not self.noProto: + time.sleep(0.1) # wait 100ms to give device time to start running self._rxThread.start() @@ -88,6 +89,9 @@ class StreamInterface(MeshInterface): if self.stream: # ignore writes when stream is closed self.stream.write(b) self.stream.flush() + # we sleep here to give the TBeam a chance to work + if not self.noProto: + time.sleep(0.1) def _readBytes(self, length): """Read an array of bytes from our stream""" From c7d3f9f787648fa8f3f785db6009118cb342fcd1 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 20:35:50 -0800 Subject: [PATCH 02/10] close seriallog if we have one --- meshtastic/__main__.py | 11 +++++++++-- meshtastic/globals.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index f7cfeaa..c8e07ee 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -567,6 +567,7 @@ def export_config(interface): def common(): """Shared code for all of our command line wrappers""" + logfile = None our_globals = Globals.getInstance() args = our_globals.get_args() parser = our_globals.get_parser() @@ -621,8 +622,8 @@ def common(): logging.info(f"Logging serial output to {args.seriallog}") # Note: using "line buffering" # pylint: disable=R1732 - logfile = open(args.seriallog, 'w+', - buffering=1, encoding='utf8') + logfile = open(args.seriallog, 'w+', buffering=1, encoding='utf8') + our_globals.set_logfile(logfile) subscribe() if args.ble: @@ -636,6 +637,8 @@ def common(): # We assume client is fully connected now onConnected(client) + #if logfile: + #logfile.close() if args.noproto or (have_tunnel and args.tunnel): # loop until someone presses ctrlc while True: @@ -828,6 +831,10 @@ def main(): our_globals.set_parser(parser) initParser() common() + logfile = our_globals.get_logfile() + if logfile: + logfile.close() + def tunnelMain(): diff --git a/meshtastic/globals.py b/meshtastic/globals.py index 05049c1..bee1c2d 100644 --- a/meshtastic/globals.py +++ b/meshtastic/globals.py @@ -29,6 +29,7 @@ class Globals: self.parser = None self.target_node = None self.channel_index = None + self.logfile = None def reset(self): """Reset all of our globals. If you add a member, add it to this method, too.""" @@ -37,6 +38,7 @@ class Globals: self.target_node = None self.channel_index = None + # setters def set_args(self, args): """Set the args""" self.args = args @@ -53,6 +55,11 @@ class Globals: """Set the channel_index""" self.channel_index = channel_index + def set_logfile(self, logfile): + """Set the logfile""" + self.logfile = logfile + + # getters def get_args(self): """Get args""" return self.args @@ -68,3 +75,7 @@ class Globals: def get_channel_index(self): """Get channel_index""" return self.channel_index + + def get_logfile(self): + """Get logfile""" + return self.logfile From 5dc800f9a3f0f7d3536efbec788299ed6f223b70 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 20:39:34 -0800 Subject: [PATCH 03/10] deal with windows on smoke1 test --- meshtastic/tests/test_smoke1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index d05b765..5ad4f3f 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -140,8 +140,8 @@ def test_smoke1_nodes(): """Test --nodes""" return_value, out = subprocess.getstatusoutput('meshtastic --nodes') assert re.match(r'Connected to radio', out) - assert re.search(r'^│ N │ User', out, re.MULTILINE) - assert re.search(r'^│ 1 │', out, re.MULTILINE) + assert re.search(r'^│ N │ User', out.encode('utf-8'), re.MULTILINE) + assert re.search(r'^│ 1 │', out.encode('utf-8'), re.MULTILINE) assert return_value == 0 From 75fe7622a4ab96613b396894980559258163d6e8 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 20:49:19 -0800 Subject: [PATCH 04/10] deal with windows on the serial issue --- meshtastic/serial_interface.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/meshtastic/serial_interface.py b/meshtastic/serial_interface.py index 464ba82..0438c76 100644 --- a/meshtastic/serial_interface.py +++ b/meshtastic/serial_interface.py @@ -2,12 +2,15 @@ """ import logging import time -import termios +import platform import serial import meshtastic.util from .stream_interface import StreamInterface +if platform.system() != 'Windows': + import termios + class SerialInterface(StreamInterface): """Interface class for meshtastic devices over a serial link""" @@ -38,12 +41,13 @@ class SerialInterface(StreamInterface): # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR # see https://github.com/pyserial/pyserial/issues/124 if not self.noProto: - with open(devPath, encoding='utf8') as f: - attrs = termios.tcgetattr(f) - attrs[2] = attrs[2] & ~termios.HUPCL - termios.tcsetattr(f, termios.TCSAFLUSH, attrs) - f.close() - time.sleep(0.1) + if platform.system() != 'windows': + with open(devPath, encoding='utf8') as f: + attrs = termios.tcgetattr(f) + attrs[2] = attrs[2] & ~termios.HUPCL + termios.tcsetattr(f, termios.TCSAFLUSH, attrs) + f.close() + time.sleep(0.1) self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5) if not self.noProto: From c7d981ec350370a37f3cbe2bdac7dc06bec36faa Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 20:51:46 -0800 Subject: [PATCH 05/10] fix typo --- meshtastic/serial_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/serial_interface.py b/meshtastic/serial_interface.py index 0438c76..3d350f4 100644 --- a/meshtastic/serial_interface.py +++ b/meshtastic/serial_interface.py @@ -41,7 +41,7 @@ class SerialInterface(StreamInterface): # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR # see https://github.com/pyserial/pyserial/issues/124 if not self.noProto: - if platform.system() != 'windows': + if platform.system() != 'Windows': with open(devPath, encoding='utf8') as f: attrs = termios.tcgetattr(f) attrs[2] = attrs[2] & ~termios.HUPCL From 181c04716a89e453bf1406f4777b3a3a60fbe40a Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 20:55:31 -0800 Subject: [PATCH 06/10] accidentally dropped an arg --- meshtastic/serial_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/serial_interface.py b/meshtastic/serial_interface.py index 3d350f4..720615e 100644 --- a/meshtastic/serial_interface.py +++ b/meshtastic/serial_interface.py @@ -49,7 +49,7 @@ class SerialInterface(StreamInterface): f.close() time.sleep(0.1) - self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5) + self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0) if not self.noProto: self.stream.flush() time.sleep(0.1) From e79faf93d0da660f3861ba0cd8a27693a1be940f Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 20:57:57 -0800 Subject: [PATCH 07/10] try a different way --- meshtastic/tests/test_smoke1.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index 5ad4f3f..b435877 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -140,8 +140,9 @@ def test_smoke1_nodes(): """Test --nodes""" return_value, out = subprocess.getstatusoutput('meshtastic --nodes') assert re.match(r'Connected to radio', out) - assert re.search(r'^│ N │ User', out.encode('utf-8'), re.MULTILINE) - assert re.search(r'^│ 1 │', out.encode('utf-8'), re.MULTILINE) + output = f'{out}' + assert re.search(r'^│ N │ User', output, re.MULTILINE) + assert re.search(r'^│ 1 │', output, re.MULTILINE) assert return_value == 0 From 9879e9f2df78baa3cd51db64c4cec3de54820865 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 21:00:22 -0800 Subject: [PATCH 08/10] try yet another way --- meshtastic/tests/test_smoke1.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index b435877..fa6193f 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -140,9 +140,8 @@ def test_smoke1_nodes(): """Test --nodes""" return_value, out = subprocess.getstatusoutput('meshtastic --nodes') assert re.match(r'Connected to radio', out) - output = f'{out}' - assert re.search(r'^│ N │ User', output, re.MULTILINE) - assert re.search(r'^│ 1 │', output, re.MULTILINE) + assert re.search(r'^│ N │ User', str(out), re.MULTILINE) + assert re.search(r'^│ 1 │', str(out), re.MULTILINE) assert return_value == 0 From ce2d9f57287c01cb28f533fee05b482bbc4ec7a6 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 21:13:20 -0800 Subject: [PATCH 09/10] yet another attempt --- meshtastic/tests/test_smoke1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index fa6193f..1b596c9 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -140,8 +140,8 @@ def test_smoke1_nodes(): """Test --nodes""" return_value, out = subprocess.getstatusoutput('meshtastic --nodes') assert re.match(r'Connected to radio', out) - assert re.search(r'^│ N │ User', str(out), re.MULTILINE) - assert re.search(r'^│ 1 │', str(out), re.MULTILINE) + assert re.search(r' User ', out, re.MULTILINE) + assert re.search(r' 1 ', out, re.MULTILINE) assert return_value == 0 From 8e1010e9f2f20d6699ce130ec5508bc5a12f15bb Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Wed, 29 Dec 2021 21:21:24 -0800 Subject: [PATCH 10/10] ignore two checks for Windows smoke1 testing --- meshtastic/tests/test_smoke1.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index 1b596c9..fee8224 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -2,6 +2,7 @@ import re import subprocess import time +import platform import os # Do not like using hard coded sleeps, but it probably makes @@ -140,8 +141,9 @@ def test_smoke1_nodes(): """Test --nodes""" return_value, out = subprocess.getstatusoutput('meshtastic --nodes') assert re.match(r'Connected to radio', out) - assert re.search(r' User ', out, re.MULTILINE) - assert re.search(r' 1 ', out, re.MULTILINE) + if platform.system() != 'Windows': + assert re.search(r' User ', out, re.MULTILINE) + assert re.search(r' 1 ', out, re.MULTILINE) assert return_value == 0