blob: c0e4dc08596973e3c45023e5f9aa4c22aaee958e [file] [log] [blame]
cliechti576de252002-02-28 23:54:44 +00001#!/usr/bin/env python
Chris Liechtifbdd8a02015-08-09 02:37:45 +02002#
cliechtia128a702004-07-21 22:13:31 +00003# Very simple serial terminal
Chris Liechtifbdd8a02015-08-09 02:37:45 +02004#
Chris Liechti3e02f702015-12-16 23:06:04 +01005# This file is part of pySerial. https://github.com/pyserial/pyserial
Chris Liechti68340d72015-08-03 14:15:48 +02006# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +02007#
8# SPDX-License-Identifier: BSD-3-Clause
cliechtifc9eb382002-03-05 01:12:29 +00009
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020010import codecs
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020011import os
12import sys
13import threading
cliechti576de252002-02-28 23:54:44 +000014
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020015import serial
Chris Liechti55ba7d92015-08-15 16:33:51 +020016from serial.tools.list_ports import comports
Chris Liechti168704f2015-09-30 16:50:29 +020017from serial.tools import hexlify_codec
18
Chris Liechtia887c932016-02-13 23:10:14 +010019# pylint: disable=wrong-import-order,wrong-import-position
20
Chris Liechti168704f2015-09-30 16:50:29 +020021codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020022
Chris Liechti68340d72015-08-03 14:15:48 +020023try:
24 raw_input
25except NameError:
Chris Liechtia887c932016-02-13 23:10:14 +010026 # pylint: disable=redefined-builtin,invalid-name
Chris Liechti68340d72015-08-03 14:15:48 +020027 raw_input = input # in python3 it's "raw"
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020028 unichr = chr
Chris Liechti68340d72015-08-03 14:15:48 +020029
cliechti6c8eb2f2009-07-08 02:10:46 +000030
31def key_description(character):
32 """generate a readable description for a key"""
33 ascii_code = ord(character)
34 if ascii_code < 32:
35 return 'Ctrl+%c' % (ord('@') + ascii_code)
36 else:
37 return repr(character)
38
cliechti91165532011-03-18 02:02:52 +000039
Chris Liechti9a720852015-08-25 00:20:38 +020040# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020041class ConsoleBase(object):
Chris Liechti397cf412016-02-11 00:11:48 +010042 """OS abstraction for console (input/output codec, no echo)"""
43
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020044 def __init__(self):
45 if sys.version_info >= (3, 0):
46 self.byte_output = sys.stdout.buffer
47 else:
48 self.byte_output = sys.stdout
49 self.output = sys.stdout
cliechtif467aa82013-10-13 21:36:49 +000050
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020051 def setup(self):
Chris Liechti397cf412016-02-11 00:11:48 +010052 """Set console to read single characters, no echo"""
cliechtif467aa82013-10-13 21:36:49 +000053
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020054 def cleanup(self):
Chris Liechti397cf412016-02-11 00:11:48 +010055 """Restore default console settings"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020056
57 def getkey(self):
Chris Liechti397cf412016-02-11 00:11:48 +010058 """Read a single key from the console"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020059 return None
60
Chris Liechtia887c932016-02-13 23:10:14 +010061 def write_bytes(self, byte_string):
Chris Liechti397cf412016-02-11 00:11:48 +010062 """Write bytes (already encoded)"""
Chris Liechtia887c932016-02-13 23:10:14 +010063 self.byte_output.write(byte_string)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020064 self.byte_output.flush()
65
Chris Liechtia887c932016-02-13 23:10:14 +010066 def write(self, text):
Chris Liechti397cf412016-02-11 00:11:48 +010067 """Write string"""
Chris Liechtia887c932016-02-13 23:10:14 +010068 self.output.write(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020069 self.output.flush()
70
Chris Liechti1eb3f6b2016-04-27 02:12:50 +020071 def cancel(self):
72 """Cancel getkey operation"""
73
Chris Liechti269f77b2015-08-24 01:31:42 +020074 # - - - - - - - - - - - - - - - - - - - - - - - -
75 # context manager:
76 # switch terminal temporary to normal mode (e.g. to get user input)
77
78 def __enter__(self):
79 self.cleanup()
80 return self
81
82 def __exit__(self, *args, **kwargs):
83 self.setup()
84
cliechti9c592b32008-06-16 22:00:14 +000085
Chris Liechtiba45c522016-02-06 23:53:23 +010086if os.name == 'nt': # noqa
cliechti576de252002-02-28 23:54:44 +000087 import msvcrt
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020088 import ctypes
Chris Liechti9cc696b2015-08-28 00:54:22 +020089
90 class Out(object):
Chris Liechtia887c932016-02-13 23:10:14 +010091 """file-like wrapper that uses os.write"""
92
Chris Liechti9cc696b2015-08-28 00:54:22 +020093 def __init__(self, fd):
94 self.fd = fd
95
96 def flush(self):
97 pass
98
99 def write(self, s):
100 os.write(self.fd, s)
101
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200102 class Console(ConsoleBase):
Chris Liechticbb00b22015-08-13 22:58:49 +0200103 def __init__(self):
104 super(Console, self).__init__()
Chris Liechti1df28272015-08-27 23:37:38 +0200105 self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
106 self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
Chris Liechticbb00b22015-08-13 22:58:49 +0200107 ctypes.windll.kernel32.SetConsoleOutputCP(65001)
108 ctypes.windll.kernel32.SetConsoleCP(65001)
Chris Liechti9cc696b2015-08-28 00:54:22 +0200109 self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
110 # the change of the code page is not propagated to Python, manually fix it
111 sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
112 sys.stdout = self.output
Chris Liechti168704f2015-09-30 16:50:29 +0200113 self.output.encoding = 'UTF-8' # needed for input
Chris Liechticbb00b22015-08-13 22:58:49 +0200114
Chris Liechti1df28272015-08-27 23:37:38 +0200115 def __del__(self):
116 ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
117 ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
118
cliechti3a8bf092008-09-17 11:26:53 +0000119 def getkey(self):
cliechti91165532011-03-18 02:02:52 +0000120 while True:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200121 z = msvcrt.getwch()
Chris Liechti9f398812015-09-13 18:50:44 +0200122 if z == unichr(13):
123 return unichr(10)
124 elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200125 msvcrt.getwch()
cliechti9c592b32008-06-16 22:00:14 +0000126 else:
cliechti9c592b32008-06-16 22:00:14 +0000127 return z
cliechti53edb472009-02-06 21:18:46 +0000128
Chris Liechti1eb3f6b2016-04-27 02:12:50 +0200129 def cancel(self):
Chris Liechti933a5172016-05-04 16:12:15 +0200130 # XXX check if CancelIOEx could be used
Chris Liechti1eb3f6b2016-04-27 02:12:50 +0200131 hwnd = ctypes.windll.kernel32.GetConsoleWindow()
132 ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
133
cliechti576de252002-02-28 23:54:44 +0000134elif os.name == 'posix':
Chris Liechtia1d5c6d2015-08-07 14:41:24 +0200135 import atexit
136 import termios
Chris Liechti9cc696b2015-08-28 00:54:22 +0200137
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200138 class Console(ConsoleBase):
cliechti9c592b32008-06-16 22:00:14 +0000139 def __init__(self):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200140 super(Console, self).__init__()
cliechti9c592b32008-06-16 22:00:14 +0000141 self.fd = sys.stdin.fileno()
Chris Liechti4d989c22015-08-24 00:24:49 +0200142 self.old = termios.tcgetattr(self.fd)
Chris Liechti89eb2472015-08-08 17:06:25 +0200143 atexit.register(self.cleanup)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200144 if sys.version_info < (3, 0):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200145 self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
146 else:
147 self.enc_stdin = sys.stdin
cliechti9c592b32008-06-16 22:00:14 +0000148
149 def setup(self):
cliechti9c592b32008-06-16 22:00:14 +0000150 new = termios.tcgetattr(self.fd)
151 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
152 new[6][termios.VMIN] = 1
153 new[6][termios.VTIME] = 0
154 termios.tcsetattr(self.fd, termios.TCSANOW, new)
cliechti53edb472009-02-06 21:18:46 +0000155
cliechti9c592b32008-06-16 22:00:14 +0000156 def getkey(self):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200157 c = self.enc_stdin.read(1)
Chris Liechti9f398812015-09-13 18:50:44 +0200158 if c == unichr(0x7f):
159 c = unichr(8) # map the BS key (which yields DEL) to backspace
Chris Liechti9a720852015-08-25 00:20:38 +0200160 return c
cliechti53edb472009-02-06 21:18:46 +0000161
cliechti9c592b32008-06-16 22:00:14 +0000162 def cleanup(self):
Chris Liechti4d989c22015-08-24 00:24:49 +0200163 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
cliechti9c592b32008-06-16 22:00:14 +0000164
cliechti576de252002-02-28 23:54:44 +0000165else:
Chris Liechti397cf412016-02-11 00:11:48 +0100166 raise NotImplementedError(
167 'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
cliechti576de252002-02-28 23:54:44 +0000168
cliechti6fa76fb2009-07-08 23:53:39 +0000169
Chris Liechti9a720852015-08-25 00:20:38 +0200170# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200171
172class Transform(object):
Chris Liechticbb00b22015-08-13 22:58:49 +0200173 """do-nothing: forward all data unchanged"""
Chris Liechtid698af72015-08-24 20:24:55 +0200174 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200175 """text received from serial port"""
176 return text
177
Chris Liechtid698af72015-08-24 20:24:55 +0200178 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200179 """text to be sent to serial port"""
180 return text
181
182 def echo(self, text):
183 """text to be sent but displayed on console"""
184 return text
185
Chris Liechti442bf512015-08-15 01:42:24 +0200186
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200187class CRLF(Transform):
188 """ENTER sends CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200189
Chris Liechtid698af72015-08-24 20:24:55 +0200190 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200191 return text.replace('\n', '\r\n')
192
Chris Liechti442bf512015-08-15 01:42:24 +0200193
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200194class CR(Transform):
195 """ENTER sends CR"""
Chris Liechtid698af72015-08-24 20:24:55 +0200196
197 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200198 return text.replace('\r', '\n')
199
Chris Liechtid698af72015-08-24 20:24:55 +0200200 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200201 return text.replace('\n', '\r')
202
Chris Liechti442bf512015-08-15 01:42:24 +0200203
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200204class LF(Transform):
205 """ENTER sends LF"""
206
207
208class NoTerminal(Transform):
209 """remove typical terminal control codes from input"""
Chris Liechti9a720852015-08-25 00:20:38 +0200210
211 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
Chris Liechtiba45c522016-02-06 23:53:23 +0100212 REPLACEMENT_MAP.update(
213 {
Chris Liechti033f17c2015-08-30 21:28:04 +0200214 0x7F: 0x2421, # DEL
215 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100216 })
Chris Liechti9a720852015-08-25 00:20:38 +0200217
Chris Liechtid698af72015-08-24 20:24:55 +0200218 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200219 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200220
Chris Liechtid698af72015-08-24 20:24:55 +0200221 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200222
223
Chris Liechti9a720852015-08-25 00:20:38 +0200224class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200225 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200226
Chris Liechti9a720852015-08-25 00:20:38 +0200227 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
Chris Liechtiba45c522016-02-06 23:53:23 +0100228 REPLACEMENT_MAP.update(
229 {
Chris Liechtia887c932016-02-13 23:10:14 +0100230 0x20: 0x2423, # visual space
Chris Liechti033f17c2015-08-30 21:28:04 +0200231 0x7F: 0x2421, # DEL
232 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100233 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200234
235
236class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200237 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200238
Chris Liechtid698af72015-08-24 20:24:55 +0200239 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200240 r = []
Chris Liechtia887c932016-02-13 23:10:14 +0100241 for c in text:
242 if ' ' <= c < '\x7f' or c in '\r\n\b\t':
243 r.append(c)
244 elif c < ' ':
245 r.append(unichr(0x2400 + ord(c)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200246 else:
Chris Liechtia887c932016-02-13 23:10:14 +0100247 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200248 r.append(' ')
249 return ''.join(r)
250
Chris Liechtid698af72015-08-24 20:24:55 +0200251 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200252
253
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200254class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200255 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200256
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200257 def __init__(self):
258 # XXX make it configurable, use colorama?
259 self.input_color = '\x1b[37m'
260 self.echo_color = '\x1b[31m'
261
Chris Liechtid698af72015-08-24 20:24:55 +0200262 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200263 return self.input_color + text
264
265 def echo(self, text):
266 return self.echo_color + text
267
Chris Liechti442bf512015-08-15 01:42:24 +0200268
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200269class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200270 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200271
Chris Liechtid698af72015-08-24 20:24:55 +0200272 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200273 sys.stderr.write(' [RX:{}] '.format(repr(text)))
274 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200275 return text
276
Chris Liechtid698af72015-08-24 20:24:55 +0200277 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200278 sys.stderr.write(' [TX:{}] '.format(repr(text)))
279 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200280 return text
281
Chris Liechti442bf512015-08-15 01:42:24 +0200282
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200283# other ideas:
284# - add date/time for each newline
285# - insert newline after: a) timeout b) packet end character
286
Chris Liechtib3df13e2015-08-25 02:20:09 +0200287EOL_TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100288 'crlf': CRLF,
289 'cr': CR,
290 'lf': LF,
291}
Chris Liechtib3df13e2015-08-25 02:20:09 +0200292
293TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100294 'direct': Transform, # no transformation
295 'default': NoTerminal,
296 'nocontrol': NoControls,
297 'printable': Printable,
298 'colorize': Colorize,
299 'debug': DebugIO,
300}
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200301
302
Chris Liechti033f17c2015-08-30 21:28:04 +0200303# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200304def ask_for_port():
305 """\
306 Show a list of ports and ask the user for a choice. To make selection
307 easier on systems with long device names, also allow the input of an
308 index.
309 """
310 sys.stderr.write('\n--- Available ports:\n')
311 ports = []
312 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
313 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
314 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
315 ports.append(port)
316 while True:
317 port = raw_input('--- Enter port index or full name: ')
318 try:
319 index = int(port) - 1
320 if not 0 <= index < len(ports):
321 sys.stderr.write('--- Invalid index!\n')
322 continue
323 except ValueError:
324 pass
325 else:
326 port = ports[index]
327 return port
cliechti1351dde2012-04-12 16:47:47 +0000328
329
cliechti8c2ea842011-03-18 01:51:46 +0000330class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200331 """\
332 Terminal application. Copy data from serial port to console and vice versa.
333 Handle special keys from the console to show menu etc.
334 """
335
Chris Liechti3b454802015-08-26 23:39:59 +0200336 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200337 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200338 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000339 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200340 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200341 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200342 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200343 self.eol = eol
344 self.filters = filters
345 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200346 self.exit_character = 0x1d # GS/CTRL+]
347 self.menu_character = 0x14 # Menu: CTRL+T
Chris Liechti397cf412016-02-11 00:11:48 +0100348 self.alive = None
349 self._reader_alive = None
350 self.receiver_thread = None
351 self.rx_decoder = None
352 self.tx_decoder = None
cliechti576de252002-02-28 23:54:44 +0000353
cliechti8c2ea842011-03-18 01:51:46 +0000354 def _start_reader(self):
355 """Start reader thread"""
356 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000357 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200358 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
359 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000360 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000361
362 def _stop_reader(self):
363 """Stop reader thread only, wait for clean exit of thread"""
364 self._reader_alive = False
Chris Liechti933a5172016-05-04 16:12:15 +0200365 if hasattr(self.serial, 'cancel_read'):
366 self.serial.cancel_read()
cliechti8c2ea842011-03-18 01:51:46 +0000367 self.receiver_thread.join()
368
cliechti8c2ea842011-03-18 01:51:46 +0000369 def start(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100370 """start worker threads"""
cliechti8c2ea842011-03-18 01:51:46 +0000371 self.alive = True
372 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000373 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200374 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
375 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000376 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200377 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000378
cliechti6385f2c2005-09-21 19:51:19 +0000379 def stop(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100380 """set flag to stop worker threads"""
cliechti6385f2c2005-09-21 19:51:19 +0000381 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000382
cliechtibf6bb7d2006-03-30 00:28:18 +0000383 def join(self, transmit_only=False):
Chris Liechtia887c932016-02-13 23:10:14 +0100384 """wait for worker threads to terminate"""
cliechti6385f2c2005-09-21 19:51:19 +0000385 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000386 if not transmit_only:
Chris Liechti933a5172016-05-04 16:12:15 +0200387 if hasattr(self.serial, 'cancel_read'):
388 self.serial.cancel_read()
cliechtibf6bb7d2006-03-30 00:28:18 +0000389 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000390
Chris Liechti933a5172016-05-04 16:12:15 +0200391 def close(self):
392 self.serial.close()
393
Chris Liechtib3df13e2015-08-25 02:20:09 +0200394 def update_transformations(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100395 """take list of transformation classes and instantiate them for rx and tx"""
Chris Liechti397cf412016-02-11 00:11:48 +0100396 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
397 for f in self.filters]
Chris Liechtib3df13e2015-08-25 02:20:09 +0200398 self.tx_transformations = [t() for t in transformations]
399 self.rx_transformations = list(reversed(self.tx_transformations))
400
Chris Liechtid698af72015-08-24 20:24:55 +0200401 def set_rx_encoding(self, encoding, errors='replace'):
Chris Liechtia887c932016-02-13 23:10:14 +0100402 """set encoding for received data"""
Chris Liechtid698af72015-08-24 20:24:55 +0200403 self.input_encoding = encoding
404 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
405
406 def set_tx_encoding(self, encoding, errors='replace'):
Chris Liechtia887c932016-02-13 23:10:14 +0100407 """set encoding for transmitted data"""
Chris Liechtid698af72015-08-24 20:24:55 +0200408 self.output_encoding = encoding
409 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
410
cliechti6c8eb2f2009-07-08 02:10:46 +0000411 def dump_port_settings(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100412 """Write current settings to sys.stderr"""
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200413 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
Chris Liechti397cf412016-02-11 00:11:48 +0100414 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200415 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100416 ('active' if self.serial.rts else 'inactive'),
417 ('active' if self.serial.dtr else 'inactive'),
418 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000419 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200420 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100421 ('active' if self.serial.cts else 'inactive'),
422 ('active' if self.serial.dsr else 'inactive'),
423 ('active' if self.serial.ri else 'inactive'),
424 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000425 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200426 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000427 # yet received. ignore this error.
428 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200429 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
430 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
Chris Liechti442bf512015-08-15 01:42:24 +0200431 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
432 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200433 sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
434 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
cliechti6c8eb2f2009-07-08 02:10:46 +0000435
cliechti6385f2c2005-09-21 19:51:19 +0000436 def reader(self):
437 """loop and copy serial->console"""
cliechti6963b262010-01-02 03:01:21 +0000438 try:
cliechti8c2ea842011-03-18 01:51:46 +0000439 while self.alive and self._reader_alive:
Chris Liechti188cf592015-08-22 00:28:19 +0200440 # read all that is there or wait for one byte
Chris Liechti3b454802015-08-26 23:39:59 +0200441 data = self.serial.read(self.serial.in_waiting or 1)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200442 if data:
443 if self.raw:
444 self.console.write_bytes(data)
cliechti6963b262010-01-02 03:01:21 +0000445 else:
Chris Liechtid698af72015-08-24 20:24:55 +0200446 text = self.rx_decoder.decode(data)
Chris Liechtie1384382015-08-15 17:06:05 +0200447 for transformation in self.rx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200448 text = transformation.rx(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200449 self.console.write(text)
Chris Liechti033f17c2015-08-30 21:28:04 +0200450 except serial.SerialException:
cliechti6963b262010-01-02 03:01:21 +0000451 self.alive = False
Chris Liechti1eb3f6b2016-04-27 02:12:50 +0200452 self.console.cancel()
453 raise # XXX handle instead of re-raise?
cliechti576de252002-02-28 23:54:44 +0000454
cliechti6385f2c2005-09-21 19:51:19 +0000455 def writer(self):
cliechti8c2ea842011-03-18 01:51:46 +0000456 """\
Chris Liechti442bf512015-08-15 01:42:24 +0200457 Loop and copy console->serial until self.exit_character character is
458 found. When self.menu_character is found, interpret the next key
cliechti8c2ea842011-03-18 01:51:46 +0000459 locally.
cliechti6c8eb2f2009-07-08 02:10:46 +0000460 """
461 menu_active = False
462 try:
463 while self.alive:
464 try:
Chris Liechti89eb2472015-08-08 17:06:25 +0200465 c = self.console.getkey()
cliechti6c8eb2f2009-07-08 02:10:46 +0000466 except KeyboardInterrupt:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200467 c = '\x03'
Chris Liechti1eb3f6b2016-04-27 02:12:50 +0200468 if not self.alive:
469 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000470 if menu_active:
Chris Liechti7af7c752015-08-12 15:45:19 +0200471 self.handle_menu_key(c)
cliechti6c8eb2f2009-07-08 02:10:46 +0000472 menu_active = False
Chris Liechti442bf512015-08-15 01:42:24 +0200473 elif c == self.menu_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200474 menu_active = True # next char will be for menu
Chris Liechti442bf512015-08-15 01:42:24 +0200475 elif c == self.exit_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200476 self.stop() # exit app
477 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000478 else:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200479 #~ if self.raw:
480 text = c
Chris Liechtie1384382015-08-15 17:06:05 +0200481 for transformation in self.tx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200482 text = transformation.tx(text)
Chris Liechtid698af72015-08-24 20:24:55 +0200483 self.serial.write(self.tx_encoder.encode(text))
cliechti6c8eb2f2009-07-08 02:10:46 +0000484 if self.echo:
Chris Liechti3b454802015-08-26 23:39:59 +0200485 echo_text = c
486 for transformation in self.tx_transformations:
487 echo_text = transformation.echo(echo_text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200488 self.console.write(echo_text)
cliechti6c8eb2f2009-07-08 02:10:46 +0000489 except:
490 self.alive = False
491 raise
cliechti6385f2c2005-09-21 19:51:19 +0000492
Chris Liechti7af7c752015-08-12 15:45:19 +0200493 def handle_menu_key(self, c):
494 """Implement a simple menu / settings"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200495 if c == self.menu_character or c == self.exit_character:
496 # Menu/exit character again -> send itself
Chris Liechtid698af72015-08-24 20:24:55 +0200497 self.serial.write(self.tx_encoder.encode(c))
Chris Liechti7af7c752015-08-12 15:45:19 +0200498 if self.echo:
499 self.console.write(c)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200500 elif c == '\x15': # CTRL+U -> upload file
Chris Liechti7af7c752015-08-12 15:45:19 +0200501 sys.stderr.write('\n--- File to upload: ')
502 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200503 with self.console:
504 filename = sys.stdin.readline().rstrip('\r\n')
505 if filename:
506 try:
507 with open(filename, 'rb') as f:
508 sys.stderr.write('--- Sending file {} ---\n'.format(filename))
509 while True:
510 block = f.read(1024)
511 if not block:
512 break
513 self.serial.write(block)
514 # Wait for output buffer to drain.
515 self.serial.flush()
516 sys.stderr.write('.') # Progress indicator.
517 sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
518 except IOError as e:
519 sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200520 elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
Chris Liechti442bf512015-08-15 01:42:24 +0200521 sys.stderr.write(self.get_help_text())
Chris Liechti7af7c752015-08-12 15:45:19 +0200522 elif c == '\x12': # CTRL+R -> Toggle RTS
Chris Liechti3b454802015-08-26 23:39:59 +0200523 self.serial.rts = not self.serial.rts
524 sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200525 elif c == '\x04': # CTRL+D -> Toggle DTR
Chris Liechti3b454802015-08-26 23:39:59 +0200526 self.serial.dtr = not self.serial.dtr
527 sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200528 elif c == '\x02': # CTRL+B -> toggle BREAK condition
Chris Liechti3b454802015-08-26 23:39:59 +0200529 self.serial.break_condition = not self.serial.break_condition
530 sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200531 elif c == '\x05': # CTRL+E -> toggle local echo
532 self.echo = not self.echo
Chris Liechti442bf512015-08-15 01:42:24 +0200533 sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200534 elif c == '\x06': # CTRL+F -> edit filters
535 sys.stderr.write('\n--- Available Filters:\n')
536 sys.stderr.write('\n'.join(
Chris Liechti397cf412016-02-11 00:11:48 +0100537 '--- {:<10} = {.__doc__}'.format(k, v)
538 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200539 sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
540 with self.console:
541 new_filters = sys.stdin.readline().lower().split()
542 if new_filters:
543 for f in new_filters:
544 if f not in TRANSFORMATIONS:
545 sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
546 break
547 else:
548 self.filters = new_filters
549 self.update_transformations()
550 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
551 elif c == '\x0c': # CTRL+L -> EOL mode
Chris Liechti033f17c2015-08-30 21:28:04 +0200552 modes = list(EOL_TRANSFORMATIONS) # keys
Chris Liechtib3df13e2015-08-25 02:20:09 +0200553 eol = modes.index(self.eol) + 1
554 if eol >= len(modes):
555 eol = 0
556 self.eol = modes[eol]
557 sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
558 self.update_transformations()
559 elif c == '\x01': # CTRL+A -> set encoding
560 sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
561 with self.console:
562 new_encoding = sys.stdin.readline().strip()
563 if new_encoding:
564 try:
565 codecs.lookup(new_encoding)
566 except LookupError:
567 sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
568 else:
569 self.set_rx_encoding(new_encoding)
570 self.set_tx_encoding(new_encoding)
571 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
572 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechti7af7c752015-08-12 15:45:19 +0200573 elif c == '\x09': # CTRL+I -> info
574 self.dump_port_settings()
575 #~ elif c == '\x01': # CTRL+A -> cycle escape mode
576 #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
577 elif c in 'pP': # P -> change port
Chris Liechti269f77b2015-08-24 01:31:42 +0200578 with self.console:
579 try:
Chris Liechti89313c92015-09-01 02:33:13 +0200580 port = ask_for_port()
Chris Liechti269f77b2015-08-24 01:31:42 +0200581 except KeyboardInterrupt:
582 port = None
Chris Liechti7af7c752015-08-12 15:45:19 +0200583 if port and port != self.serial.port:
584 # reader thread needs to be shut down
585 self._stop_reader()
586 # save settings
587 settings = self.serial.getSettingsDict()
588 try:
589 new_serial = serial.serial_for_url(port, do_not_open=True)
590 # restore settings and open
591 new_serial.applySettingsDict(settings)
Chris Liechti89313c92015-09-01 02:33:13 +0200592 new_serial.rts = self.serial.rts
593 new_serial.dtr = self.serial.dtr
Chris Liechti7af7c752015-08-12 15:45:19 +0200594 new_serial.open()
Chris Liechti89313c92015-09-01 02:33:13 +0200595 new_serial.break_condition = self.serial.break_condition
Chris Liechti7af7c752015-08-12 15:45:19 +0200596 except Exception as e:
Chris Liechti442bf512015-08-15 01:42:24 +0200597 sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200598 new_serial.close()
599 else:
600 self.serial.close()
601 self.serial = new_serial
Chris Liechti442bf512015-08-15 01:42:24 +0200602 sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
Chris Liechti7af7c752015-08-12 15:45:19 +0200603 # and restart the reader thread
604 self._start_reader()
605 elif c in 'bB': # B -> change baudrate
606 sys.stderr.write('\n--- Baudrate: ')
607 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200608 with self.console:
609 backup = self.serial.baudrate
610 try:
611 self.serial.baudrate = int(sys.stdin.readline().strip())
612 except ValueError as e:
Chris Liechti4d541e22016-02-13 23:13:59 +0100613 sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
Chris Liechti269f77b2015-08-24 01:31:42 +0200614 self.serial.baudrate = backup
615 else:
616 self.dump_port_settings()
Chris Liechti7af7c752015-08-12 15:45:19 +0200617 elif c == '8': # 8 -> change to 8 bits
618 self.serial.bytesize = serial.EIGHTBITS
619 self.dump_port_settings()
620 elif c == '7': # 7 -> change to 8 bits
621 self.serial.bytesize = serial.SEVENBITS
622 self.dump_port_settings()
623 elif c in 'eE': # E -> change to even parity
624 self.serial.parity = serial.PARITY_EVEN
625 self.dump_port_settings()
626 elif c in 'oO': # O -> change to odd parity
627 self.serial.parity = serial.PARITY_ODD
628 self.dump_port_settings()
629 elif c in 'mM': # M -> change to mark parity
630 self.serial.parity = serial.PARITY_MARK
631 self.dump_port_settings()
632 elif c in 'sS': # S -> change to space parity
633 self.serial.parity = serial.PARITY_SPACE
634 self.dump_port_settings()
635 elif c in 'nN': # N -> change to no parity
636 self.serial.parity = serial.PARITY_NONE
637 self.dump_port_settings()
638 elif c == '1': # 1 -> change to 1 stop bits
639 self.serial.stopbits = serial.STOPBITS_ONE
640 self.dump_port_settings()
641 elif c == '2': # 2 -> change to 2 stop bits
642 self.serial.stopbits = serial.STOPBITS_TWO
643 self.dump_port_settings()
644 elif c == '3': # 3 -> change to 1.5 stop bits
645 self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
646 self.dump_port_settings()
647 elif c in 'xX': # X -> change software flow control
648 self.serial.xonxoff = (c == 'X')
649 self.dump_port_settings()
650 elif c in 'rR': # R -> change hardware flow control
651 self.serial.rtscts = (c == 'R')
652 self.dump_port_settings()
653 else:
Chris Liechti442bf512015-08-15 01:42:24 +0200654 sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
655
656 def get_help_text(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100657 """return the help text"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200658 # help text, starts with blank line!
Chris Liechti442bf512015-08-15 01:42:24 +0200659 return """
660--- pySerial ({version}) - miniterm - help
661---
662--- {exit:8} Exit program
663--- {menu:8} Menu escape key, followed by:
664--- Menu keys:
665--- {menu:7} Send the menu character itself to remote
666--- {exit:7} Send the exit character itself to remote
667--- {info:7} Show info
668--- {upload:7} Upload file (prompt will be shown)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200669--- {repr:7} encoding
670--- {filter:7} edit filters
Chris Liechti442bf512015-08-15 01:42:24 +0200671--- Toggles:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200672--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
673--- {echo:7} echo {eol:7} EOL
Chris Liechti442bf512015-08-15 01:42:24 +0200674---
Chris Liechti55ba7d92015-08-15 16:33:51 +0200675--- Port settings ({menu} followed by the following):
Chris Liechti442bf512015-08-15 01:42:24 +0200676--- p change port
677--- 7 8 set data bits
Chris Liechtib7550bd2015-08-15 04:09:10 +0200678--- N E O S M change parity (None, Even, Odd, Space, Mark)
Chris Liechti442bf512015-08-15 01:42:24 +0200679--- 1 2 3 set stop bits (1, 2, 1.5)
680--- b change baud rate
681--- x X disable/enable software flow control
682--- r R disable/enable hardware flow control
Chris Liechtia887c932016-02-13 23:10:14 +0100683""".format(version=getattr(serial, 'VERSION', 'unknown version'),
684 exit=key_description(self.exit_character),
685 menu=key_description(self.menu_character),
686 rts=key_description('\x12'),
687 dtr=key_description('\x04'),
688 brk=key_description('\x02'),
689 echo=key_description('\x05'),
690 info=key_description('\x09'),
691 upload=key_description('\x15'),
692 repr=key_description('\x01'),
693 filter=key_description('\x06'),
694 eol=key_description('\x0c'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200695
696
Chris Liechtib3df13e2015-08-25 02:20:09 +0200697# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti55ba7d92015-08-15 16:33:51 +0200698# default args can be used to override when calling main() from an other script
699# e.g to create a miniterm-my-device.py
700def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
Chris Liechtia887c932016-02-13 23:10:14 +0100701 """Command line tool, entry point"""
702
Chris Liechtib7550bd2015-08-15 04:09:10 +0200703 import argparse
cliechti6385f2c2005-09-21 19:51:19 +0000704
Chris Liechtib7550bd2015-08-15 04:09:10 +0200705 parser = argparse.ArgumentParser(
Chris Liechti397cf412016-02-11 00:11:48 +0100706 description="Miniterm - A simple terminal program for the serial port.")
cliechti6385f2c2005-09-21 19:51:19 +0000707
Chris Liechti033f17c2015-08-30 21:28:04 +0200708 parser.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100709 "port",
710 nargs='?',
711 help="serial port name ('-' to show port list)",
712 default=default_port)
cliechti5370cee2013-10-13 03:08:19 +0000713
Chris Liechti033f17c2015-08-30 21:28:04 +0200714 parser.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100715 "baudrate",
716 nargs='?',
717 type=int,
718 help="set baud rate, default: %(default)s",
719 default=default_baudrate)
cliechti6385f2c2005-09-21 19:51:19 +0000720
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200721 group = parser.add_argument_group("port settings")
cliechti53edb472009-02-06 21:18:46 +0000722
Chris Liechti033f17c2015-08-30 21:28:04 +0200723 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100724 "--parity",
725 choices=['N', 'E', 'O', 'S', 'M'],
726 type=lambda c: c.upper(),
727 help="set parity, one of {N E O S M}, default: N",
728 default='N')
cliechti53edb472009-02-06 21:18:46 +0000729
Chris Liechti033f17c2015-08-30 21:28:04 +0200730 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100731 "--rtscts",
732 action="store_true",
733 help="enable RTS/CTS flow control (default off)",
734 default=False)
cliechti53edb472009-02-06 21:18:46 +0000735
Chris Liechti033f17c2015-08-30 21:28:04 +0200736 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100737 "--xonxoff",
738 action="store_true",
739 help="enable software flow control (default off)",
740 default=False)
cliechti53edb472009-02-06 21:18:46 +0000741
Chris Liechti033f17c2015-08-30 21:28:04 +0200742 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100743 "--rts",
744 type=int,
745 help="set initial RTS line state (possible values: 0, 1)",
746 default=default_rts)
cliechti5370cee2013-10-13 03:08:19 +0000747
Chris Liechti033f17c2015-08-30 21:28:04 +0200748 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100749 "--dtr",
750 type=int,
751 help="set initial DTR line state (possible values: 0, 1)",
752 default=default_dtr)
cliechti5370cee2013-10-13 03:08:19 +0000753
Chris Liechti00f84282015-12-24 23:40:34 +0100754 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100755 "--ask",
756 action="store_true",
757 help="ask again for port when open fails",
758 default=False)
Chris Liechti00f84282015-12-24 23:40:34 +0100759
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200760 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000761
Chris Liechti033f17c2015-08-30 21:28:04 +0200762 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100763 "-e", "--echo",
764 action="store_true",
765 help="enable local echo (default off)",
766 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000767
Chris Liechti033f17c2015-08-30 21:28:04 +0200768 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100769 "--encoding",
770 dest="serial_port_encoding",
771 metavar="CODEC",
772 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
773 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000774
Chris Liechti033f17c2015-08-30 21:28:04 +0200775 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100776 "-f", "--filter",
777 action="append",
778 metavar="NAME",
779 help="add text transformation",
780 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200781
Chris Liechti033f17c2015-08-30 21:28:04 +0200782 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100783 "--eol",
784 choices=['CR', 'LF', 'CRLF'],
785 type=lambda c: c.upper(),
786 help="end of line mode",
787 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000788
Chris Liechti033f17c2015-08-30 21:28:04 +0200789 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100790 "--raw",
791 action="store_true",
792 help="Do no apply any encodings/transformations",
793 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000794
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200795 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000796
Chris Liechti033f17c2015-08-30 21:28:04 +0200797 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100798 "--exit-char",
799 type=int,
800 metavar='NUM',
801 help="Unicode of special character that is used to exit the application, default: %(default)s",
802 default=0x1d) # GS/CTRL+]
cliechtibf6bb7d2006-03-30 00:28:18 +0000803
Chris Liechti033f17c2015-08-30 21:28:04 +0200804 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100805 "--menu-char",
806 type=int,
807 metavar='NUM',
808 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
809 default=0x14) # Menu: CTRL+T
cliechti9c592b32008-06-16 22:00:14 +0000810
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200811 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000812
Chris Liechti033f17c2015-08-30 21:28:04 +0200813 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100814 "-q", "--quiet",
815 action="store_true",
816 help="suppress non-error messages",
817 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000818
Chris Liechti033f17c2015-08-30 21:28:04 +0200819 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100820 "--develop",
821 action="store_true",
822 help="show Python traceback on error",
823 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000824
Chris Liechtib7550bd2015-08-15 04:09:10 +0200825 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000826
Chris Liechtib7550bd2015-08-15 04:09:10 +0200827 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000828 parser.error('--exit-char can not be the same as --menu-char')
829
Chris Liechtib3df13e2015-08-25 02:20:09 +0200830 if args.filter:
831 if 'help' in args.filter:
832 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200833 sys.stderr.write('\n'.join(
Chris Liechti397cf412016-02-11 00:11:48 +0100834 '{:<10} = {.__doc__}'.format(k, v)
835 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200836 sys.stderr.write('\n')
837 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200838 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200839 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200840 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200841
Chris Liechti00f84282015-12-24 23:40:34 +0100842 while True:
843 # no port given on command line -> ask user now
844 if args.port is None or args.port == '-':
845 try:
846 args.port = ask_for_port()
847 except KeyboardInterrupt:
848 sys.stderr.write('\n')
849 parser.error('user aborted and port is not given')
850 else:
851 if not args.port:
852 parser.error('port is not given')
853 try:
854 serial_instance = serial.serial_for_url(
Chris Liechti397cf412016-02-11 00:11:48 +0100855 args.port,
856 args.baudrate,
857 parity=args.parity,
858 rtscts=args.rtscts,
859 xonxoff=args.xonxoff,
860 timeout=1,
861 do_not_open=True)
Chris Liechti3b454802015-08-26 23:39:59 +0200862
Chris Liechti00f84282015-12-24 23:40:34 +0100863 if args.dtr is not None:
864 if not args.quiet:
865 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
866 serial_instance.dtr = args.dtr
867 if args.rts is not None:
868 if not args.quiet:
869 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
870 serial_instance.rts = args.rts
Chris Liechti3b454802015-08-26 23:39:59 +0200871
Chris Liechti00f84282015-12-24 23:40:34 +0100872 serial_instance.open()
873 except serial.SerialException as e:
874 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
875 if args.develop:
876 raise
877 if not args.ask:
878 sys.exit(1)
879 else:
880 args.port = '-'
881 else:
882 break
cliechti6385f2c2005-09-21 19:51:19 +0000883
Chris Liechti3b454802015-08-26 23:39:59 +0200884 miniterm = Miniterm(
Chris Liechti397cf412016-02-11 00:11:48 +0100885 serial_instance,
886 echo=args.echo,
887 eol=args.eol.lower(),
888 filters=filters)
Chris Liechti3b454802015-08-26 23:39:59 +0200889 miniterm.exit_character = unichr(args.exit_char)
890 miniterm.menu_character = unichr(args.menu_char)
891 miniterm.raw = args.raw
892 miniterm.set_rx_encoding(args.serial_port_encoding)
893 miniterm.set_tx_encoding(args.serial_port_encoding)
894
Chris Liechtib7550bd2015-08-15 04:09:10 +0200895 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200896 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100897 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200898 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100899 key_description(miniterm.exit_character),
900 key_description(miniterm.menu_character),
901 key_description(miniterm.menu_character),
902 key_description('\x08')))
cliechti6fa76fb2009-07-08 23:53:39 +0000903
cliechti6385f2c2005-09-21 19:51:19 +0000904 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000905 try:
906 miniterm.join(True)
907 except KeyboardInterrupt:
908 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200909 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000910 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000911 miniterm.join()
Chris Liechti933a5172016-05-04 16:12:15 +0200912 miniterm.close()
cliechtibf6bb7d2006-03-30 00:28:18 +0000913
cliechti5370cee2013-10-13 03:08:19 +0000914# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000915if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000916 main()