blob: cb24d0d63da7baf72bfb59276ec8d19405c2c3f3 [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 Liechti68340d72015-08-03 14:15:48 +02005# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +02006#
7# SPDX-License-Identifier: BSD-3-Clause
cliechtifc9eb382002-03-05 01:12:29 +00008
Chris Liechtic7a5d4c2015-08-11 23:32:20 +02009import codecs
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020010import os
11import sys
12import threading
cliechti576de252002-02-28 23:54:44 +000013
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020014import serial
Chris Liechti55ba7d92015-08-15 16:33:51 +020015from serial.tools.list_ports import comports
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020016
Chris Liechti68340d72015-08-03 14:15:48 +020017try:
18 raw_input
19except NameError:
20 raw_input = input # in python3 it's "raw"
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020021 unichr = chr
Chris Liechti68340d72015-08-03 14:15:48 +020022
Chris Liechtic0c660a2015-08-25 00:55:51 +020023from . import hexlify_codec
24codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
25
cliechti6c8eb2f2009-07-08 02:10:46 +000026
27def key_description(character):
28 """generate a readable description for a key"""
29 ascii_code = ord(character)
30 if ascii_code < 32:
31 return 'Ctrl+%c' % (ord('@') + ascii_code)
32 else:
33 return repr(character)
34
cliechti91165532011-03-18 02:02:52 +000035
Chris Liechti9a720852015-08-25 00:20:38 +020036# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020037class ConsoleBase(object):
38 def __init__(self):
39 if sys.version_info >= (3, 0):
40 self.byte_output = sys.stdout.buffer
41 else:
42 self.byte_output = sys.stdout
43 self.output = sys.stdout
cliechtif467aa82013-10-13 21:36:49 +000044
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020045 def setup(self):
Chris Liechti1df28272015-08-27 23:37:38 +020046 pass
cliechtif467aa82013-10-13 21:36:49 +000047
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020048 def cleanup(self):
Chris Liechti1df28272015-08-27 23:37:38 +020049 pass
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020050
51 def getkey(self):
52 return None
53
54 def write_bytes(self, s):
55 self.byte_output.write(s)
56 self.byte_output.flush()
57
58 def write(self, s):
59 self.output.write(s)
60 self.output.flush()
61
Chris Liechti269f77b2015-08-24 01:31:42 +020062 # - - - - - - - - - - - - - - - - - - - - - - - -
63 # context manager:
64 # switch terminal temporary to normal mode (e.g. to get user input)
65
66 def __enter__(self):
67 self.cleanup()
68 return self
69
70 def __exit__(self, *args, **kwargs):
71 self.setup()
72
cliechti9c592b32008-06-16 22:00:14 +000073
cliechtifc9eb382002-03-05 01:12:29 +000074if os.name == 'nt':
cliechti576de252002-02-28 23:54:44 +000075 import msvcrt
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020076 import ctypes
Chris Liechti9cc696b2015-08-28 00:54:22 +020077
78 class Out(object):
79 def __init__(self, fd):
80 self.fd = fd
81
82 def flush(self):
83 pass
84
85 def write(self, s):
86 os.write(self.fd, s)
87
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020088 class Console(ConsoleBase):
Chris Liechticbb00b22015-08-13 22:58:49 +020089 def __init__(self):
90 super(Console, self).__init__()
Chris Liechti1df28272015-08-27 23:37:38 +020091 self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
92 self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
Chris Liechticbb00b22015-08-13 22:58:49 +020093 ctypes.windll.kernel32.SetConsoleOutputCP(65001)
94 ctypes.windll.kernel32.SetConsoleCP(65001)
Chris Liechti9cc696b2015-08-28 00:54:22 +020095 self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
96 # the change of the code page is not propagated to Python, manually fix it
97 sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
98 sys.stdout = self.output
Chris Liechticbb00b22015-08-13 22:58:49 +020099
Chris Liechti1df28272015-08-27 23:37:38 +0200100 def __del__(self):
101 ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
102 ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
103
cliechti3a8bf092008-09-17 11:26:53 +0000104 def getkey(self):
cliechti91165532011-03-18 02:02:52 +0000105 while True:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200106 z = msvcrt.getwch()
Chris Liechti3b454802015-08-26 23:39:59 +0200107 if z == u'\r':
108 return u'\n'
109 elif z in u'\x00\x0e': # functions keys, ignore
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200110 msvcrt.getwch()
cliechti9c592b32008-06-16 22:00:14 +0000111 else:
cliechti9c592b32008-06-16 22:00:14 +0000112 return z
cliechti53edb472009-02-06 21:18:46 +0000113
cliechti576de252002-02-28 23:54:44 +0000114elif os.name == 'posix':
Chris Liechtia1d5c6d2015-08-07 14:41:24 +0200115 import atexit
116 import termios
Chris Liechti9cc696b2015-08-28 00:54:22 +0200117
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200118 class Console(ConsoleBase):
cliechti9c592b32008-06-16 22:00:14 +0000119 def __init__(self):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200120 super(Console, self).__init__()
cliechti9c592b32008-06-16 22:00:14 +0000121 self.fd = sys.stdin.fileno()
Chris Liechti4d989c22015-08-24 00:24:49 +0200122 self.old = termios.tcgetattr(self.fd)
Chris Liechti89eb2472015-08-08 17:06:25 +0200123 atexit.register(self.cleanup)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200124 if sys.version_info < (3, 0):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200125 self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
126 else:
127 self.enc_stdin = sys.stdin
cliechti9c592b32008-06-16 22:00:14 +0000128
129 def setup(self):
cliechti9c592b32008-06-16 22:00:14 +0000130 new = termios.tcgetattr(self.fd)
131 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
132 new[6][termios.VMIN] = 1
133 new[6][termios.VTIME] = 0
134 termios.tcsetattr(self.fd, termios.TCSANOW, new)
cliechti53edb472009-02-06 21:18:46 +0000135
cliechti9c592b32008-06-16 22:00:14 +0000136 def getkey(self):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200137 c = self.enc_stdin.read(1)
Chris Liechti3b454802015-08-26 23:39:59 +0200138 if c == u'\x7f':
139 c = u'\b' # map the BS key (which yields DEL) to backspace
Chris Liechti9a720852015-08-25 00:20:38 +0200140 return c
cliechti53edb472009-02-06 21:18:46 +0000141
cliechti9c592b32008-06-16 22:00:14 +0000142 def cleanup(self):
Chris Liechti4d989c22015-08-24 00:24:49 +0200143 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
cliechti9c592b32008-06-16 22:00:14 +0000144
cliechti576de252002-02-28 23:54:44 +0000145else:
cliechti8c2ea842011-03-18 01:51:46 +0000146 raise NotImplementedError("Sorry no implementation for your platform (%s) available." % sys.platform)
cliechti576de252002-02-28 23:54:44 +0000147
cliechti6fa76fb2009-07-08 23:53:39 +0000148
Chris Liechti9a720852015-08-25 00:20:38 +0200149# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200150
151class Transform(object):
Chris Liechticbb00b22015-08-13 22:58:49 +0200152 """do-nothing: forward all data unchanged"""
Chris Liechtid698af72015-08-24 20:24:55 +0200153 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200154 """text received from serial port"""
155 return text
156
Chris Liechtid698af72015-08-24 20:24:55 +0200157 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200158 """text to be sent to serial port"""
159 return text
160
161 def echo(self, text):
162 """text to be sent but displayed on console"""
163 return text
164
Chris Liechti442bf512015-08-15 01:42:24 +0200165
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200166class CRLF(Transform):
167 """ENTER sends CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200168
Chris Liechtid698af72015-08-24 20:24:55 +0200169 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200170 return text.replace('\n', '\r\n')
171
Chris Liechti442bf512015-08-15 01:42:24 +0200172
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200173class CR(Transform):
174 """ENTER sends CR"""
Chris Liechtid698af72015-08-24 20:24:55 +0200175
176 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200177 return text.replace('\r', '\n')
178
Chris Liechtid698af72015-08-24 20:24:55 +0200179 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200180 return text.replace('\n', '\r')
181
Chris Liechti442bf512015-08-15 01:42:24 +0200182
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200183class LF(Transform):
184 """ENTER sends LF"""
185
186
187class NoTerminal(Transform):
188 """remove typical terminal control codes from input"""
Chris Liechti9a720852015-08-25 00:20:38 +0200189
190 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
191 REPLACEMENT_MAP.update({
Chris Liechti033f17c2015-08-30 21:28:04 +0200192 0x7F: 0x2421, # DEL
193 0x9B: 0x2425, # CSI
Chris Liechti9a720852015-08-25 00:20:38 +0200194 })
195
Chris Liechtid698af72015-08-24 20:24:55 +0200196 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200197 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200198
Chris Liechtid698af72015-08-24 20:24:55 +0200199 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200200
201
Chris Liechti9a720852015-08-25 00:20:38 +0200202class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200203 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200204
Chris Liechti9a720852015-08-25 00:20:38 +0200205 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
206 REPLACEMENT_MAP.update({
Chris Liechti033f17c2015-08-30 21:28:04 +0200207 32: 0x2423, # visual space
208 0x7F: 0x2421, # DEL
209 0x9B: 0x2425, # CSI
Chris Liechti9a720852015-08-25 00:20:38 +0200210 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200211
212
213class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200214 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200215
Chris Liechtid698af72015-08-24 20:24:55 +0200216 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200217 r = []
218 for t in text:
Chris Liechti7e9cfd42015-08-12 15:28:19 +0200219 if ' ' <= t < '\x7f' or t in '\r\n\b\t':
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200220 r.append(t)
Chris Liechtid698af72015-08-24 20:24:55 +0200221 elif t < ' ':
222 r.append(unichr(0x2400 + ord(t)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200223 else:
224 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(t)))
225 r.append(' ')
226 return ''.join(r)
227
Chris Liechtid698af72015-08-24 20:24:55 +0200228 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200229
230
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200231class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200232 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200233
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200234 def __init__(self):
235 # XXX make it configurable, use colorama?
236 self.input_color = '\x1b[37m'
237 self.echo_color = '\x1b[31m'
238
Chris Liechtid698af72015-08-24 20:24:55 +0200239 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200240 return self.input_color + text
241
242 def echo(self, text):
243 return self.echo_color + text
244
Chris Liechti442bf512015-08-15 01:42:24 +0200245
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200246class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200247 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200248
Chris Liechtid698af72015-08-24 20:24:55 +0200249 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200250 sys.stderr.write(' [RX:{}] '.format(repr(text)))
251 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200252 return text
253
Chris Liechtid698af72015-08-24 20:24:55 +0200254 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200255 sys.stderr.write(' [TX:{}] '.format(repr(text)))
256 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200257 return text
258
Chris Liechti442bf512015-08-15 01:42:24 +0200259
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200260# other ideas:
261# - add date/time for each newline
262# - insert newline after: a) timeout b) packet end character
263
Chris Liechtib3df13e2015-08-25 02:20:09 +0200264EOL_TRANSFORMATIONS = {
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200265 'crlf': CRLF,
266 'cr': CR,
267 'lf': LF,
Chris Liechtib3df13e2015-08-25 02:20:09 +0200268 }
269
270TRANSFORMATIONS = {
Chris Liechticbb00b22015-08-13 22:58:49 +0200271 'direct': Transform, # no transformation
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200272 'default': NoTerminal,
273 'nocontrol': NoControls,
274 'printable': Printable,
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200275 'colorize': Colorize,
276 'debug': DebugIO,
277 }
278
279
Chris Liechti033f17c2015-08-30 21:28:04 +0200280# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200281def ask_for_port():
282 """\
283 Show a list of ports and ask the user for a choice. To make selection
284 easier on systems with long device names, also allow the input of an
285 index.
286 """
287 sys.stderr.write('\n--- Available ports:\n')
288 ports = []
289 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
290 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
291 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
292 ports.append(port)
293 while True:
294 port = raw_input('--- Enter port index or full name: ')
295 try:
296 index = int(port) - 1
297 if not 0 <= index < len(ports):
298 sys.stderr.write('--- Invalid index!\n')
299 continue
300 except ValueError:
301 pass
302 else:
303 port = ports[index]
304 return port
cliechti1351dde2012-04-12 16:47:47 +0000305
306
cliechti8c2ea842011-03-18 01:51:46 +0000307class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200308 """\
309 Terminal application. Copy data from serial port to console and vice versa.
310 Handle special keys from the console to show menu etc.
311 """
312
Chris Liechti3b454802015-08-26 23:39:59 +0200313 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200314 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200315 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000316 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200317 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200318 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200319 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200320 self.eol = eol
321 self.filters = filters
322 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200323 self.exit_character = 0x1d # GS/CTRL+]
324 self.menu_character = 0x14 # Menu: CTRL+T
cliechti576de252002-02-28 23:54:44 +0000325
cliechti8c2ea842011-03-18 01:51:46 +0000326 def _start_reader(self):
327 """Start reader thread"""
328 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000329 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200330 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
331 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000332 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000333
334 def _stop_reader(self):
335 """Stop reader thread only, wait for clean exit of thread"""
336 self._reader_alive = False
337 self.receiver_thread.join()
338
cliechti8c2ea842011-03-18 01:51:46 +0000339 def start(self):
340 self.alive = True
341 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000342 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200343 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
344 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000345 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200346 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000347
cliechti6385f2c2005-09-21 19:51:19 +0000348 def stop(self):
349 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000350
cliechtibf6bb7d2006-03-30 00:28:18 +0000351 def join(self, transmit_only=False):
cliechti6385f2c2005-09-21 19:51:19 +0000352 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000353 if not transmit_only:
354 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000355
Chris Liechtib3df13e2015-08-25 02:20:09 +0200356 def update_transformations(self):
357 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] for f in self.filters]
358 self.tx_transformations = [t() for t in transformations]
359 self.rx_transformations = list(reversed(self.tx_transformations))
360
Chris Liechtid698af72015-08-24 20:24:55 +0200361 def set_rx_encoding(self, encoding, errors='replace'):
362 self.input_encoding = encoding
363 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
364
365 def set_tx_encoding(self, encoding, errors='replace'):
366 self.output_encoding = encoding
367 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
368
cliechti6c8eb2f2009-07-08 02:10:46 +0000369 def dump_port_settings(self):
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200370 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
371 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200372 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200373 ('active' if self.serial.rts else 'inactive'),
374 ('active' if self.serial.dtr else 'inactive'),
375 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000376 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200377 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200378 ('active' if self.serial.cts else 'inactive'),
379 ('active' if self.serial.dsr else 'inactive'),
380 ('active' if self.serial.ri else 'inactive'),
381 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000382 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200383 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000384 # yet received. ignore this error.
385 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200386 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
387 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200388 #~ sys.stderr.write('--- data escaping: %s linefeed: %s\n' % (
389 #~ REPR_MODES[self.repr_mode],
390 #~ LF_MODES[self.convert_outgoing]))
Chris Liechti442bf512015-08-15 01:42:24 +0200391 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
392 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200393 sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
394 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
cliechti6c8eb2f2009-07-08 02:10:46 +0000395
cliechti6385f2c2005-09-21 19:51:19 +0000396 def reader(self):
397 """loop and copy serial->console"""
cliechti6963b262010-01-02 03:01:21 +0000398 try:
cliechti8c2ea842011-03-18 01:51:46 +0000399 while self.alive and self._reader_alive:
Chris Liechti188cf592015-08-22 00:28:19 +0200400 # read all that is there or wait for one byte
Chris Liechti3b454802015-08-26 23:39:59 +0200401 data = self.serial.read(self.serial.in_waiting or 1)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200402 if data:
403 if self.raw:
404 self.console.write_bytes(data)
cliechti6963b262010-01-02 03:01:21 +0000405 else:
Chris Liechtid698af72015-08-24 20:24:55 +0200406 text = self.rx_decoder.decode(data)
Chris Liechtie1384382015-08-15 17:06:05 +0200407 for transformation in self.rx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200408 text = transformation.rx(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200409 self.console.write(text)
Chris Liechti033f17c2015-08-30 21:28:04 +0200410 except serial.SerialException:
cliechti6963b262010-01-02 03:01:21 +0000411 self.alive = False
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200412 # XXX would be nice if the writer could be interrupted at this
413 # point... to exit completely
cliechti6963b262010-01-02 03:01:21 +0000414 raise
cliechti576de252002-02-28 23:54:44 +0000415
cliechti6385f2c2005-09-21 19:51:19 +0000416 def writer(self):
cliechti8c2ea842011-03-18 01:51:46 +0000417 """\
Chris Liechti442bf512015-08-15 01:42:24 +0200418 Loop and copy console->serial until self.exit_character character is
419 found. When self.menu_character is found, interpret the next key
cliechti8c2ea842011-03-18 01:51:46 +0000420 locally.
cliechti6c8eb2f2009-07-08 02:10:46 +0000421 """
422 menu_active = False
423 try:
424 while self.alive:
425 try:
Chris Liechti89eb2472015-08-08 17:06:25 +0200426 c = self.console.getkey()
cliechti6c8eb2f2009-07-08 02:10:46 +0000427 except KeyboardInterrupt:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200428 c = '\x03'
cliechti6c8eb2f2009-07-08 02:10:46 +0000429 if menu_active:
Chris Liechti7af7c752015-08-12 15:45:19 +0200430 self.handle_menu_key(c)
cliechti6c8eb2f2009-07-08 02:10:46 +0000431 menu_active = False
Chris Liechti442bf512015-08-15 01:42:24 +0200432 elif c == self.menu_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200433 menu_active = True # next char will be for menu
Chris Liechti442bf512015-08-15 01:42:24 +0200434 elif c == self.exit_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200435 self.stop() # exit app
436 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000437 else:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200438 #~ if self.raw:
439 text = c
Chris Liechtie1384382015-08-15 17:06:05 +0200440 for transformation in self.tx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200441 text = transformation.tx(text)
Chris Liechtid698af72015-08-24 20:24:55 +0200442 self.serial.write(self.tx_encoder.encode(text))
cliechti6c8eb2f2009-07-08 02:10:46 +0000443 if self.echo:
Chris Liechti3b454802015-08-26 23:39:59 +0200444 echo_text = c
445 for transformation in self.tx_transformations:
446 echo_text = transformation.echo(echo_text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200447 self.console.write(echo_text)
cliechti6c8eb2f2009-07-08 02:10:46 +0000448 except:
449 self.alive = False
450 raise
cliechti6385f2c2005-09-21 19:51:19 +0000451
Chris Liechti7af7c752015-08-12 15:45:19 +0200452 def handle_menu_key(self, c):
453 """Implement a simple menu / settings"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200454 if c == self.menu_character or c == self.exit_character:
455 # Menu/exit character again -> send itself
Chris Liechtid698af72015-08-24 20:24:55 +0200456 self.serial.write(self.tx_encoder.encode(c))
Chris Liechti7af7c752015-08-12 15:45:19 +0200457 if self.echo:
458 self.console.write(c)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200459 elif c == '\x15': # CTRL+U -> upload file
Chris Liechti7af7c752015-08-12 15:45:19 +0200460 sys.stderr.write('\n--- File to upload: ')
461 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200462 with self.console:
463 filename = sys.stdin.readline().rstrip('\r\n')
464 if filename:
465 try:
466 with open(filename, 'rb') as f:
467 sys.stderr.write('--- Sending file {} ---\n'.format(filename))
468 while True:
469 block = f.read(1024)
470 if not block:
471 break
472 self.serial.write(block)
473 # Wait for output buffer to drain.
474 self.serial.flush()
475 sys.stderr.write('.') # Progress indicator.
476 sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
477 except IOError as e:
478 sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200479 elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
Chris Liechti442bf512015-08-15 01:42:24 +0200480 sys.stderr.write(self.get_help_text())
Chris Liechti7af7c752015-08-12 15:45:19 +0200481 elif c == '\x12': # CTRL+R -> Toggle RTS
Chris Liechti3b454802015-08-26 23:39:59 +0200482 self.serial.rts = not self.serial.rts
483 sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200484 elif c == '\x04': # CTRL+D -> Toggle DTR
Chris Liechti3b454802015-08-26 23:39:59 +0200485 self.serial.dtr = not self.serial.dtr
486 sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200487 elif c == '\x02': # CTRL+B -> toggle BREAK condition
Chris Liechti3b454802015-08-26 23:39:59 +0200488 self.serial.break_condition = not self.serial.break_condition
489 sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200490 elif c == '\x05': # CTRL+E -> toggle local echo
491 self.echo = not self.echo
Chris Liechti442bf512015-08-15 01:42:24 +0200492 sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200493 elif c == '\x06': # CTRL+F -> edit filters
494 sys.stderr.write('\n--- Available Filters:\n')
495 sys.stderr.write('\n'.join(
496 '--- {:<10} = {.__doc__}'.format(k, v)
497 for k, v in sorted(TRANSFORMATIONS.items())))
498 sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
499 with self.console:
500 new_filters = sys.stdin.readline().lower().split()
501 if new_filters:
502 for f in new_filters:
503 if f not in TRANSFORMATIONS:
504 sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
505 break
506 else:
507 self.filters = new_filters
508 self.update_transformations()
509 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
510 elif c == '\x0c': # CTRL+L -> EOL mode
Chris Liechti033f17c2015-08-30 21:28:04 +0200511 modes = list(EOL_TRANSFORMATIONS) # keys
Chris Liechtib3df13e2015-08-25 02:20:09 +0200512 eol = modes.index(self.eol) + 1
513 if eol >= len(modes):
514 eol = 0
515 self.eol = modes[eol]
516 sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
517 self.update_transformations()
518 elif c == '\x01': # CTRL+A -> set encoding
519 sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
520 with self.console:
521 new_encoding = sys.stdin.readline().strip()
522 if new_encoding:
523 try:
524 codecs.lookup(new_encoding)
525 except LookupError:
526 sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
527 else:
528 self.set_rx_encoding(new_encoding)
529 self.set_tx_encoding(new_encoding)
530 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
531 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechti7af7c752015-08-12 15:45:19 +0200532 elif c == '\x09': # CTRL+I -> info
533 self.dump_port_settings()
534 #~ elif c == '\x01': # CTRL+A -> cycle escape mode
535 #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
536 elif c in 'pP': # P -> change port
Chris Liechti269f77b2015-08-24 01:31:42 +0200537 with self.console:
538 try:
Chris Liechti89313c92015-09-01 02:33:13 +0200539 port = ask_for_port()
Chris Liechti269f77b2015-08-24 01:31:42 +0200540 except KeyboardInterrupt:
541 port = None
Chris Liechti7af7c752015-08-12 15:45:19 +0200542 if port and port != self.serial.port:
543 # reader thread needs to be shut down
544 self._stop_reader()
545 # save settings
546 settings = self.serial.getSettingsDict()
547 try:
548 new_serial = serial.serial_for_url(port, do_not_open=True)
549 # restore settings and open
550 new_serial.applySettingsDict(settings)
Chris Liechti89313c92015-09-01 02:33:13 +0200551 new_serial.rts = self.serial.rts
552 new_serial.dtr = self.serial.dtr
Chris Liechti7af7c752015-08-12 15:45:19 +0200553 new_serial.open()
Chris Liechti89313c92015-09-01 02:33:13 +0200554 new_serial.break_condition = self.serial.break_condition
Chris Liechti7af7c752015-08-12 15:45:19 +0200555 except Exception as e:
Chris Liechti442bf512015-08-15 01:42:24 +0200556 sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200557 new_serial.close()
558 else:
559 self.serial.close()
560 self.serial = new_serial
Chris Liechti442bf512015-08-15 01:42:24 +0200561 sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
Chris Liechti7af7c752015-08-12 15:45:19 +0200562 # and restart the reader thread
563 self._start_reader()
564 elif c in 'bB': # B -> change baudrate
565 sys.stderr.write('\n--- Baudrate: ')
566 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200567 with self.console:
568 backup = self.serial.baudrate
569 try:
570 self.serial.baudrate = int(sys.stdin.readline().strip())
571 except ValueError as e:
572 sys.stderr.write('--- ERROR setting baudrate: %s ---\n'.format(e))
573 self.serial.baudrate = backup
574 else:
575 self.dump_port_settings()
Chris Liechti7af7c752015-08-12 15:45:19 +0200576 elif c == '8': # 8 -> change to 8 bits
577 self.serial.bytesize = serial.EIGHTBITS
578 self.dump_port_settings()
579 elif c == '7': # 7 -> change to 8 bits
580 self.serial.bytesize = serial.SEVENBITS
581 self.dump_port_settings()
582 elif c in 'eE': # E -> change to even parity
583 self.serial.parity = serial.PARITY_EVEN
584 self.dump_port_settings()
585 elif c in 'oO': # O -> change to odd parity
586 self.serial.parity = serial.PARITY_ODD
587 self.dump_port_settings()
588 elif c in 'mM': # M -> change to mark parity
589 self.serial.parity = serial.PARITY_MARK
590 self.dump_port_settings()
591 elif c in 'sS': # S -> change to space parity
592 self.serial.parity = serial.PARITY_SPACE
593 self.dump_port_settings()
594 elif c in 'nN': # N -> change to no parity
595 self.serial.parity = serial.PARITY_NONE
596 self.dump_port_settings()
597 elif c == '1': # 1 -> change to 1 stop bits
598 self.serial.stopbits = serial.STOPBITS_ONE
599 self.dump_port_settings()
600 elif c == '2': # 2 -> change to 2 stop bits
601 self.serial.stopbits = serial.STOPBITS_TWO
602 self.dump_port_settings()
603 elif c == '3': # 3 -> change to 1.5 stop bits
604 self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
605 self.dump_port_settings()
606 elif c in 'xX': # X -> change software flow control
607 self.serial.xonxoff = (c == 'X')
608 self.dump_port_settings()
609 elif c in 'rR': # R -> change hardware flow control
610 self.serial.rtscts = (c == 'R')
611 self.dump_port_settings()
612 else:
Chris Liechti442bf512015-08-15 01:42:24 +0200613 sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
614
615 def get_help_text(self):
Chris Liechti55ba7d92015-08-15 16:33:51 +0200616 # help text, starts with blank line!
Chris Liechti442bf512015-08-15 01:42:24 +0200617 return """
618--- pySerial ({version}) - miniterm - help
619---
620--- {exit:8} Exit program
621--- {menu:8} Menu escape key, followed by:
622--- Menu keys:
623--- {menu:7} Send the menu character itself to remote
624--- {exit:7} Send the exit character itself to remote
625--- {info:7} Show info
626--- {upload:7} Upload file (prompt will be shown)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200627--- {repr:7} encoding
628--- {filter:7} edit filters
Chris Liechti442bf512015-08-15 01:42:24 +0200629--- Toggles:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200630--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
631--- {echo:7} echo {eol:7} EOL
Chris Liechti442bf512015-08-15 01:42:24 +0200632---
Chris Liechti55ba7d92015-08-15 16:33:51 +0200633--- Port settings ({menu} followed by the following):
Chris Liechti442bf512015-08-15 01:42:24 +0200634--- p change port
635--- 7 8 set data bits
Chris Liechtib7550bd2015-08-15 04:09:10 +0200636--- N E O S M change parity (None, Even, Odd, Space, Mark)
Chris Liechti442bf512015-08-15 01:42:24 +0200637--- 1 2 3 set stop bits (1, 2, 1.5)
638--- b change baud rate
639--- x X disable/enable software flow control
640--- r R disable/enable hardware flow control
641""".format(
642 version=getattr(serial, 'VERSION', 'unknown version'),
643 exit=key_description(self.exit_character),
644 menu=key_description(self.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200645 rts=key_description('\x12'),
646 dtr=key_description('\x04'),
647 brk=key_description('\x02'),
648 echo=key_description('\x05'),
649 info=key_description('\x09'),
650 upload=key_description('\x15'),
Chris Liechtib3df13e2015-08-25 02:20:09 +0200651 repr=key_description('\x01'),
652 filter=key_description('\x06'),
653 eol=key_description('\x0c'),
Chris Liechti442bf512015-08-15 01:42:24 +0200654 )
Chris Liechti7af7c752015-08-12 15:45:19 +0200655
656
Chris Liechtib3df13e2015-08-25 02:20:09 +0200657# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti55ba7d92015-08-15 16:33:51 +0200658# default args can be used to override when calling main() from an other script
659# e.g to create a miniterm-my-device.py
660def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
Chris Liechtib7550bd2015-08-15 04:09:10 +0200661 import argparse
cliechti6385f2c2005-09-21 19:51:19 +0000662
Chris Liechtib7550bd2015-08-15 04:09:10 +0200663 parser = argparse.ArgumentParser(
664 description="Miniterm - A simple terminal program for the serial port.")
cliechti6385f2c2005-09-21 19:51:19 +0000665
Chris Liechti033f17c2015-08-30 21:28:04 +0200666 parser.add_argument(
667 "port",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200668 nargs='?',
Chris Liechti7cf82762015-09-01 02:56:04 +0200669 help="serial port name ('-' to show port list)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200670 default=default_port)
cliechti5370cee2013-10-13 03:08:19 +0000671
Chris Liechti033f17c2015-08-30 21:28:04 +0200672 parser.add_argument(
673 "baudrate",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200674 nargs='?',
675 type=int,
676 help="set baud rate, default: %(default)s",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200677 default=default_baudrate)
cliechti6385f2c2005-09-21 19:51:19 +0000678
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200679 group = parser.add_argument_group("port settings")
cliechti53edb472009-02-06 21:18:46 +0000680
Chris Liechti033f17c2015-08-30 21:28:04 +0200681 group.add_argument(
682 "--parity",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200683 choices=['N', 'E', 'O', 'S', 'M'],
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200684 type=lambda c: c.upper(),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200685 help="set parity, one of {N E O S M}, default: N",
686 default='N')
cliechti53edb472009-02-06 21:18:46 +0000687
Chris Liechti033f17c2015-08-30 21:28:04 +0200688 group.add_argument(
689 "--rtscts",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200690 action="store_true",
691 help="enable RTS/CTS flow control (default off)",
692 default=False)
cliechti53edb472009-02-06 21:18:46 +0000693
Chris Liechti033f17c2015-08-30 21:28:04 +0200694 group.add_argument(
695 "--xonxoff",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200696 action="store_true",
697 help="enable software flow control (default off)",
698 default=False)
cliechti53edb472009-02-06 21:18:46 +0000699
Chris Liechti033f17c2015-08-30 21:28:04 +0200700 group.add_argument(
701 "--rts",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200702 type=int,
703 help="set initial RTS line state (possible values: 0, 1)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200704 default=default_rts)
cliechti5370cee2013-10-13 03:08:19 +0000705
Chris Liechti033f17c2015-08-30 21:28:04 +0200706 group.add_argument(
707 "--dtr",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200708 type=int,
709 help="set initial DTR line state (possible values: 0, 1)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200710 default=default_dtr)
cliechti5370cee2013-10-13 03:08:19 +0000711
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200712 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000713
Chris Liechti033f17c2015-08-30 21:28:04 +0200714 group.add_argument(
715 "-e", "--echo",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200716 action="store_true",
717 help="enable local echo (default off)",
718 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000719
Chris Liechti033f17c2015-08-30 21:28:04 +0200720 group.add_argument(
721 "--encoding",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200722 dest="serial_port_encoding",
723 metavar="CODEC",
Chris Liechtia7e7b692015-08-25 21:10:28 +0200724 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200725 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000726
Chris Liechti033f17c2015-08-30 21:28:04 +0200727 group.add_argument(
728 "-f", "--filter",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200729 action="append",
730 metavar="NAME",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200731 help="add text transformation",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200732 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200733
Chris Liechti033f17c2015-08-30 21:28:04 +0200734 group.add_argument(
735 "--eol",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200736 choices=['CR', 'LF', 'CRLF'],
737 type=lambda c: c.upper(),
738 help="end of line mode",
739 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000740
Chris Liechti033f17c2015-08-30 21:28:04 +0200741 group.add_argument(
742 "--raw",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200743 action="store_true",
744 help="Do no apply any encodings/transformations",
745 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000746
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200747 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000748
Chris Liechti033f17c2015-08-30 21:28:04 +0200749 group.add_argument(
750 "--exit-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200751 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200752 metavar='NUM',
753 help="Unicode of special character that is used to exit the application, default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200754 default=0x1d # GS/CTRL+]
755 )
cliechtibf6bb7d2006-03-30 00:28:18 +0000756
Chris Liechti033f17c2015-08-30 21:28:04 +0200757 group.add_argument(
758 "--menu-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200759 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200760 metavar='NUM',
761 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200762 default=0x14 # Menu: CTRL+T
763 )
cliechti9c592b32008-06-16 22:00:14 +0000764
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200765 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000766
Chris Liechti033f17c2015-08-30 21:28:04 +0200767 group.add_argument(
768 "-q", "--quiet",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200769 action="store_true",
770 help="suppress non-error messages",
771 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000772
Chris Liechti033f17c2015-08-30 21:28:04 +0200773 group.add_argument(
774 "--develop",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200775 action="store_true",
776 help="show Python traceback on error",
777 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000778
Chris Liechtib7550bd2015-08-15 04:09:10 +0200779 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000780
Chris Liechtib7550bd2015-08-15 04:09:10 +0200781 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000782 parser.error('--exit-char can not be the same as --menu-char')
783
Chris Liechtib7550bd2015-08-15 04:09:10 +0200784 # no port given on command line -> ask user now
Chris Liechti7cf82762015-09-01 02:56:04 +0200785 if args.port is None or args.port == '-':
Chris Liechti89313c92015-09-01 02:33:13 +0200786 try:
787 args.port = ask_for_port()
788 except KeyboardInterrupt:
789 sys.stderr.write('\n')
790 parser.error('user aborted and port is not given')
791 else:
792 if not args.port:
793 parser.error('port is not given')
cliechti53edb472009-02-06 21:18:46 +0000794
Chris Liechtib3df13e2015-08-25 02:20:09 +0200795 if args.filter:
796 if 'help' in args.filter:
797 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200798 sys.stderr.write('\n'.join(
Chris Liechtib3df13e2015-08-25 02:20:09 +0200799 '{:<10} = {.__doc__}'.format(k, v)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200800 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200801 sys.stderr.write('\n')
802 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200803 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200804 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200805 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200806
cliechti6385f2c2005-09-21 19:51:19 +0000807 try:
Chris Liechti3b454802015-08-26 23:39:59 +0200808 serial_instance = serial.serial_for_url(
Chris Liechtib7550bd2015-08-15 04:09:10 +0200809 args.port,
810 args.baudrate,
Chris Liechti3b454802015-08-26 23:39:59 +0200811 parity=args.parity,
Chris Liechtib7550bd2015-08-15 04:09:10 +0200812 rtscts=args.rtscts,
813 xonxoff=args.xonxoff,
Chris Liechti3b454802015-08-26 23:39:59 +0200814 timeout=1,
815 do_not_open=True)
816
817 if args.dtr is not None:
818 if not args.quiet:
819 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
820 serial_instance.dtr = args.dtr
821 if args.rts is not None:
822 if not args.quiet:
823 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
824 serial_instance.rts = args.rts
825
826 serial_instance.open()
Chris Liechti68340d72015-08-03 14:15:48 +0200827 except serial.SerialException as e:
Chris Liechtiaccd2012015-08-17 03:09:23 +0200828 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200829 if args.develop:
Chris Liechti91090912015-08-05 02:36:14 +0200830 raise
cliechti6385f2c2005-09-21 19:51:19 +0000831 sys.exit(1)
832
Chris Liechti3b454802015-08-26 23:39:59 +0200833 miniterm = Miniterm(
834 serial_instance,
835 echo=args.echo,
836 eol=args.eol.lower(),
837 filters=filters)
838 miniterm.exit_character = unichr(args.exit_char)
839 miniterm.menu_character = unichr(args.menu_char)
840 miniterm.raw = args.raw
841 miniterm.set_rx_encoding(args.serial_port_encoding)
842 miniterm.set_tx_encoding(args.serial_port_encoding)
843
Chris Liechtib7550bd2015-08-15 04:09:10 +0200844 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200845 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
846 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200847 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti442bf512015-08-15 01:42:24 +0200848 key_description(miniterm.exit_character),
849 key_description(miniterm.menu_character),
850 key_description(miniterm.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200851 key_description('\x08'),
Chris Liechti442bf512015-08-15 01:42:24 +0200852 ))
cliechti6fa76fb2009-07-08 23:53:39 +0000853
cliechti6385f2c2005-09-21 19:51:19 +0000854 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000855 try:
856 miniterm.join(True)
857 except KeyboardInterrupt:
858 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200859 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000860 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000861 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000862
cliechti5370cee2013-10-13 03:08:19 +0000863# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000864if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000865 main()