mirror of
https://github.com/meshtastic/python.git
synced 2026-01-15 19:28:05 -05:00
Merge pull request #188 from mkinney/work_on_serial_issue
revamp the serial connection to avoid Tbeam reboots
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
""" Serial interface class
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
import platform
|
||||
import os
|
||||
import stat
|
||||
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"""
|
||||
|
||||
@@ -20,6 +22,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 +38,30 @@ 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:
|
||||
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)
|
||||
|
||||
# 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, write_timeout=0)
|
||||
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)
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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'^│ N │ 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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user