blob: 65a5031fcb8ec50d3f1c91dda789e0b88b54a2c8 [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):
39 def __init__(self):
40 if sys.version_info >= (3, 0):
41 self.byte_output = sys.stdout.buffer
42 else:
43 self.byte_output = sys.stdout
44 self.output = sys.stdout
cliechtif467aa82013-10-13 21:36:49 +000045
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020046 def setup(self):
Chris Liechti1df28272015-08-27 23:37:38 +020047 pass
cliechtif467aa82013-10-13 21:36:49 +000048
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020049 def cleanup(self):
Chris Liechti1df28272015-08-27 23:37:38 +020050 pass
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020051
52 def getkey(self):
53 return None
54
55 def write_bytes(self, s):
56 self.byte_output.write(s)
57 self.byte_output.flush()
58
59 def write(self, s):
60 self.output.write(s)
61 self.output.flush()
62
Chris Liechti269f77b2015-08-24 01:31:42 +020063 # - - - - - - - - - - - - - - - - - - - - - - - -
64 # context manager:
65 # switch terminal temporary to normal mode (e.g. to get user input)
66
67 def __enter__(self):
68 self.cleanup()
69 return self
70
71 def __exit__(self, *args, **kwargs):
72 self.setup()
73
cliechti9c592b32008-06-16 22:00:14 +000074
Chris Liechtiba45c522016-02-06 23:53:23 +010075if os.name == 'nt': # noqa
cliechti576de252002-02-28 23:54:44 +000076 import msvcrt
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020077 import ctypes
Chris Liechti9cc696b2015-08-28 00:54:22 +020078
79 class Out(object):
80 def __init__(self, fd):
81 self.fd = fd
82
83 def flush(self):
84 pass
85
86 def write(self, s):
87 os.write(self.fd, s)
88
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020089 class Console(ConsoleBase):
Chris Liechticbb00b22015-08-13 22:58:49 +020090 def __init__(self):
91 super(Console, self).__init__()
Chris Liechti1df28272015-08-27 23:37:38 +020092 self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
93 self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
Chris Liechticbb00b22015-08-13 22:58:49 +020094 ctypes.windll.kernel32.SetConsoleOutputCP(65001)
95 ctypes.windll.kernel32.SetConsoleCP(65001)
Chris Liechti9cc696b2015-08-28 00:54:22 +020096 self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
97 # the change of the code page is not propagated to Python, manually fix it
98 sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
99 sys.stdout = self.output
Chris Liechti168704f2015-09-30 16:50:29 +0200100 self.output.encoding = 'UTF-8' # needed for input
Chris Liechticbb00b22015-08-13 22:58:49 +0200101
Chris Liechti1df28272015-08-27 23:37:38 +0200102 def __del__(self):
103 ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
104 ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
105
cliechti3a8bf092008-09-17 11:26:53 +0000106 def getkey(self):
cliechti91165532011-03-18 02:02:52 +0000107 while True:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200108 z = msvcrt.getwch()
Chris Liechti9f398812015-09-13 18:50:44 +0200109 if z == unichr(13):
110 return unichr(10)
111 elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200112 msvcrt.getwch()
cliechti9c592b32008-06-16 22:00:14 +0000113 else:
cliechti9c592b32008-06-16 22:00:14 +0000114 return z
cliechti53edb472009-02-06 21:18:46 +0000115
cliechti576de252002-02-28 23:54:44 +0000116elif os.name == 'posix':
Chris Liechtia1d5c6d2015-08-07 14:41:24 +0200117 import atexit
118 import termios
Chris Liechti9cc696b2015-08-28 00:54:22 +0200119
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200120 class Console(ConsoleBase):
cliechti9c592b32008-06-16 22:00:14 +0000121 def __init__(self):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200122 super(Console, self).__init__()
cliechti9c592b32008-06-16 22:00:14 +0000123 self.fd = sys.stdin.fileno()
Chris Liechti4d989c22015-08-24 00:24:49 +0200124 self.old = termios.tcgetattr(self.fd)
Chris Liechti89eb2472015-08-08 17:06:25 +0200125 atexit.register(self.cleanup)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200126 if sys.version_info < (3, 0):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200127 self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
128 else:
129 self.enc_stdin = sys.stdin
cliechti9c592b32008-06-16 22:00:14 +0000130
131 def setup(self):
cliechti9c592b32008-06-16 22:00:14 +0000132 new = termios.tcgetattr(self.fd)
133 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
134 new[6][termios.VMIN] = 1
135 new[6][termios.VTIME] = 0
136 termios.tcsetattr(self.fd, termios.TCSANOW, new)
cliechti53edb472009-02-06 21:18:46 +0000137
cliechti9c592b32008-06-16 22:00:14 +0000138 def getkey(self):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200139 c = self.enc_stdin.read(1)
Chris Liechti9f398812015-09-13 18:50:44 +0200140 if c == unichr(0x7f):
141 c = unichr(8) # map the BS key (which yields DEL) to backspace
Chris Liechti9a720852015-08-25 00:20:38 +0200142 return c
cliechti53edb472009-02-06 21:18:46 +0000143
cliechti9c592b32008-06-16 22:00:14 +0000144 def cleanup(self):
Chris Liechti4d989c22015-08-24 00:24:49 +0200145 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
cliechti9c592b32008-06-16 22:00:14 +0000146
cliechti576de252002-02-28 23:54:44 +0000147else:
cliechti8c2ea842011-03-18 01:51:46 +0000148 raise NotImplementedError("Sorry no implementation for your platform (%s) available." % sys.platform)
cliechti576de252002-02-28 23:54:44 +0000149
cliechti6fa76fb2009-07-08 23:53:39 +0000150
Chris Liechti9a720852015-08-25 00:20:38 +0200151# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200152
153class Transform(object):
Chris Liechticbb00b22015-08-13 22:58:49 +0200154 """do-nothing: forward all data unchanged"""
Chris Liechtid698af72015-08-24 20:24:55 +0200155 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200156 """text received from serial port"""
157 return text
158
Chris Liechtid698af72015-08-24 20:24:55 +0200159 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200160 """text to be sent to serial port"""
161 return text
162
163 def echo(self, text):
164 """text to be sent but displayed on console"""
165 return text
166
Chris Liechti442bf512015-08-15 01:42:24 +0200167
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200168class CRLF(Transform):
169 """ENTER sends CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200170
Chris Liechtid698af72015-08-24 20:24:55 +0200171 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200172 return text.replace('\n', '\r\n')
173
Chris Liechti442bf512015-08-15 01:42:24 +0200174
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200175class CR(Transform):
176 """ENTER sends CR"""
Chris Liechtid698af72015-08-24 20:24:55 +0200177
178 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200179 return text.replace('\r', '\n')
180
Chris Liechtid698af72015-08-24 20:24:55 +0200181 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200182 return text.replace('\n', '\r')
183
Chris Liechti442bf512015-08-15 01:42:24 +0200184
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200185class LF(Transform):
186 """ENTER sends LF"""
187
188
189class NoTerminal(Transform):
190 """remove typical terminal control codes from input"""
Chris Liechti9a720852015-08-25 00:20:38 +0200191
192 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 +0100193 REPLACEMENT_MAP.update(
194 {
Chris Liechti033f17c2015-08-30 21:28:04 +0200195 0x7F: 0x2421, # DEL
196 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100197 })
Chris Liechti9a720852015-08-25 00:20:38 +0200198
Chris Liechtid698af72015-08-24 20:24:55 +0200199 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200200 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200201
Chris Liechtid698af72015-08-24 20:24:55 +0200202 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200203
204
Chris Liechti9a720852015-08-25 00:20:38 +0200205class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200206 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200207
Chris Liechti9a720852015-08-25 00:20:38 +0200208 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
Chris Liechtiba45c522016-02-06 23:53:23 +0100209 REPLACEMENT_MAP.update(
210 {
Chris Liechti033f17c2015-08-30 21:28:04 +0200211 32: 0x2423, # visual space
212 0x7F: 0x2421, # DEL
213 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100214 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200215
216
217class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200218 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200219
Chris Liechtid698af72015-08-24 20:24:55 +0200220 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200221 r = []
222 for t in text:
Chris Liechti7e9cfd42015-08-12 15:28:19 +0200223 if ' ' <= t < '\x7f' or t in '\r\n\b\t':
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200224 r.append(t)
Chris Liechtid698af72015-08-24 20:24:55 +0200225 elif t < ' ':
226 r.append(unichr(0x2400 + ord(t)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200227 else:
228 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(t)))
229 r.append(' ')
230 return ''.join(r)
231
Chris Liechtid698af72015-08-24 20:24:55 +0200232 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200233
234
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200235class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200236 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200237
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200238 def __init__(self):
239 # XXX make it configurable, use colorama?
240 self.input_color = '\x1b[37m'
241 self.echo_color = '\x1b[31m'
242
Chris Liechtid698af72015-08-24 20:24:55 +0200243 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200244 return self.input_color + text
245
246 def echo(self, text):
247 return self.echo_color + text
248
Chris Liechti442bf512015-08-15 01:42:24 +0200249
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200250class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200251 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200252
Chris Liechtid698af72015-08-24 20:24:55 +0200253 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200254 sys.stderr.write(' [RX:{}] '.format(repr(text)))
255 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200256 return text
257
Chris Liechtid698af72015-08-24 20:24:55 +0200258 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200259 sys.stderr.write(' [TX:{}] '.format(repr(text)))
260 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200261 return text
262
Chris Liechti442bf512015-08-15 01:42:24 +0200263
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200264# other ideas:
265# - add date/time for each newline
266# - insert newline after: a) timeout b) packet end character
267
Chris Liechtib3df13e2015-08-25 02:20:09 +0200268EOL_TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100269 'crlf': CRLF,
270 'cr': CR,
271 'lf': LF,
272}
Chris Liechtib3df13e2015-08-25 02:20:09 +0200273
274TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100275 'direct': Transform, # no transformation
276 'default': NoTerminal,
277 'nocontrol': NoControls,
278 'printable': Printable,
279 'colorize': Colorize,
280 'debug': DebugIO,
281}
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200282
283
Chris Liechti033f17c2015-08-30 21:28:04 +0200284# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200285def ask_for_port():
286 """\
287 Show a list of ports and ask the user for a choice. To make selection
288 easier on systems with long device names, also allow the input of an
289 index.
290 """
291 sys.stderr.write('\n--- Available ports:\n')
292 ports = []
293 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
294 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
295 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
296 ports.append(port)
297 while True:
298 port = raw_input('--- Enter port index or full name: ')
299 try:
300 index = int(port) - 1
301 if not 0 <= index < len(ports):
302 sys.stderr.write('--- Invalid index!\n')
303 continue
304 except ValueError:
305 pass
306 else:
307 port = ports[index]
308 return port
cliechti1351dde2012-04-12 16:47:47 +0000309
310
cliechti8c2ea842011-03-18 01:51:46 +0000311class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200312 """\
313 Terminal application. Copy data from serial port to console and vice versa.
314 Handle special keys from the console to show menu etc.
315 """
316
Chris Liechti3b454802015-08-26 23:39:59 +0200317 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200318 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200319 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000320 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200321 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200322 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200323 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200324 self.eol = eol
325 self.filters = filters
326 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200327 self.exit_character = 0x1d # GS/CTRL+]
328 self.menu_character = 0x14 # Menu: CTRL+T
cliechti576de252002-02-28 23:54:44 +0000329
cliechti8c2ea842011-03-18 01:51:46 +0000330 def _start_reader(self):
331 """Start reader thread"""
332 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000333 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200334 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
335 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000336 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000337
338 def _stop_reader(self):
339 """Stop reader thread only, wait for clean exit of thread"""
340 self._reader_alive = False
341 self.receiver_thread.join()
342
cliechti8c2ea842011-03-18 01:51:46 +0000343 def start(self):
344 self.alive = True
345 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000346 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200347 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
348 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000349 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200350 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000351
cliechti6385f2c2005-09-21 19:51:19 +0000352 def stop(self):
353 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000354
cliechtibf6bb7d2006-03-30 00:28:18 +0000355 def join(self, transmit_only=False):
cliechti6385f2c2005-09-21 19:51:19 +0000356 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000357 if not transmit_only:
358 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000359
Chris Liechtib3df13e2015-08-25 02:20:09 +0200360 def update_transformations(self):
361 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] for f in self.filters]
362 self.tx_transformations = [t() for t in transformations]
363 self.rx_transformations = list(reversed(self.tx_transformations))
364
Chris Liechtid698af72015-08-24 20:24:55 +0200365 def set_rx_encoding(self, encoding, errors='replace'):
366 self.input_encoding = encoding
367 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
368
369 def set_tx_encoding(self, encoding, errors='replace'):
370 self.output_encoding = encoding
371 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
372
cliechti6c8eb2f2009-07-08 02:10:46 +0000373 def dump_port_settings(self):
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200374 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
375 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200376 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200377 ('active' if self.serial.rts else 'inactive'),
378 ('active' if self.serial.dtr else 'inactive'),
379 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000380 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200381 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200382 ('active' if self.serial.cts else 'inactive'),
383 ('active' if self.serial.dsr else 'inactive'),
384 ('active' if self.serial.ri else 'inactive'),
385 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000386 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200387 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000388 # yet received. ignore this error.
389 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200390 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
391 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
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(
Chris Liechtiba45c522016-02-06 23:53:23 +0100643 version=getattr(serial, 'VERSION', 'unknown version'),
644 exit=key_description(self.exit_character),
645 menu=key_description(self.menu_character),
646 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'),
652 repr=key_description('\x01'),
653 filter=key_description('\x06'),
654 eol=key_description('\x0c'))
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 Liechti00f84282015-12-24 23:40:34 +0100712 group.add_argument(
713 "--ask",
714 action="store_true",
715 help="ask again for port when open fails",
716 default=False)
717
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200718 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000719
Chris Liechti033f17c2015-08-30 21:28:04 +0200720 group.add_argument(
721 "-e", "--echo",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200722 action="store_true",
723 help="enable local echo (default off)",
724 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000725
Chris Liechti033f17c2015-08-30 21:28:04 +0200726 group.add_argument(
727 "--encoding",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200728 dest="serial_port_encoding",
729 metavar="CODEC",
Chris Liechtia7e7b692015-08-25 21:10:28 +0200730 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200731 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000732
Chris Liechti033f17c2015-08-30 21:28:04 +0200733 group.add_argument(
734 "-f", "--filter",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200735 action="append",
736 metavar="NAME",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200737 help="add text transformation",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200738 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200739
Chris Liechti033f17c2015-08-30 21:28:04 +0200740 group.add_argument(
741 "--eol",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200742 choices=['CR', 'LF', 'CRLF'],
743 type=lambda c: c.upper(),
744 help="end of line mode",
745 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000746
Chris Liechti033f17c2015-08-30 21:28:04 +0200747 group.add_argument(
748 "--raw",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200749 action="store_true",
750 help="Do no apply any encodings/transformations",
751 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000752
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200753 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000754
Chris Liechti033f17c2015-08-30 21:28:04 +0200755 group.add_argument(
756 "--exit-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200757 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200758 metavar='NUM',
759 help="Unicode of special character that is used to exit the application, default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200760 default=0x1d # GS/CTRL+]
761 )
cliechtibf6bb7d2006-03-30 00:28:18 +0000762
Chris Liechti033f17c2015-08-30 21:28:04 +0200763 group.add_argument(
764 "--menu-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200765 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200766 metavar='NUM',
767 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200768 default=0x14 # Menu: CTRL+T
769 )
cliechti9c592b32008-06-16 22:00:14 +0000770
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200771 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000772
Chris Liechti033f17c2015-08-30 21:28:04 +0200773 group.add_argument(
774 "-q", "--quiet",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200775 action="store_true",
776 help="suppress non-error messages",
777 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000778
Chris Liechti033f17c2015-08-30 21:28:04 +0200779 group.add_argument(
780 "--develop",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200781 action="store_true",
782 help="show Python traceback on error",
783 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000784
Chris Liechtib7550bd2015-08-15 04:09:10 +0200785 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000786
Chris Liechtib7550bd2015-08-15 04:09:10 +0200787 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000788 parser.error('--exit-char can not be the same as --menu-char')
789
Chris Liechtib3df13e2015-08-25 02:20:09 +0200790 if args.filter:
791 if 'help' in args.filter:
792 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200793 sys.stderr.write('\n'.join(
Chris Liechtib3df13e2015-08-25 02:20:09 +0200794 '{:<10} = {.__doc__}'.format(k, v)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200795 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200796 sys.stderr.write('\n')
797 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200798 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200799 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200800 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200801
Chris Liechti00f84282015-12-24 23:40:34 +0100802 while True:
803 # no port given on command line -> ask user now
804 if args.port is None or args.port == '-':
805 try:
806 args.port = ask_for_port()
807 except KeyboardInterrupt:
808 sys.stderr.write('\n')
809 parser.error('user aborted and port is not given')
810 else:
811 if not args.port:
812 parser.error('port is not given')
813 try:
814 serial_instance = serial.serial_for_url(
815 args.port,
816 args.baudrate,
817 parity=args.parity,
818 rtscts=args.rtscts,
819 xonxoff=args.xonxoff,
820 timeout=1,
821 do_not_open=True)
Chris Liechti3b454802015-08-26 23:39:59 +0200822
Chris Liechti00f84282015-12-24 23:40:34 +0100823 if args.dtr is not None:
824 if not args.quiet:
825 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
826 serial_instance.dtr = args.dtr
827 if args.rts is not None:
828 if not args.quiet:
829 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
830 serial_instance.rts = args.rts
Chris Liechti3b454802015-08-26 23:39:59 +0200831
Chris Liechti00f84282015-12-24 23:40:34 +0100832 serial_instance.open()
833 except serial.SerialException as e:
834 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
835 if args.develop:
836 raise
837 if not args.ask:
838 sys.exit(1)
839 else:
840 args.port = '-'
841 else:
842 break
cliechti6385f2c2005-09-21 19:51:19 +0000843
Chris Liechti3b454802015-08-26 23:39:59 +0200844 miniterm = Miniterm(
845 serial_instance,
846 echo=args.echo,
847 eol=args.eol.lower(),
848 filters=filters)
849 miniterm.exit_character = unichr(args.exit_char)
850 miniterm.menu_character = unichr(args.menu_char)
851 miniterm.raw = args.raw
852 miniterm.set_rx_encoding(args.serial_port_encoding)
853 miniterm.set_tx_encoding(args.serial_port_encoding)
854
Chris Liechtib7550bd2015-08-15 04:09:10 +0200855 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200856 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
857 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200858 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti442bf512015-08-15 01:42:24 +0200859 key_description(miniterm.exit_character),
860 key_description(miniterm.menu_character),
861 key_description(miniterm.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200862 key_description('\x08'),
Chris Liechti442bf512015-08-15 01:42:24 +0200863 ))
cliechti6fa76fb2009-07-08 23:53:39 +0000864
cliechti6385f2c2005-09-21 19:51:19 +0000865 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000866 try:
867 miniterm.join(True)
868 except KeyboardInterrupt:
869 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200870 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000871 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000872 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000873
cliechti5370cee2013-10-13 03:08:19 +0000874# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000875if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000876 main()