blob: 39591e39ae05170658d3189382fb7325d8906622 [file] [log] [blame]
cliechti576de252002-02-28 23:54:44 +00001#!/usr/bin/env python
Chris Liechtifbdd8a02015-08-09 02:37:45 +02002#
cliechtia128a702004-07-21 22:13:31 +00003# Very simple serial terminal
Chris Liechtifbdd8a02015-08-09 02:37:45 +02004#
Chris Liechti3e02f702015-12-16 23:06:04 +01005# This file is part of pySerial. https://github.com/pyserial/pyserial
Chris Liechti68340d72015-08-03 14:15:48 +02006# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +02007#
8# SPDX-License-Identifier: BSD-3-Clause
cliechtifc9eb382002-03-05 01:12:29 +00009
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020010import codecs
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020011import os
12import sys
13import threading
cliechti576de252002-02-28 23:54:44 +000014
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020015import serial
Chris Liechti55ba7d92015-08-15 16:33:51 +020016from serial.tools.list_ports import comports
Chris Liechti168704f2015-09-30 16:50:29 +020017from serial.tools import hexlify_codec
18
Chris Liechtia887c932016-02-13 23:10:14 +010019# pylint: disable=wrong-import-order,wrong-import-position
20
Chris Liechti168704f2015-09-30 16:50:29 +020021codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
Chris Liechtia1d5c6d2015-08-07 14:41:24 +020022
Chris Liechti68340d72015-08-03 14:15:48 +020023try:
24 raw_input
25except NameError:
Chris Liechtia887c932016-02-13 23:10:14 +010026 # pylint: disable=redefined-builtin,invalid-name
Chris Liechti68340d72015-08-03 14:15:48 +020027 raw_input = input # in python3 it's "raw"
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020028 unichr = chr
Chris Liechti68340d72015-08-03 14:15:48 +020029
cliechti6c8eb2f2009-07-08 02:10:46 +000030
31def key_description(character):
32 """generate a readable description for a key"""
33 ascii_code = ord(character)
34 if ascii_code < 32:
35 return 'Ctrl+%c' % (ord('@') + ascii_code)
36 else:
37 return repr(character)
38
cliechti91165532011-03-18 02:02:52 +000039
Chris Liechti9a720852015-08-25 00:20:38 +020040# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020041class ConsoleBase(object):
Chris Liechti397cf412016-02-11 00:11:48 +010042 """OS abstraction for console (input/output codec, no echo)"""
43
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020044 def __init__(self):
45 if sys.version_info >= (3, 0):
46 self.byte_output = sys.stdout.buffer
47 else:
48 self.byte_output = sys.stdout
49 self.output = sys.stdout
cliechtif467aa82013-10-13 21:36:49 +000050
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020051 def setup(self):
Chris Liechti397cf412016-02-11 00:11:48 +010052 """Set console to read single characters, no echo"""
cliechtif467aa82013-10-13 21:36:49 +000053
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020054 def cleanup(self):
Chris Liechti397cf412016-02-11 00:11:48 +010055 """Restore default console settings"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020056
57 def getkey(self):
Chris Liechti397cf412016-02-11 00:11:48 +010058 """Read a single key from the console"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020059 return None
60
Chris Liechtia887c932016-02-13 23:10:14 +010061 def write_bytes(self, byte_string):
Chris Liechti397cf412016-02-11 00:11:48 +010062 """Write bytes (already encoded)"""
Chris Liechtia887c932016-02-13 23:10:14 +010063 self.byte_output.write(byte_string)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020064 self.byte_output.flush()
65
Chris Liechtia887c932016-02-13 23:10:14 +010066 def write(self, text):
Chris Liechti397cf412016-02-11 00:11:48 +010067 """Write string"""
Chris Liechtia887c932016-02-13 23:10:14 +010068 self.output.write(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020069 self.output.flush()
70
Chris Liechti269f77b2015-08-24 01:31:42 +020071 # - - - - - - - - - - - - - - - - - - - - - - - -
72 # context manager:
73 # switch terminal temporary to normal mode (e.g. to get user input)
74
75 def __enter__(self):
76 self.cleanup()
77 return self
78
79 def __exit__(self, *args, **kwargs):
80 self.setup()
81
cliechti9c592b32008-06-16 22:00:14 +000082
Chris Liechtiba45c522016-02-06 23:53:23 +010083if os.name == 'nt': # noqa
cliechti576de252002-02-28 23:54:44 +000084 import msvcrt
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020085 import ctypes
Chris Liechti9cc696b2015-08-28 00:54:22 +020086
87 class Out(object):
Chris Liechtia887c932016-02-13 23:10:14 +010088 """file-like wrapper that uses os.write"""
89
Chris Liechti9cc696b2015-08-28 00:54:22 +020090 def __init__(self, fd):
91 self.fd = fd
92
93 def flush(self):
94 pass
95
96 def write(self, s):
97 os.write(self.fd, s)
98
Chris Liechtic7a5d4c2015-08-11 23:32:20 +020099 class Console(ConsoleBase):
Chris Liechticbb00b22015-08-13 22:58:49 +0200100 def __init__(self):
101 super(Console, self).__init__()
Chris Liechti1df28272015-08-27 23:37:38 +0200102 self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
103 self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
Chris Liechticbb00b22015-08-13 22:58:49 +0200104 ctypes.windll.kernel32.SetConsoleOutputCP(65001)
105 ctypes.windll.kernel32.SetConsoleCP(65001)
Chris Liechti9cc696b2015-08-28 00:54:22 +0200106 self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
107 # the change of the code page is not propagated to Python, manually fix it
108 sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
109 sys.stdout = self.output
Chris Liechti168704f2015-09-30 16:50:29 +0200110 self.output.encoding = 'UTF-8' # needed for input
Chris Liechticbb00b22015-08-13 22:58:49 +0200111
Chris Liechti1df28272015-08-27 23:37:38 +0200112 def __del__(self):
113 ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
114 ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
115
cliechti3a8bf092008-09-17 11:26:53 +0000116 def getkey(self):
cliechti91165532011-03-18 02:02:52 +0000117 while True:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200118 z = msvcrt.getwch()
Chris Liechti9f398812015-09-13 18:50:44 +0200119 if z == unichr(13):
120 return unichr(10)
121 elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200122 msvcrt.getwch()
cliechti9c592b32008-06-16 22:00:14 +0000123 else:
cliechti9c592b32008-06-16 22:00:14 +0000124 return z
cliechti53edb472009-02-06 21:18:46 +0000125
cliechti576de252002-02-28 23:54:44 +0000126elif os.name == 'posix':
Chris Liechtia1d5c6d2015-08-07 14:41:24 +0200127 import atexit
128 import termios
Chris Liechti9cc696b2015-08-28 00:54:22 +0200129
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200130 class Console(ConsoleBase):
cliechti9c592b32008-06-16 22:00:14 +0000131 def __init__(self):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200132 super(Console, self).__init__()
cliechti9c592b32008-06-16 22:00:14 +0000133 self.fd = sys.stdin.fileno()
Chris Liechti4d989c22015-08-24 00:24:49 +0200134 self.old = termios.tcgetattr(self.fd)
Chris Liechti89eb2472015-08-08 17:06:25 +0200135 atexit.register(self.cleanup)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200136 if sys.version_info < (3, 0):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200137 self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
138 else:
139 self.enc_stdin = sys.stdin
cliechti9c592b32008-06-16 22:00:14 +0000140
141 def setup(self):
cliechti9c592b32008-06-16 22:00:14 +0000142 new = termios.tcgetattr(self.fd)
143 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
144 new[6][termios.VMIN] = 1
145 new[6][termios.VTIME] = 0
146 termios.tcsetattr(self.fd, termios.TCSANOW, new)
cliechti53edb472009-02-06 21:18:46 +0000147
cliechti9c592b32008-06-16 22:00:14 +0000148 def getkey(self):
Chris Liechtia7e7b692015-08-25 21:10:28 +0200149 c = self.enc_stdin.read(1)
Chris Liechti9f398812015-09-13 18:50:44 +0200150 if c == unichr(0x7f):
151 c = unichr(8) # map the BS key (which yields DEL) to backspace
Chris Liechti9a720852015-08-25 00:20:38 +0200152 return c
cliechti53edb472009-02-06 21:18:46 +0000153
cliechti9c592b32008-06-16 22:00:14 +0000154 def cleanup(self):
Chris Liechti4d989c22015-08-24 00:24:49 +0200155 termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
cliechti9c592b32008-06-16 22:00:14 +0000156
cliechti576de252002-02-28 23:54:44 +0000157else:
Chris Liechti397cf412016-02-11 00:11:48 +0100158 raise NotImplementedError(
159 'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
cliechti576de252002-02-28 23:54:44 +0000160
cliechti6fa76fb2009-07-08 23:53:39 +0000161
Chris Liechti9a720852015-08-25 00:20:38 +0200162# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200163
164class Transform(object):
Chris Liechticbb00b22015-08-13 22:58:49 +0200165 """do-nothing: forward all data unchanged"""
Chris Liechtid698af72015-08-24 20:24:55 +0200166 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200167 """text received from serial port"""
168 return text
169
Chris Liechtid698af72015-08-24 20:24:55 +0200170 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200171 """text to be sent to serial port"""
172 return text
173
174 def echo(self, text):
175 """text to be sent but displayed on console"""
176 return text
177
Chris Liechti442bf512015-08-15 01:42:24 +0200178
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200179class CRLF(Transform):
180 """ENTER sends CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200181
Chris Liechtid698af72015-08-24 20:24:55 +0200182 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200183 return text.replace('\n', '\r\n')
184
Chris Liechti442bf512015-08-15 01:42:24 +0200185
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200186class CR(Transform):
187 """ENTER sends CR"""
Chris Liechtid698af72015-08-24 20:24:55 +0200188
189 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200190 return text.replace('\r', '\n')
191
Chris Liechtid698af72015-08-24 20:24:55 +0200192 def tx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200193 return text.replace('\n', '\r')
194
Chris Liechti442bf512015-08-15 01:42:24 +0200195
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200196class LF(Transform):
197 """ENTER sends LF"""
198
199
200class NoTerminal(Transform):
201 """remove typical terminal control codes from input"""
Chris Liechti9a720852015-08-25 00:20:38 +0200202
203 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 +0100204 REPLACEMENT_MAP.update(
205 {
Chris Liechti033f17c2015-08-30 21:28:04 +0200206 0x7F: 0x2421, # DEL
207 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100208 })
Chris Liechti9a720852015-08-25 00:20:38 +0200209
Chris Liechtid698af72015-08-24 20:24:55 +0200210 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200211 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200212
Chris Liechtid698af72015-08-24 20:24:55 +0200213 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200214
215
Chris Liechti9a720852015-08-25 00:20:38 +0200216class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200217 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200218
Chris Liechti9a720852015-08-25 00:20:38 +0200219 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
Chris Liechtiba45c522016-02-06 23:53:23 +0100220 REPLACEMENT_MAP.update(
221 {
Chris Liechtia887c932016-02-13 23:10:14 +0100222 0x20: 0x2423, # visual space
Chris Liechti033f17c2015-08-30 21:28:04 +0200223 0x7F: 0x2421, # DEL
224 0x9B: 0x2425, # CSI
Chris Liechtiba45c522016-02-06 23:53:23 +0100225 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200226
227
228class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200229 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200230
Chris Liechtid698af72015-08-24 20:24:55 +0200231 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200232 r = []
Chris Liechtia887c932016-02-13 23:10:14 +0100233 for c in text:
234 if ' ' <= c < '\x7f' or c in '\r\n\b\t':
235 r.append(c)
236 elif c < ' ':
237 r.append(unichr(0x2400 + ord(c)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200238 else:
Chris Liechtia887c932016-02-13 23:10:14 +0100239 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200240 r.append(' ')
241 return ''.join(r)
242
Chris Liechtid698af72015-08-24 20:24:55 +0200243 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200244
245
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200246class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200247 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200248
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200249 def __init__(self):
250 # XXX make it configurable, use colorama?
251 self.input_color = '\x1b[37m'
252 self.echo_color = '\x1b[31m'
253
Chris Liechtid698af72015-08-24 20:24:55 +0200254 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200255 return self.input_color + text
256
257 def echo(self, text):
258 return self.echo_color + text
259
Chris Liechti442bf512015-08-15 01:42:24 +0200260
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200261class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200262 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200263
Chris Liechtid698af72015-08-24 20:24:55 +0200264 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200265 sys.stderr.write(' [RX:{}] '.format(repr(text)))
266 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200267 return text
268
Chris Liechtid698af72015-08-24 20:24:55 +0200269 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200270 sys.stderr.write(' [TX:{}] '.format(repr(text)))
271 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200272 return text
273
Chris Liechti442bf512015-08-15 01:42:24 +0200274
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200275# other ideas:
276# - add date/time for each newline
277# - insert newline after: a) timeout b) packet end character
278
Chris Liechtib3df13e2015-08-25 02:20:09 +0200279EOL_TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100280 'crlf': CRLF,
281 'cr': CR,
282 'lf': LF,
283}
Chris Liechtib3df13e2015-08-25 02:20:09 +0200284
285TRANSFORMATIONS = {
Chris Liechtiba45c522016-02-06 23:53:23 +0100286 'direct': Transform, # no transformation
287 'default': NoTerminal,
288 'nocontrol': NoControls,
289 'printable': Printable,
290 'colorize': Colorize,
291 'debug': DebugIO,
292}
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200293
294
Chris Liechti033f17c2015-08-30 21:28:04 +0200295# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200296def ask_for_port():
297 """\
298 Show a list of ports and ask the user for a choice. To make selection
299 easier on systems with long device names, also allow the input of an
300 index.
301 """
302 sys.stderr.write('\n--- Available ports:\n')
303 ports = []
304 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
305 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
306 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
307 ports.append(port)
308 while True:
309 port = raw_input('--- Enter port index or full name: ')
310 try:
311 index = int(port) - 1
312 if not 0 <= index < len(ports):
313 sys.stderr.write('--- Invalid index!\n')
314 continue
315 except ValueError:
316 pass
317 else:
318 port = ports[index]
319 return port
cliechti1351dde2012-04-12 16:47:47 +0000320
321
cliechti8c2ea842011-03-18 01:51:46 +0000322class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200323 """\
324 Terminal application. Copy data from serial port to console and vice versa.
325 Handle special keys from the console to show menu etc.
326 """
327
Chris Liechti3b454802015-08-26 23:39:59 +0200328 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200329 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200330 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000331 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200332 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200333 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200334 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200335 self.eol = eol
336 self.filters = filters
337 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200338 self.exit_character = 0x1d # GS/CTRL+]
339 self.menu_character = 0x14 # Menu: CTRL+T
Chris Liechti397cf412016-02-11 00:11:48 +0100340 self.alive = None
341 self._reader_alive = None
342 self.receiver_thread = None
343 self.rx_decoder = None
344 self.tx_decoder = None
cliechti576de252002-02-28 23:54:44 +0000345
cliechti8c2ea842011-03-18 01:51:46 +0000346 def _start_reader(self):
347 """Start reader thread"""
348 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000349 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200350 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
351 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000352 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000353
354 def _stop_reader(self):
355 """Stop reader thread only, wait for clean exit of thread"""
356 self._reader_alive = False
357 self.receiver_thread.join()
358
cliechti8c2ea842011-03-18 01:51:46 +0000359 def start(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100360 """start worker threads"""
cliechti8c2ea842011-03-18 01:51:46 +0000361 self.alive = True
362 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000363 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200364 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
365 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000366 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200367 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000368
cliechti6385f2c2005-09-21 19:51:19 +0000369 def stop(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100370 """set flag to stop worker threads"""
cliechti6385f2c2005-09-21 19:51:19 +0000371 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000372
cliechtibf6bb7d2006-03-30 00:28:18 +0000373 def join(self, transmit_only=False):
Chris Liechtia887c932016-02-13 23:10:14 +0100374 """wait for worker threads to terminate"""
cliechti6385f2c2005-09-21 19:51:19 +0000375 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000376 if not transmit_only:
377 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000378
Chris Liechtib3df13e2015-08-25 02:20:09 +0200379 def update_transformations(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100380 """take list of transformation classes and instantiate them for rx and tx"""
Chris Liechti397cf412016-02-11 00:11:48 +0100381 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
382 for f in self.filters]
Chris Liechtib3df13e2015-08-25 02:20:09 +0200383 self.tx_transformations = [t() for t in transformations]
384 self.rx_transformations = list(reversed(self.tx_transformations))
385
Chris Liechtid698af72015-08-24 20:24:55 +0200386 def set_rx_encoding(self, encoding, errors='replace'):
Chris Liechtia887c932016-02-13 23:10:14 +0100387 """set encoding for received data"""
Chris Liechtid698af72015-08-24 20:24:55 +0200388 self.input_encoding = encoding
389 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
390
391 def set_tx_encoding(self, encoding, errors='replace'):
Chris Liechtia887c932016-02-13 23:10:14 +0100392 """set encoding for transmitted data"""
Chris Liechtid698af72015-08-24 20:24:55 +0200393 self.output_encoding = encoding
394 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
395
cliechti6c8eb2f2009-07-08 02:10:46 +0000396 def dump_port_settings(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100397 """Write current settings to sys.stderr"""
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200398 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
Chris Liechti397cf412016-02-11 00:11:48 +0100399 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200400 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100401 ('active' if self.serial.rts else 'inactive'),
402 ('active' if self.serial.dtr else 'inactive'),
403 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000404 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200405 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100406 ('active' if self.serial.cts else 'inactive'),
407 ('active' if self.serial.dsr else 'inactive'),
408 ('active' if self.serial.ri else 'inactive'),
409 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000410 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200411 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000412 # yet received. ignore this error.
413 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200414 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
415 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
Chris Liechti442bf512015-08-15 01:42:24 +0200416 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
417 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200418 sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
419 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
cliechti6c8eb2f2009-07-08 02:10:46 +0000420
cliechti6385f2c2005-09-21 19:51:19 +0000421 def reader(self):
422 """loop and copy serial->console"""
cliechti6963b262010-01-02 03:01:21 +0000423 try:
cliechti8c2ea842011-03-18 01:51:46 +0000424 while self.alive and self._reader_alive:
Chris Liechti188cf592015-08-22 00:28:19 +0200425 # read all that is there or wait for one byte
Chris Liechti3b454802015-08-26 23:39:59 +0200426 data = self.serial.read(self.serial.in_waiting or 1)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200427 if data:
428 if self.raw:
429 self.console.write_bytes(data)
cliechti6963b262010-01-02 03:01:21 +0000430 else:
Chris Liechtid698af72015-08-24 20:24:55 +0200431 text = self.rx_decoder.decode(data)
Chris Liechtie1384382015-08-15 17:06:05 +0200432 for transformation in self.rx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200433 text = transformation.rx(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200434 self.console.write(text)
Chris Liechti033f17c2015-08-30 21:28:04 +0200435 except serial.SerialException:
cliechti6963b262010-01-02 03:01:21 +0000436 self.alive = False
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200437 # XXX would be nice if the writer could be interrupted at this
438 # point... to exit completely
cliechti6963b262010-01-02 03:01:21 +0000439 raise
cliechti576de252002-02-28 23:54:44 +0000440
cliechti6385f2c2005-09-21 19:51:19 +0000441 def writer(self):
cliechti8c2ea842011-03-18 01:51:46 +0000442 """\
Chris Liechti442bf512015-08-15 01:42:24 +0200443 Loop and copy console->serial until self.exit_character character is
444 found. When self.menu_character is found, interpret the next key
cliechti8c2ea842011-03-18 01:51:46 +0000445 locally.
cliechti6c8eb2f2009-07-08 02:10:46 +0000446 """
447 menu_active = False
448 try:
449 while self.alive:
450 try:
Chris Liechti89eb2472015-08-08 17:06:25 +0200451 c = self.console.getkey()
cliechti6c8eb2f2009-07-08 02:10:46 +0000452 except KeyboardInterrupt:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200453 c = '\x03'
cliechti6c8eb2f2009-07-08 02:10:46 +0000454 if menu_active:
Chris Liechti7af7c752015-08-12 15:45:19 +0200455 self.handle_menu_key(c)
cliechti6c8eb2f2009-07-08 02:10:46 +0000456 menu_active = False
Chris Liechti442bf512015-08-15 01:42:24 +0200457 elif c == self.menu_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200458 menu_active = True # next char will be for menu
Chris Liechti442bf512015-08-15 01:42:24 +0200459 elif c == self.exit_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200460 self.stop() # exit app
461 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000462 else:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200463 #~ if self.raw:
464 text = c
Chris Liechtie1384382015-08-15 17:06:05 +0200465 for transformation in self.tx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200466 text = transformation.tx(text)
Chris Liechtid698af72015-08-24 20:24:55 +0200467 self.serial.write(self.tx_encoder.encode(text))
cliechti6c8eb2f2009-07-08 02:10:46 +0000468 if self.echo:
Chris Liechti3b454802015-08-26 23:39:59 +0200469 echo_text = c
470 for transformation in self.tx_transformations:
471 echo_text = transformation.echo(echo_text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200472 self.console.write(echo_text)
cliechti6c8eb2f2009-07-08 02:10:46 +0000473 except:
474 self.alive = False
475 raise
cliechti6385f2c2005-09-21 19:51:19 +0000476
Chris Liechti7af7c752015-08-12 15:45:19 +0200477 def handle_menu_key(self, c):
478 """Implement a simple menu / settings"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200479 if c == self.menu_character or c == self.exit_character:
480 # Menu/exit character again -> send itself
Chris Liechtid698af72015-08-24 20:24:55 +0200481 self.serial.write(self.tx_encoder.encode(c))
Chris Liechti7af7c752015-08-12 15:45:19 +0200482 if self.echo:
483 self.console.write(c)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200484 elif c == '\x15': # CTRL+U -> upload file
Chris Liechti7af7c752015-08-12 15:45:19 +0200485 sys.stderr.write('\n--- File to upload: ')
486 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200487 with self.console:
488 filename = sys.stdin.readline().rstrip('\r\n')
489 if filename:
490 try:
491 with open(filename, 'rb') as f:
492 sys.stderr.write('--- Sending file {} ---\n'.format(filename))
493 while True:
494 block = f.read(1024)
495 if not block:
496 break
497 self.serial.write(block)
498 # Wait for output buffer to drain.
499 self.serial.flush()
500 sys.stderr.write('.') # Progress indicator.
501 sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
502 except IOError as e:
503 sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200504 elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
Chris Liechti442bf512015-08-15 01:42:24 +0200505 sys.stderr.write(self.get_help_text())
Chris Liechti7af7c752015-08-12 15:45:19 +0200506 elif c == '\x12': # CTRL+R -> Toggle RTS
Chris Liechti3b454802015-08-26 23:39:59 +0200507 self.serial.rts = not self.serial.rts
508 sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200509 elif c == '\x04': # CTRL+D -> Toggle DTR
Chris Liechti3b454802015-08-26 23:39:59 +0200510 self.serial.dtr = not self.serial.dtr
511 sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200512 elif c == '\x02': # CTRL+B -> toggle BREAK condition
Chris Liechti3b454802015-08-26 23:39:59 +0200513 self.serial.break_condition = not self.serial.break_condition
514 sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200515 elif c == '\x05': # CTRL+E -> toggle local echo
516 self.echo = not self.echo
Chris Liechti442bf512015-08-15 01:42:24 +0200517 sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200518 elif c == '\x06': # CTRL+F -> edit filters
519 sys.stderr.write('\n--- Available Filters:\n')
520 sys.stderr.write('\n'.join(
Chris Liechti397cf412016-02-11 00:11:48 +0100521 '--- {:<10} = {.__doc__}'.format(k, v)
522 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200523 sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
524 with self.console:
525 new_filters = sys.stdin.readline().lower().split()
526 if new_filters:
527 for f in new_filters:
528 if f not in TRANSFORMATIONS:
529 sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
530 break
531 else:
532 self.filters = new_filters
533 self.update_transformations()
534 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
535 elif c == '\x0c': # CTRL+L -> EOL mode
Chris Liechti033f17c2015-08-30 21:28:04 +0200536 modes = list(EOL_TRANSFORMATIONS) # keys
Chris Liechtib3df13e2015-08-25 02:20:09 +0200537 eol = modes.index(self.eol) + 1
538 if eol >= len(modes):
539 eol = 0
540 self.eol = modes[eol]
541 sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
542 self.update_transformations()
543 elif c == '\x01': # CTRL+A -> set encoding
544 sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
545 with self.console:
546 new_encoding = sys.stdin.readline().strip()
547 if new_encoding:
548 try:
549 codecs.lookup(new_encoding)
550 except LookupError:
551 sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
552 else:
553 self.set_rx_encoding(new_encoding)
554 self.set_tx_encoding(new_encoding)
555 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
556 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechti7af7c752015-08-12 15:45:19 +0200557 elif c == '\x09': # CTRL+I -> info
558 self.dump_port_settings()
559 #~ elif c == '\x01': # CTRL+A -> cycle escape mode
560 #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
561 elif c in 'pP': # P -> change port
Chris Liechti269f77b2015-08-24 01:31:42 +0200562 with self.console:
563 try:
Chris Liechti89313c92015-09-01 02:33:13 +0200564 port = ask_for_port()
Chris Liechti269f77b2015-08-24 01:31:42 +0200565 except KeyboardInterrupt:
566 port = None
Chris Liechti7af7c752015-08-12 15:45:19 +0200567 if port and port != self.serial.port:
568 # reader thread needs to be shut down
569 self._stop_reader()
570 # save settings
571 settings = self.serial.getSettingsDict()
572 try:
573 new_serial = serial.serial_for_url(port, do_not_open=True)
574 # restore settings and open
575 new_serial.applySettingsDict(settings)
Chris Liechti89313c92015-09-01 02:33:13 +0200576 new_serial.rts = self.serial.rts
577 new_serial.dtr = self.serial.dtr
Chris Liechti7af7c752015-08-12 15:45:19 +0200578 new_serial.open()
Chris Liechti89313c92015-09-01 02:33:13 +0200579 new_serial.break_condition = self.serial.break_condition
Chris Liechti7af7c752015-08-12 15:45:19 +0200580 except Exception as e:
Chris Liechti442bf512015-08-15 01:42:24 +0200581 sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200582 new_serial.close()
583 else:
584 self.serial.close()
585 self.serial = new_serial
Chris Liechti442bf512015-08-15 01:42:24 +0200586 sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
Chris Liechti7af7c752015-08-12 15:45:19 +0200587 # and restart the reader thread
588 self._start_reader()
589 elif c in 'bB': # B -> change baudrate
590 sys.stderr.write('\n--- Baudrate: ')
591 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200592 with self.console:
593 backup = self.serial.baudrate
594 try:
595 self.serial.baudrate = int(sys.stdin.readline().strip())
596 except ValueError as e:
597 sys.stderr.write('--- ERROR setting baudrate: %s ---\n'.format(e))
598 self.serial.baudrate = backup
599 else:
600 self.dump_port_settings()
Chris Liechti7af7c752015-08-12 15:45:19 +0200601 elif c == '8': # 8 -> change to 8 bits
602 self.serial.bytesize = serial.EIGHTBITS
603 self.dump_port_settings()
604 elif c == '7': # 7 -> change to 8 bits
605 self.serial.bytesize = serial.SEVENBITS
606 self.dump_port_settings()
607 elif c in 'eE': # E -> change to even parity
608 self.serial.parity = serial.PARITY_EVEN
609 self.dump_port_settings()
610 elif c in 'oO': # O -> change to odd parity
611 self.serial.parity = serial.PARITY_ODD
612 self.dump_port_settings()
613 elif c in 'mM': # M -> change to mark parity
614 self.serial.parity = serial.PARITY_MARK
615 self.dump_port_settings()
616 elif c in 'sS': # S -> change to space parity
617 self.serial.parity = serial.PARITY_SPACE
618 self.dump_port_settings()
619 elif c in 'nN': # N -> change to no parity
620 self.serial.parity = serial.PARITY_NONE
621 self.dump_port_settings()
622 elif c == '1': # 1 -> change to 1 stop bits
623 self.serial.stopbits = serial.STOPBITS_ONE
624 self.dump_port_settings()
625 elif c == '2': # 2 -> change to 2 stop bits
626 self.serial.stopbits = serial.STOPBITS_TWO
627 self.dump_port_settings()
628 elif c == '3': # 3 -> change to 1.5 stop bits
629 self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
630 self.dump_port_settings()
631 elif c in 'xX': # X -> change software flow control
632 self.serial.xonxoff = (c == 'X')
633 self.dump_port_settings()
634 elif c in 'rR': # R -> change hardware flow control
635 self.serial.rtscts = (c == 'R')
636 self.dump_port_settings()
637 else:
Chris Liechti442bf512015-08-15 01:42:24 +0200638 sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
639
640 def get_help_text(self):
Chris Liechtia887c932016-02-13 23:10:14 +0100641 """return the help text"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200642 # help text, starts with blank line!
Chris Liechti442bf512015-08-15 01:42:24 +0200643 return """
644--- pySerial ({version}) - miniterm - help
645---
646--- {exit:8} Exit program
647--- {menu:8} Menu escape key, followed by:
648--- Menu keys:
649--- {menu:7} Send the menu character itself to remote
650--- {exit:7} Send the exit character itself to remote
651--- {info:7} Show info
652--- {upload:7} Upload file (prompt will be shown)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200653--- {repr:7} encoding
654--- {filter:7} edit filters
Chris Liechti442bf512015-08-15 01:42:24 +0200655--- Toggles:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200656--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
657--- {echo:7} echo {eol:7} EOL
Chris Liechti442bf512015-08-15 01:42:24 +0200658---
Chris Liechti55ba7d92015-08-15 16:33:51 +0200659--- Port settings ({menu} followed by the following):
Chris Liechti442bf512015-08-15 01:42:24 +0200660--- p change port
661--- 7 8 set data bits
Chris Liechtib7550bd2015-08-15 04:09:10 +0200662--- N E O S M change parity (None, Even, Odd, Space, Mark)
Chris Liechti442bf512015-08-15 01:42:24 +0200663--- 1 2 3 set stop bits (1, 2, 1.5)
664--- b change baud rate
665--- x X disable/enable software flow control
666--- r R disable/enable hardware flow control
Chris Liechtia887c932016-02-13 23:10:14 +0100667""".format(version=getattr(serial, 'VERSION', 'unknown version'),
668 exit=key_description(self.exit_character),
669 menu=key_description(self.menu_character),
670 rts=key_description('\x12'),
671 dtr=key_description('\x04'),
672 brk=key_description('\x02'),
673 echo=key_description('\x05'),
674 info=key_description('\x09'),
675 upload=key_description('\x15'),
676 repr=key_description('\x01'),
677 filter=key_description('\x06'),
678 eol=key_description('\x0c'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200679
680
Chris Liechtib3df13e2015-08-25 02:20:09 +0200681# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti55ba7d92015-08-15 16:33:51 +0200682# default args can be used to override when calling main() from an other script
683# e.g to create a miniterm-my-device.py
684def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
Chris Liechtia887c932016-02-13 23:10:14 +0100685 """Command line tool, entry point"""
686
Chris Liechtib7550bd2015-08-15 04:09:10 +0200687 import argparse
cliechti6385f2c2005-09-21 19:51:19 +0000688
Chris Liechtib7550bd2015-08-15 04:09:10 +0200689 parser = argparse.ArgumentParser(
Chris Liechti397cf412016-02-11 00:11:48 +0100690 description="Miniterm - A simple terminal program for the serial port.")
cliechti6385f2c2005-09-21 19:51:19 +0000691
Chris Liechti033f17c2015-08-30 21:28:04 +0200692 parser.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100693 "port",
694 nargs='?',
695 help="serial port name ('-' to show port list)",
696 default=default_port)
cliechti5370cee2013-10-13 03:08:19 +0000697
Chris Liechti033f17c2015-08-30 21:28:04 +0200698 parser.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100699 "baudrate",
700 nargs='?',
701 type=int,
702 help="set baud rate, default: %(default)s",
703 default=default_baudrate)
cliechti6385f2c2005-09-21 19:51:19 +0000704
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200705 group = parser.add_argument_group("port settings")
cliechti53edb472009-02-06 21:18:46 +0000706
Chris Liechti033f17c2015-08-30 21:28:04 +0200707 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100708 "--parity",
709 choices=['N', 'E', 'O', 'S', 'M'],
710 type=lambda c: c.upper(),
711 help="set parity, one of {N E O S M}, default: N",
712 default='N')
cliechti53edb472009-02-06 21:18:46 +0000713
Chris Liechti033f17c2015-08-30 21:28:04 +0200714 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100715 "--rtscts",
716 action="store_true",
717 help="enable RTS/CTS flow control (default off)",
718 default=False)
cliechti53edb472009-02-06 21:18:46 +0000719
Chris Liechti033f17c2015-08-30 21:28:04 +0200720 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100721 "--xonxoff",
722 action="store_true",
723 help="enable software flow control (default off)",
724 default=False)
cliechti53edb472009-02-06 21:18:46 +0000725
Chris Liechti033f17c2015-08-30 21:28:04 +0200726 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100727 "--rts",
728 type=int,
729 help="set initial RTS line state (possible values: 0, 1)",
730 default=default_rts)
cliechti5370cee2013-10-13 03:08:19 +0000731
Chris Liechti033f17c2015-08-30 21:28:04 +0200732 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100733 "--dtr",
734 type=int,
735 help="set initial DTR line state (possible values: 0, 1)",
736 default=default_dtr)
cliechti5370cee2013-10-13 03:08:19 +0000737
Chris Liechti00f84282015-12-24 23:40:34 +0100738 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100739 "--ask",
740 action="store_true",
741 help="ask again for port when open fails",
742 default=False)
Chris Liechti00f84282015-12-24 23:40:34 +0100743
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200744 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000745
Chris Liechti033f17c2015-08-30 21:28:04 +0200746 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100747 "-e", "--echo",
748 action="store_true",
749 help="enable local echo (default off)",
750 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000751
Chris Liechti033f17c2015-08-30 21:28:04 +0200752 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100753 "--encoding",
754 dest="serial_port_encoding",
755 metavar="CODEC",
756 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
757 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000758
Chris Liechti033f17c2015-08-30 21:28:04 +0200759 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100760 "-f", "--filter",
761 action="append",
762 metavar="NAME",
763 help="add text transformation",
764 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200765
Chris Liechti033f17c2015-08-30 21:28:04 +0200766 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100767 "--eol",
768 choices=['CR', 'LF', 'CRLF'],
769 type=lambda c: c.upper(),
770 help="end of line mode",
771 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000772
Chris Liechti033f17c2015-08-30 21:28:04 +0200773 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100774 "--raw",
775 action="store_true",
776 help="Do no apply any encodings/transformations",
777 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000778
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200779 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000780
Chris Liechti033f17c2015-08-30 21:28:04 +0200781 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100782 "--exit-char",
783 type=int,
784 metavar='NUM',
785 help="Unicode of special character that is used to exit the application, default: %(default)s",
786 default=0x1d) # GS/CTRL+]
cliechtibf6bb7d2006-03-30 00:28:18 +0000787
Chris Liechti033f17c2015-08-30 21:28:04 +0200788 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100789 "--menu-char",
790 type=int,
791 metavar='NUM',
792 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
793 default=0x14) # Menu: CTRL+T
cliechti9c592b32008-06-16 22:00:14 +0000794
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200795 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000796
Chris Liechti033f17c2015-08-30 21:28:04 +0200797 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100798 "-q", "--quiet",
799 action="store_true",
800 help="suppress non-error messages",
801 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000802
Chris Liechti033f17c2015-08-30 21:28:04 +0200803 group.add_argument(
Chris Liechti397cf412016-02-11 00:11:48 +0100804 "--develop",
805 action="store_true",
806 help="show Python traceback on error",
807 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000808
Chris Liechtib7550bd2015-08-15 04:09:10 +0200809 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000810
Chris Liechtib7550bd2015-08-15 04:09:10 +0200811 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000812 parser.error('--exit-char can not be the same as --menu-char')
813
Chris Liechtib3df13e2015-08-25 02:20:09 +0200814 if args.filter:
815 if 'help' in args.filter:
816 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200817 sys.stderr.write('\n'.join(
Chris Liechti397cf412016-02-11 00:11:48 +0100818 '{:<10} = {.__doc__}'.format(k, v)
819 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200820 sys.stderr.write('\n')
821 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200822 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200823 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200824 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200825
Chris Liechti00f84282015-12-24 23:40:34 +0100826 while True:
827 # no port given on command line -> ask user now
828 if args.port is None or args.port == '-':
829 try:
830 args.port = ask_for_port()
831 except KeyboardInterrupt:
832 sys.stderr.write('\n')
833 parser.error('user aborted and port is not given')
834 else:
835 if not args.port:
836 parser.error('port is not given')
837 try:
838 serial_instance = serial.serial_for_url(
Chris Liechti397cf412016-02-11 00:11:48 +0100839 args.port,
840 args.baudrate,
841 parity=args.parity,
842 rtscts=args.rtscts,
843 xonxoff=args.xonxoff,
844 timeout=1,
845 do_not_open=True)
Chris Liechti3b454802015-08-26 23:39:59 +0200846
Chris Liechti00f84282015-12-24 23:40:34 +0100847 if args.dtr is not None:
848 if not args.quiet:
849 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
850 serial_instance.dtr = args.dtr
851 if args.rts is not None:
852 if not args.quiet:
853 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
854 serial_instance.rts = args.rts
Chris Liechti3b454802015-08-26 23:39:59 +0200855
Chris Liechti00f84282015-12-24 23:40:34 +0100856 serial_instance.open()
857 except serial.SerialException as e:
858 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
859 if args.develop:
860 raise
861 if not args.ask:
862 sys.exit(1)
863 else:
864 args.port = '-'
865 else:
866 break
cliechti6385f2c2005-09-21 19:51:19 +0000867
Chris Liechti3b454802015-08-26 23:39:59 +0200868 miniterm = Miniterm(
Chris Liechti397cf412016-02-11 00:11:48 +0100869 serial_instance,
870 echo=args.echo,
871 eol=args.eol.lower(),
872 filters=filters)
Chris Liechti3b454802015-08-26 23:39:59 +0200873 miniterm.exit_character = unichr(args.exit_char)
874 miniterm.menu_character = unichr(args.menu_char)
875 miniterm.raw = args.raw
876 miniterm.set_rx_encoding(args.serial_port_encoding)
877 miniterm.set_tx_encoding(args.serial_port_encoding)
878
Chris Liechtib7550bd2015-08-15 04:09:10 +0200879 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200880 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100881 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200882 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti397cf412016-02-11 00:11:48 +0100883 key_description(miniterm.exit_character),
884 key_description(miniterm.menu_character),
885 key_description(miniterm.menu_character),
886 key_description('\x08')))
cliechti6fa76fb2009-07-08 23:53:39 +0000887
cliechti6385f2c2005-09-21 19:51:19 +0000888 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000889 try:
890 miniterm.join(True)
891 except KeyboardInterrupt:
892 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200893 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000894 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000895 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000896
cliechti5370cee2013-10-13 03:08:19 +0000897# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000898if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000899 main()