Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/env python
      2 
      3 # Very simple serial terminal
      4 # (C)2002-2011 Chris Liechti <cliechti (at] gmx.net>
      5 
      6 # Input characters are sent directly (only LF -> CR/LF/CRLF translation is
      7 # done), received characters are displayed as is (or escaped trough pythons
      8 # repr, useful for debug purposes)
      9 
     10 
     11 import sys, os, serial, threading
     12 try:
     13     from serial.tools.list_ports import comports
     14 except ImportError:
     15     comports = None
     16 
     17 EXITCHARCTER = serial.to_bytes([0x1d])   # GS/CTRL+]
     18 MENUCHARACTER = serial.to_bytes([0x14])  # Menu: CTRL+T
     19 
     20 DEFAULT_PORT = None
     21 DEFAULT_BAUDRATE = 9600
     22 DEFAULT_RTS = None
     23 DEFAULT_DTR = None
     24 
     25 
     26 def key_description(character):
     27     """generate a readable description for a key"""
     28     ascii_code = ord(character)
     29     if ascii_code < 32:
     30         return 'Ctrl+%c' % (ord('@') + ascii_code)
     31     else:
     32         return repr(character)
     33 
     34 
     35 # help text, starts with blank line! it's a function so that the current values
     36 # for the shortcut keys is used and not the value at program start
     37 def get_help_text():
     38     return """
     39 --- pySerial (%(version)s) - miniterm - help
     40 ---
     41 --- %(exit)-8s Exit program
     42 --- %(menu)-8s Menu escape key, followed by:
     43 --- Menu keys:
     44 ---    %(itself)-7s Send the menu character itself to remote
     45 ---    %(exchar)-7s Send the exit character itself to remote
     46 ---    %(info)-7s Show info
     47 ---    %(upload)-7s Upload file (prompt will be shown)
     48 --- Toggles:
     49 ---    %(rts)-7s RTS          %(echo)-7s local echo
     50 ---    %(dtr)-7s DTR          %(break)-7s BREAK
     51 ---    %(lfm)-7s line feed    %(repr)-7s Cycle repr mode
     52 ---
     53 --- Port settings (%(menu)s followed by the following):
     54 ---    p          change port
     55 ---    7 8        set data bits
     56 ---    n e o s m  change parity (None, Even, Odd, Space, Mark)
     57 ---    1 2 3      set stop bits (1, 2, 1.5)
     58 ---    b          change baud rate
     59 ---    x X        disable/enable software flow control
     60 ---    r R        disable/enable hardware flow control
     61 """ % {
     62     'version': getattr(serial, 'VERSION', 'unknown version'),
     63     'exit': key_description(EXITCHARCTER),
     64     'menu': key_description(MENUCHARACTER),
     65     'rts': key_description('\x12'),
     66     'repr': key_description('\x01'),
     67     'dtr': key_description('\x04'),
     68     'lfm': key_description('\x0c'),
     69     'break': key_description('\x02'),
     70     'echo': key_description('\x05'),
     71     'info': key_description('\x09'),
     72     'upload': key_description('\x15'),
     73     'itself': key_description(MENUCHARACTER),
     74     'exchar': key_description(EXITCHARCTER),
     75 }
     76 
     77 if sys.version_info >= (3, 0):
     78     def character(b):
     79         return b.decode('latin1')
     80 else:
     81     def character(b):
     82         return b
     83 
     84 LF = serial.to_bytes([10])
     85 CR = serial.to_bytes([13])
     86 CRLF = serial.to_bytes([13, 10])
     87 
     88 X00 = serial.to_bytes([0])
     89 X0E = serial.to_bytes([0x0e])
     90 
     91 # first choose a platform dependant way to read single characters from the console
     92 global console
     93 
     94 if os.name == 'nt':
     95     import msvcrt
     96     class Console(object):
     97         def __init__(self):
     98             pass
     99 
    100         def setup(self):
    101             pass    # Do nothing for 'nt'
    102 
    103         def cleanup(self):
    104             pass    # Do nothing for 'nt'
    105 
    106         def getkey(self):
    107             while True:
    108                 z = msvcrt.getch()
    109                 if z == X00 or z == X0E:    # functions keys, ignore
    110                     msvcrt.getch()
    111                 else:
    112                     if z == CR:
    113                         return LF
    114                     return z
    115 
    116     console = Console()
    117 
    118 elif os.name == 'posix':
    119     import termios, sys, os
    120     class Console(object):
    121         def __init__(self):
    122             self.fd = sys.stdin.fileno()
    123             self.old = None
    124 
    125         def setup(self):
    126             self.old = termios.tcgetattr(self.fd)
    127             new = termios.tcgetattr(self.fd)
    128             new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
    129             new[6][termios.VMIN] = 1
    130             new[6][termios.VTIME] = 0
    131             termios.tcsetattr(self.fd, termios.TCSANOW, new)
    132 
    133         def getkey(self):
    134             c = os.read(self.fd, 1)
    135             return c
    136 
    137         def cleanup(self):
    138             if self.old is not None:
    139                 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
    140 
    141     console = Console()
    142 
    143     def cleanup_console():
    144         console.cleanup()
    145 
    146     sys.exitfunc = cleanup_console      # terminal modes have to be restored on exit...
    147 
    148 else:
    149     raise NotImplementedError("Sorry no implementation for your platform (%s) available." % sys.platform)
    150 
    151 
    152 def dump_port_list():
    153     if comports:
    154         sys.stderr.write('\n--- Available ports:\n')
    155         for port, desc, hwid in sorted(comports()):
    156             #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
    157             sys.stderr.write('--- %-20s %s\n' % (port, desc))
    158 
    159 
    160 CONVERT_CRLF = 2
    161 CONVERT_CR   = 1
    162 CONVERT_LF   = 0
    163 NEWLINE_CONVERISON_MAP = (LF, CR, CRLF)
    164 LF_MODES = ('LF', 'CR', 'CR/LF')
    165 
    166 REPR_MODES = ('raw', 'some control', 'all control', 'hex')
    167 
    168 class Miniterm(object):
    169     def __init__(self, port, baudrate, parity, rtscts, xonxoff, echo=False, convert_outgoing=CONVERT_CRLF, repr_mode=0):
    170         try:
    171             self.serial = serial.serial_for_url(port, baudrate, parity=parity, rtscts=rtscts, xonxoff=xonxoff, timeout=1)
    172         except AttributeError:
    173             # happens when the installed pyserial is older than 2.5. use the
    174             # Serial class directly then.
    175             self.serial = serial.Serial(port, baudrate, parity=parity, rtscts=rtscts, xonxoff=xonxoff, timeout=1)
    176         self.echo = echo
    177         self.repr_mode = repr_mode
    178         self.convert_outgoing = convert_outgoing
    179         self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
    180         self.dtr_state = True
    181         self.rts_state = True
    182         self.break_state = False
    183 
    184     def _start_reader(self):
    185         """Start reader thread"""
    186         self._reader_alive = True
    187         # start serial->console thread
    188         self.receiver_thread = threading.Thread(target=self.reader)
    189         self.receiver_thread.setDaemon(True)
    190         self.receiver_thread.start()
    191 
    192     def _stop_reader(self):
    193         """Stop reader thread only, wait for clean exit of thread"""
    194         self._reader_alive = False
    195         self.receiver_thread.join()
    196 
    197 
    198     def start(self):
    199         self.alive = True
    200         self._start_reader()
    201         # enter console->serial loop
    202         self.transmitter_thread = threading.Thread(target=self.writer)
    203         self.transmitter_thread.setDaemon(True)
    204         self.transmitter_thread.start()
    205 
    206     def stop(self):
    207         self.alive = False
    208 
    209     def join(self, transmit_only=False):
    210         self.transmitter_thread.join()
    211         if not transmit_only:
    212             self.receiver_thread.join()
    213 
    214     def dump_port_settings(self):
    215         sys.stderr.write("\n--- Settings: %s  %s,%s,%s,%s\n" % (
    216                 self.serial.portstr,
    217                 self.serial.baudrate,
    218                 self.serial.bytesize,
    219                 self.serial.parity,
    220                 self.serial.stopbits))
    221         sys.stderr.write('--- RTS: %-8s  DTR: %-8s  BREAK: %-8s\n' % (
    222                 (self.rts_state and 'active' or 'inactive'),
    223                 (self.dtr_state and 'active' or 'inactive'),
    224                 (self.break_state and 'active' or 'inactive')))
    225         try:
    226             sys.stderr.write('--- CTS: %-8s  DSR: %-8s  RI: %-8s  CD: %-8s\n' % (
    227                     (self.serial.getCTS() and 'active' or 'inactive'),
    228                     (self.serial.getDSR() and 'active' or 'inactive'),
    229                     (self.serial.getRI() and 'active' or 'inactive'),
    230                     (self.serial.getCD() and 'active' or 'inactive')))
    231         except serial.SerialException:
    232             # on RFC 2217 ports it can happen to no modem state notification was
    233             # yet received. ignore this error.
    234             pass
    235         sys.stderr.write('--- software flow control: %s\n' % (self.serial.xonxoff and 'active' or 'inactive'))
    236         sys.stderr.write('--- hardware flow control: %s\n' % (self.serial.rtscts and 'active' or 'inactive'))
    237         sys.stderr.write('--- data escaping: %s  linefeed: %s\n' % (
    238                 REPR_MODES[self.repr_mode],
    239                 LF_MODES[self.convert_outgoing]))
    240 
    241     def reader(self):
    242         """loop and copy serial->console"""
    243         try:
    244             while self.alive and self._reader_alive:
    245                 data = character(self.serial.read(1))
    246 
    247                 if self.repr_mode == 0:
    248                     # direct output, just have to care about newline setting
    249                     if data == '\r' and self.convert_outgoing == CONVERT_CR:
    250                         sys.stdout.write('\n')
    251                     else:
    252                         sys.stdout.write(data)
    253                 elif self.repr_mode == 1:
    254                     # escape non-printable, let pass newlines
    255                     if self.convert_outgoing == CONVERT_CRLF and data in '\r\n':
    256                         if data == '\n':
    257                             sys.stdout.write('\n')
    258                         elif data == '\r':
    259                             pass
    260                     elif data == '\n' and self.convert_outgoing == CONVERT_LF:
    261                         sys.stdout.write('\n')
    262                     elif data == '\r' and self.convert_outgoing == CONVERT_CR:
    263                         sys.stdout.write('\n')
    264                     else:
    265                         sys.stdout.write(repr(data)[1:-1])
    266                 elif self.repr_mode == 2:
    267                     # escape all non-printable, including newline
    268                     sys.stdout.write(repr(data)[1:-1])
    269                 elif self.repr_mode == 3:
    270                     # escape everything (hexdump)
    271                     for c in data:
    272                         sys.stdout.write("%s " % c.encode('hex'))
    273                 sys.stdout.flush()
    274         except serial.SerialException, e:
    275             self.alive = False
    276             # would be nice if the console reader could be interruptted at this
    277             # point...
    278             raise
    279 
    280 
    281     def writer(self):
    282         """\
    283         Loop and copy console->serial until EXITCHARCTER character is
    284         found. When MENUCHARACTER is found, interpret the next key
    285         locally.
    286         """
    287         menu_active = False
    288         try:
    289             while self.alive:
    290                 try:
    291                     b = console.getkey()
    292                 except KeyboardInterrupt:
    293                     b = serial.to_bytes([3])
    294                 c = character(b)
    295                 if menu_active:
    296                     if c == MENUCHARACTER or c == EXITCHARCTER: # Menu character again/exit char -> send itself
    297                         self.serial.write(b)                    # send character
    298                         if self.echo:
    299                             sys.stdout.write(c)
    300                     elif c == '\x15':                       # CTRL+U -> upload file
    301                         sys.stderr.write('\n--- File to upload: ')
    302                         sys.stderr.flush()
    303                         console.cleanup()
    304                         filename = sys.stdin.readline().rstrip('\r\n')
    305                         if filename:
    306                             try:
    307                                 file = open(filename, 'r')
    308                                 sys.stderr.write('--- Sending file %s ---\n' % filename)
    309                                 while True:
    310                                     line = file.readline().rstrip('\r\n')
    311                                     if not line:
    312                                         break
    313                                     self.serial.write(line)
    314                                     self.serial.write('\r\n')
    315                                     # Wait for output buffer to drain.
    316                                     self.serial.flush()
    317                                     sys.stderr.write('.')   # Progress indicator.
    318                                 sys.stderr.write('\n--- File %s sent ---\n' % filename)
    319                             except IOError, e:
    320                                 sys.stderr.write('--- ERROR opening file %s: %s ---\n' % (filename, e))
    321                         console.setup()
    322                     elif c in '\x08hH?':                    # CTRL+H, h, H, ? -> Show help
    323                         sys.stderr.write(get_help_text())
    324                     elif c == '\x12':                       # CTRL+R -> Toggle RTS
    325                         self.rts_state = not self.rts_state
    326                         self.serial.setRTS(self.rts_state)
    327                         sys.stderr.write('--- RTS %s ---\n' % (self.rts_state and 'active' or 'inactive'))
    328                     elif c == '\x04':                       # CTRL+D -> Toggle DTR
    329                         self.dtr_state = not self.dtr_state
    330                         self.serial.setDTR(self.dtr_state)
    331                         sys.stderr.write('--- DTR %s ---\n' % (self.dtr_state and 'active' or 'inactive'))
    332                     elif c == '\x02':                       # CTRL+B -> toggle BREAK condition
    333                         self.break_state = not self.break_state
    334                         self.serial.setBreak(self.break_state)
    335                         sys.stderr.write('--- BREAK %s ---\n' % (self.break_state and 'active' or 'inactive'))
    336                     elif c == '\x05':                       # CTRL+E -> toggle local echo
    337                         self.echo = not self.echo
    338                         sys.stderr.write('--- local echo %s ---\n' % (self.echo and 'active' or 'inactive'))
    339                     elif c == '\x09':                       # CTRL+I -> info
    340                         self.dump_port_settings()
    341                     elif c == '\x01':                       # CTRL+A -> cycle escape mode
    342                         self.repr_mode += 1
    343                         if self.repr_mode > 3:
    344                             self.repr_mode = 0
    345                         sys.stderr.write('--- escape data: %s ---\n' % (
    346                             REPR_MODES[self.repr_mode],
    347                         ))
    348                     elif c == '\x0c':                       # CTRL+L -> cycle linefeed mode
    349                         self.convert_outgoing += 1
    350                         if self.convert_outgoing > 2:
    351                             self.convert_outgoing = 0
    352                         self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
    353                         sys.stderr.write('--- line feed %s ---\n' % (
    354                             LF_MODES[self.convert_outgoing],
    355                         ))
    356                     elif c in 'pP':                         # P -> change port
    357                         dump_port_list()
    358                         sys.stderr.write('--- Enter port name: ')
    359                         sys.stderr.flush()
    360                         console.cleanup()
    361                         try:
    362                             port = sys.stdin.readline().strip()
    363                         except KeyboardInterrupt:
    364                             port = None
    365                         console.setup()
    366                         if port and port != self.serial.port:
    367                             # reader thread needs to be shut down
    368                             self._stop_reader()
    369                             # save settings
    370                             settings = self.serial.getSettingsDict()
    371                             try:
    372                                 try:
    373                                     new_serial = serial.serial_for_url(port, do_not_open=True)
    374                                 except AttributeError:
    375                                     # happens when the installed pyserial is older than 2.5. use the
    376                                     # Serial class directly then.
    377                                     new_serial = serial.Serial()
    378                                     new_serial.port = port
    379                                 # restore settings and open
    380                                 new_serial.applySettingsDict(settings)
    381                                 new_serial.open()
    382                                 new_serial.setRTS(self.rts_state)
    383                                 new_serial.setDTR(self.dtr_state)
    384                                 new_serial.setBreak(self.break_state)
    385                             except Exception, e:
    386                                 sys.stderr.write('--- ERROR opening new port: %s ---\n' % (e,))
    387                                 new_serial.close()
    388                             else:
    389                                 self.serial.close()
    390                                 self.serial = new_serial
    391                                 sys.stderr.write('--- Port changed to: %s ---\n' % (self.serial.port,))
    392                             # and restart the reader thread
    393                             self._start_reader()
    394                     elif c in 'bB':                         # B -> change baudrate
    395                         sys.stderr.write('\n--- Baudrate: ')
    396                         sys.stderr.flush()
    397                         console.cleanup()
    398                         backup = self.serial.baudrate
    399                         try:
    400                             self.serial.baudrate = int(sys.stdin.readline().strip())
    401                         except ValueError, e:
    402                             sys.stderr.write('--- ERROR setting baudrate: %s ---\n' % (e,))
    403                             self.serial.baudrate = backup
    404                         else:
    405                             self.dump_port_settings()
    406                         console.setup()
    407                     elif c == '8':                          # 8 -> change to 8 bits
    408                         self.serial.bytesize = serial.EIGHTBITS
    409                         self.dump_port_settings()
    410                     elif c == '7':                          # 7 -> change to 8 bits
    411                         self.serial.bytesize = serial.SEVENBITS
    412                         self.dump_port_settings()
    413                     elif c in 'eE':                         # E -> change to even parity
    414                         self.serial.parity = serial.PARITY_EVEN
    415                         self.dump_port_settings()
    416                     elif c in 'oO':                         # O -> change to odd parity
    417                         self.serial.parity = serial.PARITY_ODD
    418                         self.dump_port_settings()
    419                     elif c in 'mM':                         # M -> change to mark parity
    420                         self.serial.parity = serial.PARITY_MARK
    421                         self.dump_port_settings()
    422                     elif c in 'sS':                         # S -> change to space parity
    423                         self.serial.parity = serial.PARITY_SPACE
    424                         self.dump_port_settings()
    425                     elif c in 'nN':                         # N -> change to no parity
    426                         self.serial.parity = serial.PARITY_NONE
    427                         self.dump_port_settings()
    428                     elif c == '1':                          # 1 -> change to 1 stop bits
    429                         self.serial.stopbits = serial.STOPBITS_ONE
    430                         self.dump_port_settings()
    431                     elif c == '2':                          # 2 -> change to 2 stop bits
    432                         self.serial.stopbits = serial.STOPBITS_TWO
    433                         self.dump_port_settings()
    434                     elif c == '3':                          # 3 -> change to 1.5 stop bits
    435                         self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
    436                         self.dump_port_settings()
    437                     elif c in 'xX':                         # X -> change software flow control
    438                         self.serial.xonxoff = (c == 'X')
    439                         self.dump_port_settings()
    440                     elif c in 'rR':                         # R -> change hardware flow control
    441                         self.serial.rtscts = (c == 'R')
    442                         self.dump_port_settings()
    443                     else:
    444                         sys.stderr.write('--- unknown menu character %s --\n' % key_description(c))
    445                     menu_active = False
    446                 elif c == MENUCHARACTER: # next char will be for menu
    447                     menu_active = True
    448                 elif c == EXITCHARCTER: 
    449                     self.stop()
    450                     break                                   # exit app
    451                 elif c == '\n':
    452                     self.serial.write(self.newline)         # send newline character(s)
    453                     if self.echo:
    454                         sys.stdout.write(c)                 # local echo is a real newline in any case
    455                         sys.stdout.flush()
    456                 else:
    457                     self.serial.write(b)                    # send byte
    458                     if self.echo:
    459                         sys.stdout.write(c)
    460                         sys.stdout.flush()
    461         except:
    462             self.alive = False
    463             raise
    464 
    465 def main():
    466     import optparse
    467 
    468     parser = optparse.OptionParser(
    469         usage = "%prog [options] [port [baudrate]]",
    470         description = "Miniterm - A simple terminal program for the serial port."
    471     )
    472 
    473     group = optparse.OptionGroup(parser, "Port settings")
    474 
    475     group.add_option("-p", "--port",
    476         dest = "port",
    477         help = "port, a number or a device name. (deprecated option, use parameter instead)",
    478         default = DEFAULT_PORT
    479     )
    480 
    481     group.add_option("-b", "--baud",
    482         dest = "baudrate",
    483         action = "store",
    484         type = 'int',
    485         help = "set baud rate, default %default",
    486         default = DEFAULT_BAUDRATE
    487     )
    488 
    489     group.add_option("--parity",
    490         dest = "parity",
    491         action = "store",
    492         help = "set parity, one of [N, E, O, S, M], default=N",
    493         default = 'N'
    494     )
    495 
    496     group.add_option("--rtscts",
    497         dest = "rtscts",
    498         action = "store_true",
    499         help = "enable RTS/CTS flow control (default off)",
    500         default = False
    501     )
    502 
    503     group.add_option("--xonxoff",
    504         dest = "xonxoff",
    505         action = "store_true",
    506         help = "enable software flow control (default off)",
    507         default = False
    508     )
    509 
    510     group.add_option("--rts",
    511         dest = "rts_state",
    512         action = "store",
    513         type = 'int',
    514         help = "set initial RTS line state (possible values: 0, 1)",
    515         default = DEFAULT_RTS
    516     )
    517 
    518     group.add_option("--dtr",
    519         dest = "dtr_state",
    520         action = "store",
    521         type = 'int',
    522         help = "set initial DTR line state (possible values: 0, 1)",
    523         default = DEFAULT_DTR
    524     )
    525 
    526     parser.add_option_group(group)
    527 
    528     group = optparse.OptionGroup(parser, "Data handling")
    529 
    530     group.add_option("-e", "--echo",
    531         dest = "echo",
    532         action = "store_true",
    533         help = "enable local echo (default off)",
    534         default = False
    535     )
    536 
    537     group.add_option("--cr",
    538         dest = "cr",
    539         action = "store_true",
    540         help = "do not send CR+LF, send CR only",
    541         default = False
    542     )
    543 
    544     group.add_option("--lf",
    545         dest = "lf",
    546         action = "store_true",
    547         help = "do not send CR+LF, send LF only",
    548         default = False
    549     )
    550 
    551     group.add_option("-D", "--debug",
    552         dest = "repr_mode",
    553         action = "count",
    554         help = """debug received data (escape non-printable chars)
    555 --debug can be given multiple times:
    556 0: just print what is received
    557 1: escape non-printable characters, do newlines as unusual
    558 2: escape non-printable characters, newlines too
    559 3: hex dump everything""",
    560         default = 0
    561     )
    562 
    563     parser.add_option_group(group)
    564 
    565 
    566     group = optparse.OptionGroup(parser, "Hotkeys")
    567 
    568     group.add_option("--exit-char",
    569         dest = "exit_char",
    570         action = "store",
    571         type = 'int',
    572         help = "ASCII code of special character that is used to exit the application",
    573         default = 0x1d
    574     )
    575 
    576     group.add_option("--menu-char",
    577         dest = "menu_char",
    578         action = "store",
    579         type = 'int',
    580         help = "ASCII code of special character that is used to control miniterm (menu)",
    581         default = 0x14
    582     )
    583 
    584     parser.add_option_group(group)
    585 
    586     group = optparse.OptionGroup(parser, "Diagnostics")
    587 
    588     group.add_option("-q", "--quiet",
    589         dest = "quiet",
    590         action = "store_true",
    591         help = "suppress non-error messages",
    592         default = False
    593     )
    594 
    595     parser.add_option_group(group)
    596 
    597 
    598     (options, args) = parser.parse_args()
    599 
    600     options.parity = options.parity.upper()
    601     if options.parity not in 'NEOSM':
    602         parser.error("invalid parity")
    603 
    604     if options.cr and options.lf:
    605         parser.error("only one of --cr or --lf can be specified")
    606 
    607     if options.menu_char == options.exit_char:
    608         parser.error('--exit-char can not be the same as --menu-char')
    609 
    610     global EXITCHARCTER, MENUCHARACTER
    611     EXITCHARCTER = chr(options.exit_char)
    612     MENUCHARACTER = chr(options.menu_char)
    613 
    614     port = options.port
    615     baudrate = options.baudrate
    616     if args:
    617         if options.port is not None:
    618             parser.error("no arguments are allowed, options only when --port is given")
    619         port = args.pop(0)
    620         if args:
    621             try:
    622                 baudrate = int(args[0])
    623             except ValueError:
    624                 parser.error("baud rate must be a number, not %r" % args[0])
    625             args.pop(0)
    626         if args:
    627             parser.error("too many arguments")
    628     else:
    629         # noport given on command line -> ask user now
    630         if port is None:
    631             dump_port_list()
    632             port = raw_input('Enter port name:')
    633 
    634     convert_outgoing = CONVERT_CRLF
    635     if options.cr:
    636         convert_outgoing = CONVERT_CR
    637     elif options.lf:
    638         convert_outgoing = CONVERT_LF
    639 
    640     try:
    641         miniterm = Miniterm(
    642             port,
    643             baudrate,
    644             options.parity,
    645             rtscts=options.rtscts,
    646             xonxoff=options.xonxoff,
    647             echo=options.echo,
    648             convert_outgoing=convert_outgoing,
    649             repr_mode=options.repr_mode,
    650         )
    651     except serial.SerialException, e:
    652         sys.stderr.write("could not open port %r: %s\n" % (port, e))
    653         sys.exit(1)
    654 
    655     if not options.quiet:
    656         sys.stderr.write('--- Miniterm on %s: %d,%s,%s,%s ---\n' % (
    657             miniterm.serial.portstr,
    658             miniterm.serial.baudrate,
    659             miniterm.serial.bytesize,
    660             miniterm.serial.parity,
    661             miniterm.serial.stopbits,
    662         ))
    663         sys.stderr.write('--- Quit: %s  |  Menu: %s | Help: %s followed by %s ---\n' % (
    664             key_description(EXITCHARCTER),
    665             key_description(MENUCHARACTER),
    666             key_description(MENUCHARACTER),
    667             key_description('\x08'),
    668         ))
    669 
    670     if options.dtr_state is not None:
    671         if not options.quiet:
    672             sys.stderr.write('--- forcing DTR %s\n' % (options.dtr_state and 'active' or 'inactive'))
    673         miniterm.serial.setDTR(options.dtr_state)
    674         miniterm.dtr_state = options.dtr_state
    675     if options.rts_state is not None:
    676         if not options.quiet:
    677             sys.stderr.write('--- forcing RTS %s\n' % (options.rts_state and 'active' or 'inactive'))
    678         miniterm.serial.setRTS(options.rts_state)
    679         miniterm.rts_state = options.rts_state
    680 
    681     console.setup()
    682     miniterm.start()
    683     try:
    684         miniterm.join(True)
    685     except KeyboardInterrupt:
    686         pass
    687     if not options.quiet:
    688         sys.stderr.write("\n--- exit ---\n")
    689     miniterm.join()
    690     #~ console.cleanup()
    691 
    692 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    693 if __name__ == '__main__':
    694     main()
    695