blob: 05d3c8a2416f42958321161d8fac987a6167f62f [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 Liechti168704f2015-09-30 16:50:29 +020016from serial.tools import hexlify_codec
17
18codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020019
Chris Liechti68340d72015-08-03 14:15:48 +020020try:
21 raw_input
22except NameError:
23 raw_input = input # in python3 it's "raw"
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020024 unichr = chr
Chris Liechti68340d72015-08-03 14:15:48 +020025
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 Liechti168704f2015-09-30 16:50:29 +020099 self.output.encoding = 'UTF-8' # needed for input
Chris Liechticbb00b22015-08-13 22:58:49 +0200100
Chris Liechti1df28272015-08-27 23:37:38 +0200101 def __del__(self):
102 ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
103 ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
104
cliechti3a8bf092008-09-17 11:26:53 +0000105 def getkey(self):
cliechti91165532011-03-18 02:02:52 +0000106 while True:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200107 z = msvcrt.getwch()
Chris Liechti9f398812015-09-13 18:50:44 +0200108 if z == unichr(13):
109 return unichr(10)
110 elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200111 msvcrt.getwch()
cliechti9c592b32008-06-16 22:00:14 +0000112 else:
cliechti9c592b32008-06-16 22:00:14 +0000113 return z
cliechti53edb472009-02-06 21:18:46 +0000114
cliechti576de252002-02-28 23:54:44 +0000115elif os.name == 'posix':
Chris Liechtia1d5c6d2015-08-07 14:41:24 +0200116 import atexit
117 import termios
Chris Liechti9cc696b2015-08-28 00:54:22 +0200118
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200119 class Console(ConsoleBase):
cliechti9c592b32008-06-16 22:00:14 +0000120 def __init__(self):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200121 super(Console, self).__init__()
cliechti9c592b32008-06-16 22:00:14 +0000122 self.fd = sys.stdin.fileno()
Chris Liechti4d989c22015-08-24 00:24:49 +0200123 self.old = termios.tcgetattr(self.fd)
Chris Liechti89eb2472015-08-08 17:06:25 +0200124 atexit.register(self.cleanup)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200125 if sys.version_info < (3, 0):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200126 self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
127 else:
128 self.enc_stdin = sys.stdin
cliechti9c592b32008-06-16 22:00:14 +0000129
130 def setup(self):
cliechti9c592b32008-06-16 22:00:14 +0000131 new = termios.tcgetattr(self.fd)
132 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
133 new[6][termios.VMIN] = 1
134 new[6][termios.VTIME] = 0
135 termios.tcsetattr(self.fd, termios.TCSANOW, new)
cliechti53edb472009-02-06 21:18:46 +0000136
cliechti9c592b32008-06-16 22:00:14 +0000137 def getkey(self):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200138 c = self.enc_stdin.read(1)
Chris Liechti9f398812015-09-13 18:50:44 +0200139 if c == unichr(0x7f):
140 c = unichr(8) # map the BS key (which yields DEL) to backspace
Chris Liechti9a720852015-08-25 00:20:38 +0200141 return c
cliechti53edb472009-02-06 21:18:46 +0000142
cliechti9c592b32008-06-16 22:00:14 +0000143 def cleanup(self):
Chris Liechti4d989c22015-08-24 00:24:49 +0200144 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
cliechti9c592b32008-06-16 22:00:14 +0000145
cliechti576de252002-02-28 23:54:44 +0000146else:
cliechti8c2ea842011-03-18 01:51:46 +0000147 raise NotImplementedError("Sorry no implementation for your platform (%s) available." % sys.platform)
cliechti576de252002-02-28 23:54:44 +0000148
cliechti6fa76fb2009-07-08 23:53:39 +0000149
Chris Liechti9a720852015-08-25 00:20:38 +0200150# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200151
152class Transform(object):
Chris Liechticbb00b22015-08-13 22:58:49 +0200153 """do-nothing: forward all data unchanged"""
Chris Liechtid698af72015-08-24 20:24:55 +0200154 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200155 """text received from serial port"""
156 return text
157
Chris Liechtid698af72015-08-24 20:24:55 +0200158 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200159 """text to be sent to serial port"""
160 return text
161
162 def echo(self, text):
163 """text to be sent but displayed on console"""
164 return text
165
Chris Liechti442bf512015-08-15 01:42:24 +0200166
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200167class CRLF(Transform):
168 """ENTER sends CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200169
Chris Liechtid698af72015-08-24 20:24:55 +0200170 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200171 return text.replace('\n', '\r\n')
172
Chris Liechti442bf512015-08-15 01:42:24 +0200173
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200174class CR(Transform):
175 """ENTER sends CR"""
Chris Liechtid698af72015-08-24 20:24:55 +0200176
177 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200178 return text.replace('\r', '\n')
179
Chris Liechtid698af72015-08-24 20:24:55 +0200180 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200181 return text.replace('\n', '\r')
182
Chris Liechti442bf512015-08-15 01:42:24 +0200183
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200184class LF(Transform):
185 """ENTER sends LF"""
186
187
188class NoTerminal(Transform):
189 """remove typical terminal control codes from input"""
Chris Liechti9a720852015-08-25 00:20:38 +0200190
191 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
192 REPLACEMENT_MAP.update({
Chris Liechti033f17c2015-08-30 21:28:04 +0200193 0x7F: 0x2421, # DEL
194 0x9B: 0x2425, # CSI
Chris Liechti9a720852015-08-25 00:20:38 +0200195 })
196
Chris Liechtid698af72015-08-24 20:24:55 +0200197 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200198 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200199
Chris Liechtid698af72015-08-24 20:24:55 +0200200 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200201
202
Chris Liechti9a720852015-08-25 00:20:38 +0200203class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200204 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200205
Chris Liechti9a720852015-08-25 00:20:38 +0200206 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
207 REPLACEMENT_MAP.update({
Chris Liechti033f17c2015-08-30 21:28:04 +0200208 32: 0x2423, # visual space
209 0x7F: 0x2421, # DEL
210 0x9B: 0x2425, # CSI
Chris Liechti9a720852015-08-25 00:20:38 +0200211 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200212
213
214class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200215 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200216
Chris Liechtid698af72015-08-24 20:24:55 +0200217 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200218 r = []
219 for t in text:
Chris Liechti7e9cfd42015-08-12 15:28:19 +0200220 if ' ' <= t < '\x7f' or t in '\r\n\b\t':
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200221 r.append(t)
Chris Liechtid698af72015-08-24 20:24:55 +0200222 elif t < ' ':
223 r.append(unichr(0x2400 + ord(t)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200224 else:
225 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(t)))
226 r.append(' ')
227 return ''.join(r)
228
Chris Liechtid698af72015-08-24 20:24:55 +0200229 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200230
231
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200232class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200233 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200234
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200235 def __init__(self):
236 # XXX make it configurable, use colorama?
237 self.input_color = '\x1b[37m'
238 self.echo_color = '\x1b[31m'
239
Chris Liechtid698af72015-08-24 20:24:55 +0200240 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200241 return self.input_color + text
242
243 def echo(self, text):
244 return self.echo_color + text
245
Chris Liechti442bf512015-08-15 01:42:24 +0200246
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200247class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200248 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200249
Chris Liechtid698af72015-08-24 20:24:55 +0200250 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200251 sys.stderr.write(' [RX:{}] '.format(repr(text)))
252 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200253 return text
254
Chris Liechtid698af72015-08-24 20:24:55 +0200255 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200256 sys.stderr.write(' [TX:{}] '.format(repr(text)))
257 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200258 return text
259
Chris Liechti442bf512015-08-15 01:42:24 +0200260
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200261# other ideas:
262# - add date/time for each newline
263# - insert newline after: a) timeout b) packet end character
264
Chris Liechtib3df13e2015-08-25 02:20:09 +0200265EOL_TRANSFORMATIONS = {
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200266 'crlf': CRLF,
267 'cr': CR,
268 'lf': LF,
Chris Liechtib3df13e2015-08-25 02:20:09 +0200269 }
270
271TRANSFORMATIONS = {
Chris Liechticbb00b22015-08-13 22:58:49 +0200272 'direct': Transform, # no transformation
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200273 'default': NoTerminal,
274 'nocontrol': NoControls,
275 'printable': Printable,
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200276 'colorize': Colorize,
277 'debug': DebugIO,
278 }
279
280
Chris Liechti033f17c2015-08-30 21:28:04 +0200281# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200282def ask_for_port():
283 """\
284 Show a list of ports and ask the user for a choice. To make selection
285 easier on systems with long device names, also allow the input of an
286 index.
287 """
288 sys.stderr.write('\n--- Available ports:\n')
289 ports = []
290 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
291 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
292 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
293 ports.append(port)
294 while True:
295 port = raw_input('--- Enter port index or full name: ')
296 try:
297 index = int(port) - 1
298 if not 0 <= index < len(ports):
299 sys.stderr.write('--- Invalid index!\n')
300 continue
301 except ValueError:
302 pass
303 else:
304 port = ports[index]
305 return port
cliechti1351dde2012-04-12 16:47:47 +0000306
307
cliechti8c2ea842011-03-18 01:51:46 +0000308class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200309 """\
310 Terminal application. Copy data from serial port to console and vice versa.
311 Handle special keys from the console to show menu etc.
312 """
313
Chris Liechti3b454802015-08-26 23:39:59 +0200314 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200315 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200316 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000317 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200318 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200319 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200320 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200321 self.eol = eol
322 self.filters = filters
323 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200324 self.exit_character = 0x1d # GS/CTRL+]
325 self.menu_character = 0x14 # Menu: CTRL+T
cliechti576de252002-02-28 23:54:44 +0000326
cliechti8c2ea842011-03-18 01:51:46 +0000327 def _start_reader(self):
328 """Start reader thread"""
329 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000330 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200331 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
332 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000333 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000334
335 def _stop_reader(self):
336 """Stop reader thread only, wait for clean exit of thread"""
337 self._reader_alive = False
338 self.receiver_thread.join()
339
cliechti8c2ea842011-03-18 01:51:46 +0000340 def start(self):
341 self.alive = True
342 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000343 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200344 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
345 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000346 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200347 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000348
cliechti6385f2c2005-09-21 19:51:19 +0000349 def stop(self):
350 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000351
cliechtibf6bb7d2006-03-30 00:28:18 +0000352 def join(self, transmit_only=False):
cliechti6385f2c2005-09-21 19:51:19 +0000353 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000354 if not transmit_only:
355 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000356
Chris Liechtib3df13e2015-08-25 02:20:09 +0200357 def update_transformations(self):
358 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] for f in self.filters]
359 self.tx_transformations = [t() for t in transformations]
360 self.rx_transformations = list(reversed(self.tx_transformations))
361
Chris Liechtid698af72015-08-24 20:24:55 +0200362 def set_rx_encoding(self, encoding, errors='replace'):
363 self.input_encoding = encoding
364 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
365
366 def set_tx_encoding(self, encoding, errors='replace'):
367 self.output_encoding = encoding
368 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
369
cliechti6c8eb2f2009-07-08 02:10:46 +0000370 def dump_port_settings(self):
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200371 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
372 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200373 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200374 ('active' if self.serial.rts else 'inactive'),
375 ('active' if self.serial.dtr else 'inactive'),
376 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000377 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200378 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200379 ('active' if self.serial.cts else 'inactive'),
380 ('active' if self.serial.dsr else 'inactive'),
381 ('active' if self.serial.ri else 'inactive'),
382 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000383 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200384 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000385 # yet received. ignore this error.
386 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200387 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
388 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200389 #~ sys.stderr.write('--- data escaping: %s linefeed: %s\n' % (
390 #~ REPR_MODES[self.repr_mode],
391 #~ LF_MODES[self.convert_outgoing]))
Chris Liechti442bf512015-08-15 01:42:24 +0200392 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
393 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200394 sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
395 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
cliechti6c8eb2f2009-07-08 02:10:46 +0000396
cliechti6385f2c2005-09-21 19:51:19 +0000397 def reader(self):
398 """loop and copy serial->console"""
cliechti6963b262010-01-02 03:01:21 +0000399 try:
cliechti8c2ea842011-03-18 01:51:46 +0000400 while self.alive and self._reader_alive:
Chris Liechti188cf592015-08-22 00:28:19 +0200401 # read all that is there or wait for one byte
Chris Liechti3b454802015-08-26 23:39:59 +0200402 data = self.serial.read(self.serial.in_waiting or 1)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200403 if data:
404 if self.raw:
405 self.console.write_bytes(data)
cliechti6963b262010-01-02 03:01:21 +0000406 else:
Chris Liechtid698af72015-08-24 20:24:55 +0200407 text = self.rx_decoder.decode(data)
Chris Liechtie1384382015-08-15 17:06:05 +0200408 for transformation in self.rx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200409 text = transformation.rx(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200410 self.console.write(text)
Chris Liechti033f17c2015-08-30 21:28:04 +0200411 except serial.SerialException:
cliechti6963b262010-01-02 03:01:21 +0000412 self.alive = False
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200413 # XXX would be nice if the writer could be interrupted at this
414 # point... to exit completely
cliechti6963b262010-01-02 03:01:21 +0000415 raise
cliechti576de252002-02-28 23:54:44 +0000416
cliechti6385f2c2005-09-21 19:51:19 +0000417 def writer(self):
cliechti8c2ea842011-03-18 01:51:46 +0000418 """\
Chris Liechti442bf512015-08-15 01:42:24 +0200419 Loop and copy console->serial until self.exit_character character is
420 found. When self.menu_character is found, interpret the next key
cliechti8c2ea842011-03-18 01:51:46 +0000421 locally.
cliechti6c8eb2f2009-07-08 02:10:46 +0000422 """
423 menu_active = False
424 try:
425 while self.alive:
426 try:
Chris Liechti89eb2472015-08-08 17:06:25 +0200427 c = self.console.getkey()
cliechti6c8eb2f2009-07-08 02:10:46 +0000428 except KeyboardInterrupt:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200429 c = '\x03'
cliechti6c8eb2f2009-07-08 02:10:46 +0000430 if menu_active:
Chris Liechti7af7c752015-08-12 15:45:19 +0200431 self.handle_menu_key(c)
cliechti6c8eb2f2009-07-08 02:10:46 +0000432 menu_active = False
Chris Liechti442bf512015-08-15 01:42:24 +0200433 elif c == self.menu_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200434 menu_active = True # next char will be for menu
Chris Liechti442bf512015-08-15 01:42:24 +0200435 elif c == self.exit_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200436 self.stop() # exit app
437 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000438 else:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200439 #~ if self.raw:
440 text = c
Chris Liechtie1384382015-08-15 17:06:05 +0200441 for transformation in self.tx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200442 text = transformation.tx(text)
Chris Liechtid698af72015-08-24 20:24:55 +0200443 self.serial.write(self.tx_encoder.encode(text))
cliechti6c8eb2f2009-07-08 02:10:46 +0000444 if self.echo:
Chris Liechti3b454802015-08-26 23:39:59 +0200445 echo_text = c
446 for transformation in self.tx_transformations:
447 echo_text = transformation.echo(echo_text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200448 self.console.write(echo_text)
cliechti6c8eb2f2009-07-08 02:10:46 +0000449 except:
450 self.alive = False
451 raise
cliechti6385f2c2005-09-21 19:51:19 +0000452
Chris Liechti7af7c752015-08-12 15:45:19 +0200453 def handle_menu_key(self, c):
454 """Implement a simple menu / settings"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200455 if c == self.menu_character or c == self.exit_character:
456 # Menu/exit character again -> send itself
Chris Liechtid698af72015-08-24 20:24:55 +0200457 self.serial.write(self.tx_encoder.encode(c))
Chris Liechti7af7c752015-08-12 15:45:19 +0200458 if self.echo:
459 self.console.write(c)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200460 elif c == '\x15': # CTRL+U -> upload file
Chris Liechti7af7c752015-08-12 15:45:19 +0200461 sys.stderr.write('\n--- File to upload: ')
462 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200463 with self.console:
464 filename = sys.stdin.readline().rstrip('\r\n')
465 if filename:
466 try:
467 with open(filename, 'rb') as f:
468 sys.stderr.write('--- Sending file {} ---\n'.format(filename))
469 while True:
470 block = f.read(1024)
471 if not block:
472 break
473 self.serial.write(block)
474 # Wait for output buffer to drain.
475 self.serial.flush()
476 sys.stderr.write('.') # Progress indicator.
477 sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
478 except IOError as e:
479 sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200480 elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
Chris Liechti442bf512015-08-15 01:42:24 +0200481 sys.stderr.write(self.get_help_text())
Chris Liechti7af7c752015-08-12 15:45:19 +0200482 elif c == '\x12': # CTRL+R -> Toggle RTS
Chris Liechti3b454802015-08-26 23:39:59 +0200483 self.serial.rts = not self.serial.rts
484 sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200485 elif c == '\x04': # CTRL+D -> Toggle DTR
Chris Liechti3b454802015-08-26 23:39:59 +0200486 self.serial.dtr = not self.serial.dtr
487 sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200488 elif c == '\x02': # CTRL+B -> toggle BREAK condition
Chris Liechti3b454802015-08-26 23:39:59 +0200489 self.serial.break_condition = not self.serial.break_condition
490 sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200491 elif c == '\x05': # CTRL+E -> toggle local echo
492 self.echo = not self.echo
Chris Liechti442bf512015-08-15 01:42:24 +0200493 sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200494 elif c == '\x06': # CTRL+F -> edit filters
495 sys.stderr.write('\n--- Available Filters:\n')
496 sys.stderr.write('\n'.join(
497 '--- {:<10} = {.__doc__}'.format(k, v)
498 for k, v in sorted(TRANSFORMATIONS.items())))
499 sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
500 with self.console:
501 new_filters = sys.stdin.readline().lower().split()
502 if new_filters:
503 for f in new_filters:
504 if f not in TRANSFORMATIONS:
505 sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
506 break
507 else:
508 self.filters = new_filters
509 self.update_transformations()
510 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
511 elif c == '\x0c': # CTRL+L -> EOL mode
Chris Liechti033f17c2015-08-30 21:28:04 +0200512 modes = list(EOL_TRANSFORMATIONS) # keys
Chris Liechtib3df13e2015-08-25 02:20:09 +0200513 eol = modes.index(self.eol) + 1
514 if eol >= len(modes):
515 eol = 0
516 self.eol = modes[eol]
517 sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
518 self.update_transformations()
519 elif c == '\x01': # CTRL+A -> set encoding
520 sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
521 with self.console:
522 new_encoding = sys.stdin.readline().strip()
523 if new_encoding:
524 try:
525 codecs.lookup(new_encoding)
526 except LookupError:
527 sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
528 else:
529 self.set_rx_encoding(new_encoding)
530 self.set_tx_encoding(new_encoding)
531 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
532 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechti7af7c752015-08-12 15:45:19 +0200533 elif c == '\x09': # CTRL+I -> info
534 self.dump_port_settings()
535 #~ elif c == '\x01': # CTRL+A -> cycle escape mode
536 #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
537 elif c in 'pP': # P -> change port
Chris Liechti269f77b2015-08-24 01:31:42 +0200538 with self.console:
539 try:
Chris Liechti89313c92015-09-01 02:33:13 +0200540 port = ask_for_port()
Chris Liechti269f77b2015-08-24 01:31:42 +0200541 except KeyboardInterrupt:
542 port = None
Chris Liechti7af7c752015-08-12 15:45:19 +0200543 if port and port != self.serial.port:
544 # reader thread needs to be shut down
545 self._stop_reader()
546 # save settings
547 settings = self.serial.getSettingsDict()
548 try:
549 new_serial = serial.serial_for_url(port, do_not_open=True)
550 # restore settings and open
551 new_serial.applySettingsDict(settings)
Chris Liechti89313c92015-09-01 02:33:13 +0200552 new_serial.rts = self.serial.rts
553 new_serial.dtr = self.serial.dtr
Chris Liechti7af7c752015-08-12 15:45:19 +0200554 new_serial.open()
Chris Liechti89313c92015-09-01 02:33:13 +0200555 new_serial.break_condition = self.serial.break_condition
Chris Liechti7af7c752015-08-12 15:45:19 +0200556 except Exception as e:
Chris Liechti442bf512015-08-15 01:42:24 +0200557 sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200558 new_serial.close()
559 else:
560 self.serial.close()
561 self.serial = new_serial
Chris Liechti442bf512015-08-15 01:42:24 +0200562 sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
Chris Liechti7af7c752015-08-12 15:45:19 +0200563 # and restart the reader thread
564 self._start_reader()
565 elif c in 'bB': # B -> change baudrate
566 sys.stderr.write('\n--- Baudrate: ')
567 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200568 with self.console:
569 backup = self.serial.baudrate
570 try:
571 self.serial.baudrate = int(sys.stdin.readline().strip())
572 except ValueError as e:
573 sys.stderr.write('--- ERROR setting baudrate: %s ---\n'.format(e))
574 self.serial.baudrate = backup
575 else:
576 self.dump_port_settings()
Chris Liechti7af7c752015-08-12 15:45:19 +0200577 elif c == '8': # 8 -> change to 8 bits
578 self.serial.bytesize = serial.EIGHTBITS
579 self.dump_port_settings()
580 elif c == '7': # 7 -> change to 8 bits
581 self.serial.bytesize = serial.SEVENBITS
582 self.dump_port_settings()
583 elif c in 'eE': # E -> change to even parity
584 self.serial.parity = serial.PARITY_EVEN
585 self.dump_port_settings()
586 elif c in 'oO': # O -> change to odd parity
587 self.serial.parity = serial.PARITY_ODD
588 self.dump_port_settings()
589 elif c in 'mM': # M -> change to mark parity
590 self.serial.parity = serial.PARITY_MARK
591 self.dump_port_settings()
592 elif c in 'sS': # S -> change to space parity
593 self.serial.parity = serial.PARITY_SPACE
594 self.dump_port_settings()
595 elif c in 'nN': # N -> change to no parity
596 self.serial.parity = serial.PARITY_NONE
597 self.dump_port_settings()
598 elif c == '1': # 1 -> change to 1 stop bits
599 self.serial.stopbits = serial.STOPBITS_ONE
600 self.dump_port_settings()
601 elif c == '2': # 2 -> change to 2 stop bits
602 self.serial.stopbits = serial.STOPBITS_TWO
603 self.dump_port_settings()
604 elif c == '3': # 3 -> change to 1.5 stop bits
605 self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
606 self.dump_port_settings()
607 elif c in 'xX': # X -> change software flow control
608 self.serial.xonxoff = (c == 'X')
609 self.dump_port_settings()
610 elif c in 'rR': # R -> change hardware flow control
611 self.serial.rtscts = (c == 'R')
612 self.dump_port_settings()
613 else:
Chris Liechti442bf512015-08-15 01:42:24 +0200614 sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
615
616 def get_help_text(self):
Chris Liechti55ba7d92015-08-15 16:33:51 +0200617 # help text, starts with blank line!
Chris Liechti442bf512015-08-15 01:42:24 +0200618 return """
619--- pySerial ({version}) - miniterm - help
620---
621--- {exit:8} Exit program
622--- {menu:8} Menu escape key, followed by:
623--- Menu keys:
624--- {menu:7} Send the menu character itself to remote
625--- {exit:7} Send the exit character itself to remote
626--- {info:7} Show info
627--- {upload:7} Upload file (prompt will be shown)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200628--- {repr:7} encoding
629--- {filter:7} edit filters
Chris Liechti442bf512015-08-15 01:42:24 +0200630--- Toggles:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200631--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
632--- {echo:7} echo {eol:7} EOL
Chris Liechti442bf512015-08-15 01:42:24 +0200633---
Chris Liechti55ba7d92015-08-15 16:33:51 +0200634--- Port settings ({menu} followed by the following):
Chris Liechti442bf512015-08-15 01:42:24 +0200635--- p change port
636--- 7 8 set data bits
Chris Liechtib7550bd2015-08-15 04:09:10 +0200637--- N E O S M change parity (None, Even, Odd, Space, Mark)
Chris Liechti442bf512015-08-15 01:42:24 +0200638--- 1 2 3 set stop bits (1, 2, 1.5)
639--- b change baud rate
640--- x X disable/enable software flow control
641--- r R disable/enable hardware flow control
642""".format(
643 version=getattr(serial, 'VERSION', 'unknown version'),
644 exit=key_description(self.exit_character),
645 menu=key_description(self.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200646 rts=key_description('\x12'),
647 dtr=key_description('\x04'),
648 brk=key_description('\x02'),
649 echo=key_description('\x05'),
650 info=key_description('\x09'),
651 upload=key_description('\x15'),
Chris Liechtib3df13e2015-08-25 02:20:09 +0200652 repr=key_description('\x01'),
653 filter=key_description('\x06'),
654 eol=key_description('\x0c'),
Chris Liechti442bf512015-08-15 01:42:24 +0200655 )
Chris Liechti7af7c752015-08-12 15:45:19 +0200656
657
Chris Liechtib3df13e2015-08-25 02:20:09 +0200658# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti55ba7d92015-08-15 16:33:51 +0200659# default args can be used to override when calling main() from an other script
660# e.g to create a miniterm-my-device.py
661def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
Chris Liechtib7550bd2015-08-15 04:09:10 +0200662 import argparse
cliechti6385f2c2005-09-21 19:51:19 +0000663
Chris Liechtib7550bd2015-08-15 04:09:10 +0200664 parser = argparse.ArgumentParser(
665 description="Miniterm - A simple terminal program for the serial port.")
cliechti6385f2c2005-09-21 19:51:19 +0000666
Chris Liechti033f17c2015-08-30 21:28:04 +0200667 parser.add_argument(
668 "port",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200669 nargs='?',
Chris Liechti7cf82762015-09-01 02:56:04 +0200670 help="serial port name ('-' to show port list)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200671 default=default_port)
cliechti5370cee2013-10-13 03:08:19 +0000672
Chris Liechti033f17c2015-08-30 21:28:04 +0200673 parser.add_argument(
674 "baudrate",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200675 nargs='?',
676 type=int,
677 help="set baud rate, default: %(default)s",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200678 default=default_baudrate)
cliechti6385f2c2005-09-21 19:51:19 +0000679
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200680 group = parser.add_argument_group("port settings")
cliechti53edb472009-02-06 21:18:46 +0000681
Chris Liechti033f17c2015-08-30 21:28:04 +0200682 group.add_argument(
683 "--parity",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200684 choices=['N', 'E', 'O', 'S', 'M'],
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200685 type=lambda c: c.upper(),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200686 help="set parity, one of {N E O S M}, default: N",
687 default='N')
cliechti53edb472009-02-06 21:18:46 +0000688
Chris Liechti033f17c2015-08-30 21:28:04 +0200689 group.add_argument(
690 "--rtscts",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200691 action="store_true",
692 help="enable RTS/CTS flow control (default off)",
693 default=False)
cliechti53edb472009-02-06 21:18:46 +0000694
Chris Liechti033f17c2015-08-30 21:28:04 +0200695 group.add_argument(
696 "--xonxoff",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200697 action="store_true",
698 help="enable software flow control (default off)",
699 default=False)
cliechti53edb472009-02-06 21:18:46 +0000700
Chris Liechti033f17c2015-08-30 21:28:04 +0200701 group.add_argument(
702 "--rts",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200703 type=int,
704 help="set initial RTS line state (possible values: 0, 1)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200705 default=default_rts)
cliechti5370cee2013-10-13 03:08:19 +0000706
Chris Liechti033f17c2015-08-30 21:28:04 +0200707 group.add_argument(
708 "--dtr",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200709 type=int,
710 help="set initial DTR line state (possible values: 0, 1)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200711 default=default_dtr)
cliechti5370cee2013-10-13 03:08:19 +0000712
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200713 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000714
Chris Liechti033f17c2015-08-30 21:28:04 +0200715 group.add_argument(
716 "-e", "--echo",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200717 action="store_true",
718 help="enable local echo (default off)",
719 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000720
Chris Liechti033f17c2015-08-30 21:28:04 +0200721 group.add_argument(
722 "--encoding",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200723 dest="serial_port_encoding",
724 metavar="CODEC",
Chris Liechtia7e7b692015-08-25 21:10:28 +0200725 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200726 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000727
Chris Liechti033f17c2015-08-30 21:28:04 +0200728 group.add_argument(
729 "-f", "--filter",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200730 action="append",
731 metavar="NAME",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200732 help="add text transformation",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200733 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200734
Chris Liechti033f17c2015-08-30 21:28:04 +0200735 group.add_argument(
736 "--eol",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200737 choices=['CR', 'LF', 'CRLF'],
738 type=lambda c: c.upper(),
739 help="end of line mode",
740 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000741
Chris Liechti033f17c2015-08-30 21:28:04 +0200742 group.add_argument(
743 "--raw",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200744 action="store_true",
745 help="Do no apply any encodings/transformations",
746 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000747
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200748 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000749
Chris Liechti033f17c2015-08-30 21:28:04 +0200750 group.add_argument(
751 "--exit-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200752 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200753 metavar='NUM',
754 help="Unicode of special character that is used to exit the application, default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200755 default=0x1d # GS/CTRL+]
756 )
cliechtibf6bb7d2006-03-30 00:28:18 +0000757
Chris Liechti033f17c2015-08-30 21:28:04 +0200758 group.add_argument(
759 "--menu-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200760 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200761 metavar='NUM',
762 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200763 default=0x14 # Menu: CTRL+T
764 )
cliechti9c592b32008-06-16 22:00:14 +0000765
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200766 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000767
Chris Liechti033f17c2015-08-30 21:28:04 +0200768 group.add_argument(
769 "-q", "--quiet",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200770 action="store_true",
771 help="suppress non-error messages",
772 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000773
Chris Liechti033f17c2015-08-30 21:28:04 +0200774 group.add_argument(
775 "--develop",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200776 action="store_true",
777 help="show Python traceback on error",
778 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000779
Chris Liechtib7550bd2015-08-15 04:09:10 +0200780 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000781
Chris Liechtib7550bd2015-08-15 04:09:10 +0200782 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000783 parser.error('--exit-char can not be the same as --menu-char')
784
Chris Liechtib7550bd2015-08-15 04:09:10 +0200785 # no port given on command line -> ask user now
Chris Liechti7cf82762015-09-01 02:56:04 +0200786 if args.port is None or args.port == '-':
Chris Liechti89313c92015-09-01 02:33:13 +0200787 try:
788 args.port = ask_for_port()
789 except KeyboardInterrupt:
790 sys.stderr.write('\n')
791 parser.error('user aborted and port is not given')
792 else:
793 if not args.port:
794 parser.error('port is not given')
cliechti53edb472009-02-06 21:18:46 +0000795
Chris Liechtib3df13e2015-08-25 02:20:09 +0200796 if args.filter:
797 if 'help' in args.filter:
798 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200799 sys.stderr.write('\n'.join(
Chris Liechtib3df13e2015-08-25 02:20:09 +0200800 '{:<10} = {.__doc__}'.format(k, v)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200801 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200802 sys.stderr.write('\n')
803 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200804 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200805 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200806 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200807
cliechti6385f2c2005-09-21 19:51:19 +0000808 try:
Chris Liechti3b454802015-08-26 23:39:59 +0200809 serial_instance = serial.serial_for_url(
Chris Liechtib7550bd2015-08-15 04:09:10 +0200810 args.port,
811 args.baudrate,
Chris Liechti3b454802015-08-26 23:39:59 +0200812 parity=args.parity,
Chris Liechtib7550bd2015-08-15 04:09:10 +0200813 rtscts=args.rtscts,
814 xonxoff=args.xonxoff,
Chris Liechti3b454802015-08-26 23:39:59 +0200815 timeout=1,
816 do_not_open=True)
817
818 if args.dtr is not None:
819 if not args.quiet:
820 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
821 serial_instance.dtr = args.dtr
822 if args.rts is not None:
823 if not args.quiet:
824 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
825 serial_instance.rts = args.rts
826
827 serial_instance.open()
Chris Liechti68340d72015-08-03 14:15:48 +0200828 except serial.SerialException as e:
Chris Liechtiaccd2012015-08-17 03:09:23 +0200829 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200830 if args.develop:
Chris Liechti91090912015-08-05 02:36:14 +0200831 raise
cliechti6385f2c2005-09-21 19:51:19 +0000832 sys.exit(1)
833
Chris Liechti3b454802015-08-26 23:39:59 +0200834 miniterm = Miniterm(
835 serial_instance,
836 echo=args.echo,
837 eol=args.eol.lower(),
838 filters=filters)
839 miniterm.exit_character = unichr(args.exit_char)
840 miniterm.menu_character = unichr(args.menu_char)
841 miniterm.raw = args.raw
842 miniterm.set_rx_encoding(args.serial_port_encoding)
843 miniterm.set_tx_encoding(args.serial_port_encoding)
844
Chris Liechtib7550bd2015-08-15 04:09:10 +0200845 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200846 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
847 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200848 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti442bf512015-08-15 01:42:24 +0200849 key_description(miniterm.exit_character),
850 key_description(miniterm.menu_character),
851 key_description(miniterm.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200852 key_description('\x08'),
Chris Liechti442bf512015-08-15 01:42:24 +0200853 ))
cliechti6fa76fb2009-07-08 23:53:39 +0000854
cliechti6385f2c2005-09-21 19:51:19 +0000855 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000856 try:
857 miniterm.join(True)
858 except KeyboardInterrupt:
859 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200860 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000861 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000862 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000863
cliechti5370cee2013-10-13 03:08:19 +0000864# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000865if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000866 main()