def __init__(self, device, baudrate=None, parity=None, rtscts=False, debug=False): self._terminal = Terminal() self._device = device self._baudrate = baudrate or self.DEFAULT_BAUDRATE self._port = self._open_port(self._device, self._baudrate, parity, rtscts, debug) self._resume = False self._silent = False self._rxq = deque() self._rxe = Event() self._debug = debug register(self._cleanup)
class MiniTerm: """A mini serial terminal to demonstrate pyserial extensions""" DEFAULT_BAUDRATE = 115200 def __init__(self, device, baudrate=None, parity=None, rtscts=False, debug=False): self._terminal = Terminal() self._device = device self._baudrate = baudrate or self.DEFAULT_BAUDRATE self._port = self._open_port(self._device, self._baudrate, parity, rtscts, debug) self._resume = False self._silent = False self._rxq = deque() self._rxe = Event() self._debug = debug register(self._cleanup) def run(self, fullmode=False, loopback=False, silent=False, localecho=False, autocr=False): """Switch to a pure serial terminal application""" self._terminal.init(fullmode) print('Entering minicom mode @ %d bps' % self._port.baudrate) stdout.flush() self._resume = True # start the reader (target to host direction) within a dedicated thread args = [loopback] if self._device.startswith('ftdi://'): # with pyftdi/pyusb/libusb stack, there is no kernel buffering # which means that a UART source with data burst may overflow the # FTDI HW buffer while the SW stack is dealing with formatting # and console output. Use an intermediate thread to pop out data # out from the HW as soon as it is made available, and use a deque # to serve the actual reader thread args.append(self._get_from_source) sourcer = Thread(target=self._sourcer) sourcer.setDaemon(1) sourcer.start() else: # regular kernel buffered device args.append(self._get_from_port) reader = Thread(target=self._reader, args=tuple(args)) reader.setDaemon(1) reader.start() # start the writer (host to target direction) self._writer(fullmode, silent, localecho, autocr) def _sourcer(self): try: while self._resume: data = self._port.read(4096) if not data: continue self._rxq.append(data) self._rxe.set() except Exception as ex: self._resume = False print(str(ex), file=stderr) interrupt_main() def _get_from_source(self): while not self._rxq and self._resume: if self._rxe.wait(0.1): self._rxe.clear() break if not self._rxq: return bytearray() return self._rxq.popleft() def _get_from_port(self): try: return self._port.read(4096) except OSError as ex: self._resume = False print(str(ex), file=stderr) interrupt_main() except Exception as ex: print(str(ex), file=stderr) return bytearray() def _reader(self, loopback, getfunc): """Loop forever, processing received serial data in terminal mode""" try: # Try to read as many bytes as possible at once, and use a short # timeout to avoid blocking for more data self._port.timeout = 0.050 while self._resume: if self._silent: sleep(0.25) continue data = getfunc() if data: stdout.write(data.decode('utf8', errors='replace')) stdout.flush() if loopback: self._port.write(data) except KeyboardInterrupt: return except Exception as exc: print("Exception: %s" % exc) if self._debug: print(format_exc(chain=False), file=stderr) interrupt_main() def _writer(self, fullmode, silent, localecho, crlf=0): """Loop and copy console->serial until EOF character is found""" while self._resume: try: char = self._terminal.getkey() if fullmode and ord(char) == 0x2: # Ctrl+B self._cleanup(True) return if self._terminal.IS_MSWIN: if ord(char) in (0, 224): char = self._terminal.getkey() self._port.write(self._terminal.getch_to_escape(char)) continue if ord(char) == 0x3: # Ctrl+C raise KeyboardInterrupt('Ctrl-C break') if silent: if ord(char) == 0x6: # Ctrl+F self._silent = True print('Silent\n') continue if ord(char) == 0x7: # Ctrl+G self._silent = False print('Reg\n') continue if localecho: stdout.write(char.decode('utf8', errors='replace')) stdout.flush() if crlf: if char == b'\n': self._port.write(b'\r') if crlf > 1: continue self._port.write(char) except KeyError: continue except KeyboardInterrupt: if fullmode: if self._terminal.IS_MSWIN: self._port.write(b'\x03') continue self._cleanup(True) def _cleanup(self, *args): """Cleanup resource before exiting""" if args and args[0]: print('%sAborting...' % linesep) try: self._resume = False if self._port: # wait till the other thread completes sleep(0.5) try: rem = self._port.inWaiting() except IOError: # maybe a bug in underlying wrapper... rem = 0 # consumes all the received bytes for _ in range(rem): self._port.read() self._port.close() self._port = None print('Bye.') except Exception as ex: print(str(ex), file=stderr) finally: if self._terminal: self._terminal.reset() self._terminal = None @staticmethod def _open_port(device, baudrate, parity, rtscts, debug=False): """Open the serial communication port""" try: from serial.serialutil import SerialException from serial import PARITY_NONE except ImportError as exc: raise ImportError("Python serial module not installed") from exc try: from serial import serial_for_url, VERSION as serialver version = tuple([int(x) for x in serialver.split('.')]) if version < (3, 0): raise ValueError except (ValueError, IndexError, ImportError) as exc: raise ImportError("pyserial 3.0+ is required") from exc # the following import enables serial protocol extensions if device.startswith('ftdi:'): try: from pyftdi import serialext serialext.touch() except ImportError as exc: raise ImportError("PyFTDI module not installed") from exc try: port = serial_for_url(device, baudrate=baudrate, parity=parity or PARITY_NONE, rtscts=rtscts, timeout=0) if not port.is_open: port.open() if not port.is_open: raise IOError('Cannot open port "%s"' % device) if debug: backend = port.BACKEND if hasattr(port, 'BACKEND') else '?' print("Using serial backend '%s'" % backend) return port except SerialException as exc: raise IOError(str(exc)) from exc