blob: e82cc9bb7c18ec90921d9061cf6132b2ab5db0c5 [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
19codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020020
Chris Liechti68340d72015-08-03 14:15:48 +020021try:
22 raw_input
23except NameError:
24 raw_input = input # in python3 it's "raw"
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020025 unichr = chr
Chris Liechti68340d72015-08-03 14:15:48 +020026
cliechti6c8eb2f2009-07-08 02:10:46 +000027
28def key_description(character):
29 """generate a readable description for a key"""
30 ascii_code = ord(character)
31 if ascii_code < 32:
32 return 'Ctrl+%c' % (ord('@') + ascii_code)
33 else:
34 return repr(character)
35
cliechti91165532011-03-18 02:02:52 +000036
Chris Liechti9a720852015-08-25 00:20:38 +020037# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020038class ConsoleBase(object):
Chris Liechti397cf412016-02-11 00:11:48 +010039 """OS abstraction for console (input/output codec, no echo)"""
40
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020041 def __init__(self):
42 if sys.version_info >= (3, 0):
43 self.byte_output = sys.stdout.buffer
44 else:
45 self.byte_output = sys.stdout
46 self.output = sys.stdout
cliechtif467aa82013-10-13 21:36:49 +000047
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020048 def setup(self):
Chris Liechti397cf412016-02-11 00:11:48 +010049 """Set console to read single characters, no echo"""
cliechtif467aa82013-10-13 21:36:49 +000050
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020051 def cleanup(self):
Chris Liechti397cf412016-02-11 00:11:48 +010052 """Restore default console settings"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020053
54 def getkey(self):
Chris Liechti397cf412016-02-11 00:11:48 +010055 """Read a single key from the console"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020056 return None
57
58 def write_bytes(self, s):
Chris Liechti397cf412016-02-11 00:11:48 +010059 """Write bytes (already encoded)"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020060 self.byte_output.write(s)
61 self.byte_output.flush()
62
63 def write(self, s):
Chris Liechti397cf412016-02-11 00:11:48 +010064 """Write string"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020065 self.output.write(s)
66 self.output.flush()
67
Chris Liechti269f77b2015-08-24 01:31:42 +020068 # - - - - - - - - - - - - - - - - - - - - - - - -
69 # context manager:
70 # switch terminal temporary to normal mode (e.g. to get user input)
71
72 def __enter__(self):
73 self.cleanup()
74 return self
75
76 def __exit__(self, *args, **kwargs):
77 self.setup()
78
cliechti9c592b32008-06-16 22:00:14 +000079
Chris Liechtiba45c522016-02-06 23:53:23 +010080if os.name == 'nt': # noqa
cliechti576de252002-02-28 23:54:44 +000081 import msvcrt
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020082 import ctypes
Chris Liechti9cc696b2015-08-28 00:54:22 +020083
84 class Out(object):
85 def __init__(self, fd):
86 self.fd = fd
87
88 def flush(self):
89 pass
90
91 def write(self, s):
92 os.write(self.fd, s)
93
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020094 class Console(ConsoleBase):
Chris Liechticbb00b22015-08-13 22:58:49 +020095 def __init__(self):
96 super(Console, self).__init__()
Chris Liechti1df28272015-08-27 23:37:38 +020097 self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
98 self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
Chris Liechticbb00b22015-08-13 22:58:49 +020099 ctypes.windll.kernel32.SetConsoleOutputCP(65001)
100 ctypes.windll.kernel32.SetConsoleCP(65001)
Chris Liechti9cc696b2015-08-28 00:54:22 +0200101 self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
102 # the change of the code page is not propagated to Python, manually fix it
103 sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
104 sys.stdout = self.output
Chris Liechti168704f2015-09-30 16:50:29 +0200105 self.output.encoding = 'UTF-8' # needed for input
Chris Liechticbb00b22015-08-13 22:58:49 +0200106
Chris Liechti1df28272015-08-27 23:37:38 +0200107 def __del__(self):
108 ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
109 ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
110
cliechti3a8bf092008-09-17 11:26:53 +0000111 def getkey(self):
cliechti91165532011-03-18 02:02:52 +0000112 while True:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200113 z = msvcrt.getwch()
Chris Liechti9f398812015-09-13 18:50:44 +0200114 if z == unichr(13):
115 return unichr(10)
116 elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200117 msvcrt.getwch()
cliechti9c592b32008-06-16 22:00:14 +0000118 else:
cliechti9c592b32008-06-16 22:00:14 +0000119 return z
cliechti53edb472009-02-06 21:18:46 +0000120
cliechti576de252002-02-28 23:54:44 +0000121elif os.name == 'posix':
Chris Liechtia1d5c6d2015-08-07 14:41:24 +0200122 import atexit
123 import termios
Chris Liechti9cc696b2015-08-28 00:54:22 +0200124
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200125 class Console(ConsoleBase):
cliechti9c592b32008-06-16 22:00:14 +0000126 def __init__(self):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200127 super(Console, self).__init__()
cliechti9c592b32008-06-16 22:00:14 +0000128 self.fd = sys.stdin.fileno()
Chris Liechti4d989c22015-08-24 00:24:49 +0200129 self.old = termios.tcgetattr(self.fd)
Chris Liechti89eb2472015-08-08 17:06:25 +0200130 atexit.register(self.cleanup)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200131 if sys.version_info < (3, 0):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200132 self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
133 else:
134 self.enc_stdin = sys.stdin
cliechti9c592b32008-06-16 22:00:14 +0000135
136 def setup(self):
cliechti9c592b32008-06-16 22:00:14 +0000137 new = termios.tcgetattr(self.fd)
138 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
139 new[6][termios.VMIN] = 1
140 new[6][termios.VTIME] = 0
141 termios.tcsetattr(self.fd, termios.TCSANOW, new)
cliechti53edb472009-02-06 21:18:46 +0000142
cliechti9c592b32008-06-16 22:00:14 +0000143 def getkey(self):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200144 c = self.enc_stdin.read(1)
Chris Liechti9f398812015-09-13 18:50:44 +0200145 if c == unichr(0x7f):
146 c = unichr(8) # map the BS key (which yields DEL) to backspace
Chris Liechti9a720852015-08-25 00:20:38 +0200147 return c
cliechti53edb472009-02-06 21:18:46 +0000148
cliechti9c592b32008-06-16 22:00:14 +0000149 def cleanup(self):
Chris Liechti4d989c22015-08-24 00:24:49 +0200150 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
cliechti9c592b32008-06-16 22:00:14 +0000151
cliechti576de252002-02-28 23:54:44 +0000152else:
Chris Liechti397cf412016-02-11 00:11:48 +0100153 raise NotImplementedError(
154 'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
cliechti576de252002-02-28 23:54:44 +0000155
cliechti6fa76fb2009-07-08 23:53:39 +0000156
Chris Liechti9a720852015-08-25 00:20:38 +0200157# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200158
159class Transform(object):
Chris Liechticbb00b22015-08-13 22:58:49 +0200160 """do-nothing: forward all data unchanged"""
Chris Liechtid698af72015-08-24 20:24:55 +0200161 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200162 """text received from serial port"""
163 return text
164
Chris Liechtid698af72015-08-24 20:24:55 +0200165 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200166 """text to be sent to serial port"""
167 return text
168
169 def echo(self, text):
170 """text to be sent but displayed on console"""
171 return text
172
Chris Liechti442bf512015-08-15 01:42:24 +0200173
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200174class CRLF(Transform):
175 """ENTER sends CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200176
Chris Liechtid698af72015-08-24 20:24:55 +0200177 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200178 return text.replace('\n', '\r\n')
179
Chris Liechti442bf512015-08-15 01:42:24 +0200180
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200181class CR(Transform):
182 """ENTER sends CR"""
Chris Liechtid698af72015-08-24 20:24:55 +0200183
184 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200185 return text.replace('\r', '\n')
186
Chris Liechtid698af72015-08-24 20:24:55 +0200187 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200188 return text.replace('\n', '\r')
189
Chris Liechti442bf512015-08-15 01:42:24 +0200190
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200191class LF(Transform):
192 """ENTER sends LF"""
193
194
195class NoTerminal(Transform):
196 """remove typical terminal control codes from input"""
Chris Liechti9a720852015-08-25 00:20:38 +0200197
198 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 +0100199 REPLACEMENT_MAP.update(
200 {
Chris Liechti033f17c2015-08-30 21:28:04 +0200201 0x7F: 0x2421, # DEL
202 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100203 })
Chris Liechti9a720852015-08-25 00:20:38 +0200204
Chris Liechtid698af72015-08-24 20:24:55 +0200205 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200206 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200207
Chris Liechtid698af72015-08-24 20:24:55 +0200208 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200209
210
Chris Liechti9a720852015-08-25 00:20:38 +0200211class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200212 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200213
Chris Liechti9a720852015-08-25 00:20:38 +0200214 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
Chris Liechtiba45c522016-02-06 23:53:23 +0100215 REPLACEMENT_MAP.update(
216 {
Chris Liechti033f17c2015-08-30 21:28:04 +0200217 32: 0x2423, # visual space
218 0x7F: 0x2421, # DEL
219 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100220 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200221
222
223class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200224 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200225
Chris Liechtid698af72015-08-24 20:24:55 +0200226 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200227 r = []
228 for t in text:
Chris Liechti7e9cfd42015-08-12 15:28:19 +0200229 if ' ' <= t < '\x7f' or t in '\r\n\b\t':
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200230 r.append(t)
Chris Liechtid698af72015-08-24 20:24:55 +0200231 elif t < ' ':
232 r.append(unichr(0x2400 + ord(t)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200233 else:
234 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(t)))
235 r.append(' ')
236 return ''.join(r)
237
Chris Liechtid698af72015-08-24 20:24:55 +0200238 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200239
240
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200241class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200242 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200243
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200244 def __init__(self):
245 # XXX make it configurable, use colorama?
246 self.input_color = '\x1b[37m'
247 self.echo_color = '\x1b[31m'
248
Chris Liechtid698af72015-08-24 20:24:55 +0200249 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200250 return self.input_color + text
251
252 def echo(self, text):
253 return self.echo_color + text
254
Chris Liechti442bf512015-08-15 01:42:24 +0200255
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200256class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200257 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200258
Chris Liechtid698af72015-08-24 20:24:55 +0200259 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200260 sys.stderr.write(' [RX:{}] '.format(repr(text)))
261 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200262 return text
263
Chris Liechtid698af72015-08-24 20:24:55 +0200264 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200265 sys.stderr.write(' [TX:{}] '.format(repr(text)))
266 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200267 return text
268
Chris Liechti442bf512015-08-15 01:42:24 +0200269
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200270# other ideas:
271# - add date/time for each newline
272# - insert newline after: a) timeout b) packet end character
273
Chris Liechtib3df13e2015-08-25 02:20:09 +0200274EOL_TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100275 'crlf': CRLF,
276 'cr': CR,
277 'lf': LF,
278}
Chris Liechtib3df13e2015-08-25 02:20:09 +0200279
280TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100281 'direct': Transform, # no transformation
282 'default': NoTerminal,
283 'nocontrol': NoControls,
284 'printable': Printable,
285 'colorize': Colorize,
286 'debug': DebugIO,
287}
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200288
289
Chris Liechti033f17c2015-08-30 21:28:04 +0200290# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200291def ask_for_port():
292 """\
293 Show a list of ports and ask the user for a choice. To make selection
294 easier on systems with long device names, also allow the input of an
295 index.
296 """
297 sys.stderr.write('\n--- Available ports:\n')
298 ports = []
299 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
300 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
301 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
302 ports.append(port)
303 while True:
304 port = raw_input('--- Enter port index or full name: ')
305 try:
306 index = int(port) - 1
307 if not 0 <= index < len(ports):
308 sys.stderr.write('--- Invalid index!\n')
309 continue
310 except ValueError:
311 pass
312 else:
313 port = ports[index]
314 return port
cliechti1351dde2012-04-12 16:47:47 +0000315
316
cliechti8c2ea842011-03-18 01:51:46 +0000317class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200318 """\
319 Terminal application. Copy data from serial port to console and vice versa.
320 Handle special keys from the console to show menu etc.
321 """
322
Chris Liechti3b454802015-08-26 23:39:59 +0200323 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200324 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200325 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000326 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200327 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200328 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200329 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200330 self.eol = eol
331 self.filters = filters
332 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200333 self.exit_character = 0x1d # GS/CTRL+]
334 self.menu_character = 0x14 # Menu: CTRL+T
Chris Liechti397cf412016-02-11 00:11:48 +0100335 self.alive = None
336 self._reader_alive = None
337 self.receiver_thread = None
338 self.rx_decoder = None
339 self.tx_decoder = None
cliechti576de252002-02-28 23:54:44 +0000340
cliechti8c2ea842011-03-18 01:51:46 +0000341 def _start_reader(self):
342 """Start reader thread"""
343 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000344 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200345 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
346 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000347 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000348
349 def _stop_reader(self):
350 """Stop reader thread only, wait for clean exit of thread"""
351 self._reader_alive = False
352 self.receiver_thread.join()
353
cliechti8c2ea842011-03-18 01:51:46 +0000354 def start(self):
355 self.alive = True
356 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000357 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200358 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
359 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000360 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200361 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000362
cliechti6385f2c2005-09-21 19:51:19 +0000363 def stop(self):
364 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000365
cliechtibf6bb7d2006-03-30 00:28:18 +0000366 def join(self, transmit_only=False):
cliechti6385f2c2005-09-21 19:51:19 +0000367 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000368 if not transmit_only:
369 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000370
Chris Liechtib3df13e2015-08-25 02:20:09 +0200371 def update_transformations(self):
Chris Liechti397cf412016-02-11 00:11:48 +0100372 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
373 for f in self.filters]
Chris Liechtib3df13e2015-08-25 02:20:09 +0200374 self.tx_transformations = [t() for t in transformations]
375 self.rx_transformations = list(reversed(self.tx_transformations))
376
Chris Liechtid698af72015-08-24 20:24:55 +0200377 def set_rx_encoding(self, encoding, errors='replace'):
378 self.input_encoding = encoding
379 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
380
381 def set_tx_encoding(self, encoding, errors='replace'):
382 self.output_encoding = encoding
383 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
384
cliechti6c8eb2f2009-07-08 02:10:46 +0000385 def dump_port_settings(self):
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200386 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
Chris Liechti397cf412016-02-11 00:11:48 +0100387 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200388 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100389 ('active' if self.serial.rts else 'inactive'),
390 ('active' if self.serial.dtr else 'inactive'),
391 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000392 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200393 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100394 ('active' if self.serial.cts else 'inactive'),
395 ('active' if self.serial.dsr else 'inactive'),
396 ('active' if self.serial.ri else 'inactive'),
397 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000398 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200399 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000400 # yet received. ignore this error.
401 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200402 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
403 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
Chris Liechti442bf512015-08-15 01:42:24 +0200404 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
405 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200406 sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
407 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
cliechti6c8eb2f2009-07-08 02:10:46 +0000408
cliechti6385f2c2005-09-21 19:51:19 +0000409 def reader(self):
410 """loop and copy serial->console"""
cliechti6963b262010-01-02 03:01:21 +0000411 try:
cliechti8c2ea842011-03-18 01:51:46 +0000412 while self.alive and self._reader_alive:
Chris Liechti188cf592015-08-22 00:28:19 +0200413 # read all that is there or wait for one byte
Chris Liechti3b454802015-08-26 23:39:59 +0200414 data = self.serial.read(self.serial.in_waiting or 1)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200415 if data:
416 if self.raw:
417 self.console.write_bytes(data)
cliechti6963b262010-01-02 03:01:21 +0000418 else:
Chris Liechtid698af72015-08-24 20:24:55 +0200419 text = self.rx_decoder.decode(data)
Chris Liechtie1384382015-08-15 17:06:05 +0200420 for transformation in self.rx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200421 text = transformation.rx(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200422 self.console.write(text)
Chris Liechti033f17c2015-08-30 21:28:04 +0200423 except serial.SerialException:
cliechti6963b262010-01-02 03:01:21 +0000424 self.alive = False
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200425 # XXX would be nice if the writer could be interrupted at this
426 # point... to exit completely
cliechti6963b262010-01-02 03:01:21 +0000427 raise
cliechti576de252002-02-28 23:54:44 +0000428
cliechti6385f2c2005-09-21 19:51:19 +0000429 def writer(self):
cliechti8c2ea842011-03-18 01:51:46 +0000430 """\
Chris Liechti442bf512015-08-15 01:42:24 +0200431 Loop and copy console->serial until self.exit_character character is
432 found. When self.menu_character is found, interpret the next key
cliechti8c2ea842011-03-18 01:51:46 +0000433 locally.
cliechti6c8eb2f2009-07-08 02:10:46 +0000434 """
435 menu_active = False
436 try:
437 while self.alive:
438 try:
Chris Liechti89eb2472015-08-08 17:06:25 +0200439 c = self.console.getkey()
cliechti6c8eb2f2009-07-08 02:10:46 +0000440 except KeyboardInterrupt:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200441 c = '\x03'
cliechti6c8eb2f2009-07-08 02:10:46 +0000442 if menu_active:
Chris Liechti7af7c752015-08-12 15:45:19 +0200443 self.handle_menu_key(c)
cliechti6c8eb2f2009-07-08 02:10:46 +0000444 menu_active = False
Chris Liechti442bf512015-08-15 01:42:24 +0200445 elif c == self.menu_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200446 menu_active = True # next char will be for menu
Chris Liechti442bf512015-08-15 01:42:24 +0200447 elif c == self.exit_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200448 self.stop() # exit app
449 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000450 else:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200451 #~ if self.raw:
452 text = c
Chris Liechtie1384382015-08-15 17:06:05 +0200453 for transformation in self.tx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200454 text = transformation.tx(text)
Chris Liechtid698af72015-08-24 20:24:55 +0200455 self.serial.write(self.tx_encoder.encode(text))
cliechti6c8eb2f2009-07-08 02:10:46 +0000456 if self.echo:
Chris Liechti3b454802015-08-26 23:39:59 +0200457 echo_text = c
458 for transformation in self.tx_transformations:
459 echo_text = transformation.echo(echo_text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200460 self.console.write(echo_text)
cliechti6c8eb2f2009-07-08 02:10:46 +0000461 except:
462 self.alive = False
463 raise
cliechti6385f2c2005-09-21 19:51:19 +0000464
Chris Liechti7af7c752015-08-12 15:45:19 +0200465 def handle_menu_key(self, c):
466 """Implement a simple menu / settings"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200467 if c == self.menu_character or c == self.exit_character:
468 # Menu/exit character again -> send itself
Chris Liechtid698af72015-08-24 20:24:55 +0200469 self.serial.write(self.tx_encoder.encode(c))
Chris Liechti7af7c752015-08-12 15:45:19 +0200470 if self.echo:
471 self.console.write(c)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200472 elif c == '\x15': # CTRL+U -> upload file
Chris Liechti7af7c752015-08-12 15:45:19 +0200473 sys.stderr.write('\n--- File to upload: ')
474 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200475 with self.console:
476 filename = sys.stdin.readline().rstrip('\r\n')
477 if filename:
478 try:
479 with open(filename, 'rb') as f:
480 sys.stderr.write('--- Sending file {} ---\n'.format(filename))
481 while True:
482 block = f.read(1024)
483 if not block:
484 break
485 self.serial.write(block)
486 # Wait for output buffer to drain.
487 self.serial.flush()
488 sys.stderr.write('.') # Progress indicator.
489 sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
490 except IOError as e:
491 sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200492 elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
Chris Liechti442bf512015-08-15 01:42:24 +0200493 sys.stderr.write(self.get_help_text())
Chris Liechti7af7c752015-08-12 15:45:19 +0200494 elif c == '\x12': # CTRL+R -> Toggle RTS
Chris Liechti3b454802015-08-26 23:39:59 +0200495 self.serial.rts = not self.serial.rts
496 sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200497 elif c == '\x04': # CTRL+D -> Toggle DTR
Chris Liechti3b454802015-08-26 23:39:59 +0200498 self.serial.dtr = not self.serial.dtr
499 sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200500 elif c == '\x02': # CTRL+B -> toggle BREAK condition
Chris Liechti3b454802015-08-26 23:39:59 +0200501 self.serial.break_condition = not self.serial.break_condition
502 sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200503 elif c == '\x05': # CTRL+E -> toggle local echo
504 self.echo = not self.echo
Chris Liechti442bf512015-08-15 01:42:24 +0200505 sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200506 elif c == '\x06': # CTRL+F -> edit filters
507 sys.stderr.write('\n--- Available Filters:\n')
508 sys.stderr.write('\n'.join(
Chris Liechti397cf412016-02-11 00:11:48 +0100509 '--- {:<10} = {.__doc__}'.format(k, v)
510 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200511 sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
512 with self.console:
513 new_filters = sys.stdin.readline().lower().split()
514 if new_filters:
515 for f in new_filters:
516 if f not in TRANSFORMATIONS:
517 sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
518 break
519 else:
520 self.filters = new_filters
521 self.update_transformations()
522 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
523 elif c == '\x0c': # CTRL+L -> EOL mode
Chris Liechti033f17c2015-08-30 21:28:04 +0200524 modes = list(EOL_TRANSFORMATIONS) # keys
Chris Liechtib3df13e2015-08-25 02:20:09 +0200525 eol = modes.index(self.eol) + 1
526 if eol >= len(modes):
527 eol = 0
528 self.eol = modes[eol]
529 sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
530 self.update_transformations()
531 elif c == '\x01': # CTRL+A -> set encoding
532 sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
533 with self.console:
534 new_encoding = sys.stdin.readline().strip()
535 if new_encoding:
536 try:
537 codecs.lookup(new_encoding)
538 except LookupError:
539 sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
540 else:
541 self.set_rx_encoding(new_encoding)
542 self.set_tx_encoding(new_encoding)
543 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
544 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechti7af7c752015-08-12 15:45:19 +0200545 elif c == '\x09': # CTRL+I -> info
546 self.dump_port_settings()
547 #~ elif c == '\x01': # CTRL+A -> cycle escape mode
548 #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
549 elif c in 'pP': # P -> change port
Chris Liechti269f77b2015-08-24 01:31:42 +0200550 with self.console:
551 try:
Chris Liechti89313c92015-09-01 02:33:13 +0200552 port = ask_for_port()
Chris Liechti269f77b2015-08-24 01:31:42 +0200553 except KeyboardInterrupt:
554 port = None
Chris Liechti7af7c752015-08-12 15:45:19 +0200555 if port and port != self.serial.port:
556 # reader thread needs to be shut down
557 self._stop_reader()
558 # save settings
559 settings = self.serial.getSettingsDict()
560 try:
561 new_serial = serial.serial_for_url(port, do_not_open=True)
562 # restore settings and open
563 new_serial.applySettingsDict(settings)
Chris Liechti89313c92015-09-01 02:33:13 +0200564 new_serial.rts = self.serial.rts
565 new_serial.dtr = self.serial.dtr
Chris Liechti7af7c752015-08-12 15:45:19 +0200566 new_serial.open()
Chris Liechti89313c92015-09-01 02:33:13 +0200567 new_serial.break_condition = self.serial.break_condition
Chris Liechti7af7c752015-08-12 15:45:19 +0200568 except Exception as e:
Chris Liechti442bf512015-08-15 01:42:24 +0200569 sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200570 new_serial.close()
571 else:
572 self.serial.close()
573 self.serial = new_serial
Chris Liechti442bf512015-08-15 01:42:24 +0200574 sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
Chris Liechti7af7c752015-08-12 15:45:19 +0200575 # and restart the reader thread
576 self._start_reader()
577 elif c in 'bB': # B -> change baudrate
578 sys.stderr.write('\n--- Baudrate: ')
579 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200580 with self.console:
581 backup = self.serial.baudrate
582 try:
583 self.serial.baudrate = int(sys.stdin.readline().strip())
584 except ValueError as e:
585 sys.stderr.write('--- ERROR setting baudrate: %s ---\n'.format(e))
586 self.serial.baudrate = backup
587 else:
588 self.dump_port_settings()
Chris Liechti7af7c752015-08-12 15:45:19 +0200589 elif c == '8': # 8 -> change to 8 bits
590 self.serial.bytesize = serial.EIGHTBITS
591 self.dump_port_settings()
592 elif c == '7': # 7 -> change to 8 bits
593 self.serial.bytesize = serial.SEVENBITS
594 self.dump_port_settings()
595 elif c in 'eE': # E -> change to even parity
596 self.serial.parity = serial.PARITY_EVEN
597 self.dump_port_settings()
598 elif c in 'oO': # O -> change to odd parity
599 self.serial.parity = serial.PARITY_ODD
600 self.dump_port_settings()
601 elif c in 'mM': # M -> change to mark parity
602 self.serial.parity = serial.PARITY_MARK
603 self.dump_port_settings()
604 elif c in 'sS': # S -> change to space parity
605 self.serial.parity = serial.PARITY_SPACE
606 self.dump_port_settings()
607 elif c in 'nN': # N -> change to no parity
608 self.serial.parity = serial.PARITY_NONE
609 self.dump_port_settings()
610 elif c == '1': # 1 -> change to 1 stop bits
611 self.serial.stopbits = serial.STOPBITS_ONE
612 self.dump_port_settings()
613 elif c == '2': # 2 -> change to 2 stop bits
614 self.serial.stopbits = serial.STOPBITS_TWO
615 self.dump_port_settings()
616 elif c == '3': # 3 -> change to 1.5 stop bits
617 self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
618 self.dump_port_settings()
619 elif c in 'xX': # X -> change software flow control
620 self.serial.xonxoff = (c == 'X')
621 self.dump_port_settings()
622 elif c in 'rR': # R -> change hardware flow control
623 self.serial.rtscts = (c == 'R')
624 self.dump_port_settings()
625 else:
Chris Liechti442bf512015-08-15 01:42:24 +0200626 sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
627
628 def get_help_text(self):
Chris Liechti55ba7d92015-08-15 16:33:51 +0200629 # help text, starts with blank line!
Chris Liechti442bf512015-08-15 01:42:24 +0200630 return """
631--- pySerial ({version}) - miniterm - help
632---
633--- {exit:8} Exit program
634--- {menu:8} Menu escape key, followed by:
635--- Menu keys:
636--- {menu:7} Send the menu character itself to remote
637--- {exit:7} Send the exit character itself to remote
638--- {info:7} Show info
639--- {upload:7} Upload file (prompt will be shown)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200640--- {repr:7} encoding
641--- {filter:7} edit filters
Chris Liechti442bf512015-08-15 01:42:24 +0200642--- Toggles:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200643--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
644--- {echo:7} echo {eol:7} EOL
Chris Liechti442bf512015-08-15 01:42:24 +0200645---
Chris Liechti55ba7d92015-08-15 16:33:51 +0200646--- Port settings ({menu} followed by the following):
Chris Liechti442bf512015-08-15 01:42:24 +0200647--- p change port
648--- 7 8 set data bits
Chris Liechtib7550bd2015-08-15 04:09:10 +0200649--- N E O S M change parity (None, Even, Odd, Space, Mark)
Chris Liechti442bf512015-08-15 01:42:24 +0200650--- 1 2 3 set stop bits (1, 2, 1.5)
651--- b change baud rate
652--- x X disable/enable software flow control
653--- r R disable/enable hardware flow control
654""".format(
Chris Liechtiba45c522016-02-06 23:53:23 +0100655 version=getattr(serial, 'VERSION', 'unknown version'),
656 exit=key_description(self.exit_character),
657 menu=key_description(self.menu_character),
658 rts=key_description('\x12'),
659 dtr=key_description('\x04'),
660 brk=key_description('\x02'),
661 echo=key_description('\x05'),
662 info=key_description('\x09'),
663 upload=key_description('\x15'),
664 repr=key_description('\x01'),
665 filter=key_description('\x06'),
666 eol=key_description('\x0c'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200667
668
Chris Liechtib3df13e2015-08-25 02:20:09 +0200669# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti55ba7d92015-08-15 16:33:51 +0200670# default args can be used to override when calling main() from an other script
671# e.g to create a miniterm-my-device.py
672def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
Chris Liechtib7550bd2015-08-15 04:09:10 +0200673 import argparse
cliechti6385f2c2005-09-21 19:51:19 +0000674
Chris Liechtib7550bd2015-08-15 04:09:10 +0200675 parser = argparse.ArgumentParser(
Chris Liechti397cf412016-02-11 00:11:48 +0100676 description="Miniterm - A simple terminal program for the serial port.")
cliechti6385f2c2005-09-21 19:51:19 +0000677
Chris Liechti033f17c2015-08-30 21:28:04 +0200678 parser.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100679 "port",
680 nargs='?',
681 help="serial port name ('-' to show port list)",
682 default=default_port)
cliechti5370cee2013-10-13 03:08:19 +0000683
Chris Liechti033f17c2015-08-30 21:28:04 +0200684 parser.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100685 "baudrate",
686 nargs='?',
687 type=int,
688 help="set baud rate, default: %(default)s",
689 default=default_baudrate)
cliechti6385f2c2005-09-21 19:51:19 +0000690
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200691 group = parser.add_argument_group("port settings")
cliechti53edb472009-02-06 21:18:46 +0000692
Chris Liechti033f17c2015-08-30 21:28:04 +0200693 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100694 "--parity",
695 choices=['N', 'E', 'O', 'S', 'M'],
696 type=lambda c: c.upper(),
697 help="set parity, one of {N E O S M}, default: N",
698 default='N')
cliechti53edb472009-02-06 21:18:46 +0000699
Chris Liechti033f17c2015-08-30 21:28:04 +0200700 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100701 "--rtscts",
702 action="store_true",
703 help="enable RTS/CTS flow control (default off)",
704 default=False)
cliechti53edb472009-02-06 21:18:46 +0000705
Chris Liechti033f17c2015-08-30 21:28:04 +0200706 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100707 "--xonxoff",
708 action="store_true",
709 help="enable software flow control (default off)",
710 default=False)
cliechti53edb472009-02-06 21:18:46 +0000711
Chris Liechti033f17c2015-08-30 21:28:04 +0200712 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100713 "--rts",
714 type=int,
715 help="set initial RTS line state (possible values: 0, 1)",
716 default=default_rts)
cliechti5370cee2013-10-13 03:08:19 +0000717
Chris Liechti033f17c2015-08-30 21:28:04 +0200718 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100719 "--dtr",
720 type=int,
721 help="set initial DTR line state (possible values: 0, 1)",
722 default=default_dtr)
cliechti5370cee2013-10-13 03:08:19 +0000723
Chris Liechti00f84282015-12-24 23:40:34 +0100724 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100725 "--ask",
726 action="store_true",
727 help="ask again for port when open fails",
728 default=False)
Chris Liechti00f84282015-12-24 23:40:34 +0100729
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200730 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000731
Chris Liechti033f17c2015-08-30 21:28:04 +0200732 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100733 "-e", "--echo",
734 action="store_true",
735 help="enable local echo (default off)",
736 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000737
Chris Liechti033f17c2015-08-30 21:28:04 +0200738 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100739 "--encoding",
740 dest="serial_port_encoding",
741 metavar="CODEC",
742 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
743 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000744
Chris Liechti033f17c2015-08-30 21:28:04 +0200745 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100746 "-f", "--filter",
747 action="append",
748 metavar="NAME",
749 help="add text transformation",
750 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200751
Chris Liechti033f17c2015-08-30 21:28:04 +0200752 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100753 "--eol",
754 choices=['CR', 'LF', 'CRLF'],
755 type=lambda c: c.upper(),
756 help="end of line mode",
757 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000758
Chris Liechti033f17c2015-08-30 21:28:04 +0200759 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100760 "--raw",
761 action="store_true",
762 help="Do no apply any encodings/transformations",
763 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000764
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200765 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000766
Chris Liechti033f17c2015-08-30 21:28:04 +0200767 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100768 "--exit-char",
769 type=int,
770 metavar='NUM',
771 help="Unicode of special character that is used to exit the application, default: %(default)s",
772 default=0x1d) # GS/CTRL+]
cliechtibf6bb7d2006-03-30 00:28:18 +0000773
Chris Liechti033f17c2015-08-30 21:28:04 +0200774 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100775 "--menu-char",
776 type=int,
777 metavar='NUM',
778 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
779 default=0x14) # Menu: CTRL+T
cliechti9c592b32008-06-16 22:00:14 +0000780
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200781 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000782
Chris Liechti033f17c2015-08-30 21:28:04 +0200783 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100784 "-q", "--quiet",
785 action="store_true",
786 help="suppress non-error messages",
787 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000788
Chris Liechti033f17c2015-08-30 21:28:04 +0200789 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100790 "--develop",
791 action="store_true",
792 help="show Python traceback on error",
793 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000794
Chris Liechtib7550bd2015-08-15 04:09:10 +0200795 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000796
Chris Liechtib7550bd2015-08-15 04:09:10 +0200797 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000798 parser.error('--exit-char can not be the same as --menu-char')
799
Chris Liechtib3df13e2015-08-25 02:20:09 +0200800 if args.filter:
801 if 'help' in args.filter:
802 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200803 sys.stderr.write('\n'.join(
Chris Liechti397cf412016-02-11 00:11:48 +0100804 '{:<10} = {.__doc__}'.format(k, v)
805 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200806 sys.stderr.write('\n')
807 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200808 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200809 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200810 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200811
Chris Liechti00f84282015-12-24 23:40:34 +0100812 while True:
813 # no port given on command line -> ask user now
814 if args.port is None or args.port == '-':
815 try:
816 args.port = ask_for_port()
817 except KeyboardInterrupt:
818 sys.stderr.write('\n')
819 parser.error('user aborted and port is not given')
820 else:
821 if not args.port:
822 parser.error('port is not given')
823 try:
824 serial_instance = serial.serial_for_url(
Chris Liechti397cf412016-02-11 00:11:48 +0100825 args.port,
826 args.baudrate,
827 parity=args.parity,
828 rtscts=args.rtscts,
829 xonxoff=args.xonxoff,
830 timeout=1,
831 do_not_open=True)
Chris Liechti3b454802015-08-26 23:39:59 +0200832
Chris Liechti00f84282015-12-24 23:40:34 +0100833 if args.dtr is not None:
834 if not args.quiet:
835 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
836 serial_instance.dtr = args.dtr
837 if args.rts is not None:
838 if not args.quiet:
839 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
840 serial_instance.rts = args.rts
Chris Liechti3b454802015-08-26 23:39:59 +0200841
Chris Liechti00f84282015-12-24 23:40:34 +0100842 serial_instance.open()
843 except serial.SerialException as e:
844 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
845 if args.develop:
846 raise
847 if not args.ask:
848 sys.exit(1)
849 else:
850 args.port = '-'
851 else:
852 break
cliechti6385f2c2005-09-21 19:51:19 +0000853
Chris Liechti3b454802015-08-26 23:39:59 +0200854 miniterm = Miniterm(
Chris Liechti397cf412016-02-11 00:11:48 +0100855 serial_instance,
856 echo=args.echo,
857 eol=args.eol.lower(),
858 filters=filters)
Chris Liechti3b454802015-08-26 23:39:59 +0200859 miniterm.exit_character = unichr(args.exit_char)
860 miniterm.menu_character = unichr(args.menu_char)
861 miniterm.raw = args.raw
862 miniterm.set_rx_encoding(args.serial_port_encoding)
863 miniterm.set_tx_encoding(args.serial_port_encoding)
864
Chris Liechtib7550bd2015-08-15 04:09:10 +0200865 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200866 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100867 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200868 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100869 key_description(miniterm.exit_character),
870 key_description(miniterm.menu_character),
871 key_description(miniterm.menu_character),
872 key_description('\x08')))
cliechti6fa76fb2009-07-08 23:53:39 +0000873
cliechti6385f2c2005-09-21 19:51:19 +0000874 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000875 try:
876 miniterm.join(True)
877 except KeyboardInterrupt:
878 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200879 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000880 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000881 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000882
cliechti5370cee2013-10-13 03:08:19 +0000883# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000884if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000885 main()