blob: ac1773d4bfd3c2e60982f958cb68eab8e3e4f9f5 [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
cliechtifc9eb382002-03-05 01:12:29 +000075if os.name == 'nt':
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')
193 REPLACEMENT_MAP.update({
Chris Liechti033f17c2015-08-30 21:28:04 +0200194 0x7F: 0x2421, # DEL
195 0x9B: 0x2425, # CSI
Chris Liechti9a720852015-08-25 00:20:38 +0200196 })
197
Chris Liechtid698af72015-08-24 20:24:55 +0200198 def rx(self, text):
Chris Liechti9a720852015-08-25 00:20:38 +0200199 return text.translate(self.REPLACEMENT_MAP)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200200
Chris Liechtid698af72015-08-24 20:24:55 +0200201 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200202
203
Chris Liechti9a720852015-08-25 00:20:38 +0200204class NoControls(NoTerminal):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200205 """Remove all control codes, incl. CR+LF"""
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200206
Chris Liechti9a720852015-08-25 00:20:38 +0200207 REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
208 REPLACEMENT_MAP.update({
Chris Liechti033f17c2015-08-30 21:28:04 +0200209 32: 0x2423, # visual space
210 0x7F: 0x2421, # DEL
211 0x9B: 0x2425, # CSI
Chris Liechti9a720852015-08-25 00:20:38 +0200212 })
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200213
214
215class Printable(Transform):
Chris Liechtid698af72015-08-24 20:24:55 +0200216 """Show decimal code for all non-ASCII characters and replace most control codes"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200217
Chris Liechtid698af72015-08-24 20:24:55 +0200218 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200219 r = []
220 for t in text:
Chris Liechti7e9cfd42015-08-12 15:28:19 +0200221 if ' ' <= t < '\x7f' or t in '\r\n\b\t':
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200222 r.append(t)
Chris Liechtid698af72015-08-24 20:24:55 +0200223 elif t < ' ':
224 r.append(unichr(0x2400 + ord(t)))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200225 else:
226 r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(t)))
227 r.append(' ')
228 return ''.join(r)
229
Chris Liechtid698af72015-08-24 20:24:55 +0200230 echo = rx
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200231
232
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200233class Colorize(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200234 """Apply different colors for received and echo"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200235
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200236 def __init__(self):
237 # XXX make it configurable, use colorama?
238 self.input_color = '\x1b[37m'
239 self.echo_color = '\x1b[31m'
240
Chris Liechtid698af72015-08-24 20:24:55 +0200241 def rx(self, text):
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200242 return self.input_color + text
243
244 def echo(self, text):
245 return self.echo_color + text
246
Chris Liechti442bf512015-08-15 01:42:24 +0200247
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200248class DebugIO(Transform):
Chris Liechti442bf512015-08-15 01:42:24 +0200249 """Print what is sent and received"""
Chris Liechtic0c660a2015-08-25 00:55:51 +0200250
Chris Liechtid698af72015-08-24 20:24:55 +0200251 def rx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200252 sys.stderr.write(' [RX:{}] '.format(repr(text)))
253 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200254 return text
255
Chris Liechtid698af72015-08-24 20:24:55 +0200256 def tx(self, text):
Chris Liechtie1384382015-08-15 17:06:05 +0200257 sys.stderr.write(' [TX:{}] '.format(repr(text)))
258 sys.stderr.flush()
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200259 return text
260
Chris Liechti442bf512015-08-15 01:42:24 +0200261
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200262# other ideas:
263# - add date/time for each newline
264# - insert newline after: a) timeout b) packet end character
265
Chris Liechtib3df13e2015-08-25 02:20:09 +0200266EOL_TRANSFORMATIONS = {
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200267 'crlf': CRLF,
268 'cr': CR,
269 'lf': LF,
Chris Liechtib3df13e2015-08-25 02:20:09 +0200270 }
271
272TRANSFORMATIONS = {
Chris Liechticbb00b22015-08-13 22:58:49 +0200273 'direct': Transform, # no transformation
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200274 'default': NoTerminal,
275 'nocontrol': NoControls,
276 'printable': Printable,
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200277 'colorize': Colorize,
278 'debug': DebugIO,
279 }
280
281
Chris Liechti033f17c2015-08-30 21:28:04 +0200282# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti89313c92015-09-01 02:33:13 +0200283def ask_for_port():
284 """\
285 Show a list of ports and ask the user for a choice. To make selection
286 easier on systems with long device names, also allow the input of an
287 index.
288 """
289 sys.stderr.write('\n--- Available ports:\n')
290 ports = []
291 for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
292 #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
293 sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
294 ports.append(port)
295 while True:
296 port = raw_input('--- Enter port index or full name: ')
297 try:
298 index = int(port) - 1
299 if not 0 <= index < len(ports):
300 sys.stderr.write('--- Invalid index!\n')
301 continue
302 except ValueError:
303 pass
304 else:
305 port = ports[index]
306 return port
cliechti1351dde2012-04-12 16:47:47 +0000307
308
cliechti8c2ea842011-03-18 01:51:46 +0000309class Miniterm(object):
Chris Liechti89313c92015-09-01 02:33:13 +0200310 """\
311 Terminal application. Copy data from serial port to console and vice versa.
312 Handle special keys from the console to show menu etc.
313 """
314
Chris Liechti3b454802015-08-26 23:39:59 +0200315 def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
Chris Liechti89eb2472015-08-08 17:06:25 +0200316 self.console = Console()
Chris Liechti3b454802015-08-26 23:39:59 +0200317 self.serial = serial_instance
cliechti6385f2c2005-09-21 19:51:19 +0000318 self.echo = echo
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200319 self.raw = False
Chris Liechti442bf512015-08-15 01:42:24 +0200320 self.input_encoding = 'UTF-8'
Chris Liechti442bf512015-08-15 01:42:24 +0200321 self.output_encoding = 'UTF-8'
Chris Liechtib3df13e2015-08-25 02:20:09 +0200322 self.eol = eol
323 self.filters = filters
324 self.update_transformations()
Chris Liechti442bf512015-08-15 01:42:24 +0200325 self.exit_character = 0x1d # GS/CTRL+]
326 self.menu_character = 0x14 # Menu: CTRL+T
cliechti576de252002-02-28 23:54:44 +0000327
cliechti8c2ea842011-03-18 01:51:46 +0000328 def _start_reader(self):
329 """Start reader thread"""
330 self._reader_alive = True
cliechti6fa76fb2009-07-08 23:53:39 +0000331 # start serial->console thread
Chris Liechti55ba7d92015-08-15 16:33:51 +0200332 self.receiver_thread = threading.Thread(target=self.reader, name='rx')
333 self.receiver_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000334 self.receiver_thread.start()
cliechti8c2ea842011-03-18 01:51:46 +0000335
336 def _stop_reader(self):
337 """Stop reader thread only, wait for clean exit of thread"""
338 self._reader_alive = False
339 self.receiver_thread.join()
340
cliechti8c2ea842011-03-18 01:51:46 +0000341 def start(self):
342 self.alive = True
343 self._start_reader()
cliechti6fa76fb2009-07-08 23:53:39 +0000344 # enter console->serial loop
Chris Liechti55ba7d92015-08-15 16:33:51 +0200345 self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
346 self.transmitter_thread.daemon = True
cliechti6385f2c2005-09-21 19:51:19 +0000347 self.transmitter_thread.start()
Chris Liechti89eb2472015-08-08 17:06:25 +0200348 self.console.setup()
cliechti53edb472009-02-06 21:18:46 +0000349
cliechti6385f2c2005-09-21 19:51:19 +0000350 def stop(self):
351 self.alive = False
cliechti53edb472009-02-06 21:18:46 +0000352
cliechtibf6bb7d2006-03-30 00:28:18 +0000353 def join(self, transmit_only=False):
cliechti6385f2c2005-09-21 19:51:19 +0000354 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000355 if not transmit_only:
356 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +0000357
Chris Liechtib3df13e2015-08-25 02:20:09 +0200358 def update_transformations(self):
359 transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] for f in self.filters]
360 self.tx_transformations = [t() for t in transformations]
361 self.rx_transformations = list(reversed(self.tx_transformations))
362
Chris Liechtid698af72015-08-24 20:24:55 +0200363 def set_rx_encoding(self, encoding, errors='replace'):
364 self.input_encoding = encoding
365 self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
366
367 def set_tx_encoding(self, encoding, errors='replace'):
368 self.output_encoding = encoding
369 self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
370
cliechti6c8eb2f2009-07-08 02:10:46 +0000371 def dump_port_settings(self):
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200372 sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
373 p=self.serial))
Chris Liechti442bf512015-08-15 01:42:24 +0200374 sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200375 ('active' if self.serial.rts else 'inactive'),
376 ('active' if self.serial.dtr else 'inactive'),
377 ('active' if self.serial.break_condition else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000378 try:
Chris Liechti442bf512015-08-15 01:42:24 +0200379 sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
Chris Liechti3b454802015-08-26 23:39:59 +0200380 ('active' if self.serial.cts else 'inactive'),
381 ('active' if self.serial.dsr else 'inactive'),
382 ('active' if self.serial.ri else 'inactive'),
383 ('active' if self.serial.cd else 'inactive')))
cliechti10114572009-08-05 23:40:50 +0000384 except serial.SerialException:
Chris Liechti55ba7d92015-08-15 16:33:51 +0200385 # on RFC 2217 ports, it can happen if no modem state notification was
cliechti10114572009-08-05 23:40:50 +0000386 # yet received. ignore this error.
387 pass
Chris Liechti442bf512015-08-15 01:42:24 +0200388 sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
389 sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200390 #~ sys.stderr.write('--- data escaping: %s linefeed: %s\n' % (
391 #~ REPR_MODES[self.repr_mode],
392 #~ LF_MODES[self.convert_outgoing]))
Chris Liechti442bf512015-08-15 01:42:24 +0200393 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
394 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200395 sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
396 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
cliechti6c8eb2f2009-07-08 02:10:46 +0000397
cliechti6385f2c2005-09-21 19:51:19 +0000398 def reader(self):
399 """loop and copy serial->console"""
cliechti6963b262010-01-02 03:01:21 +0000400 try:
cliechti8c2ea842011-03-18 01:51:46 +0000401 while self.alive and self._reader_alive:
Chris Liechti188cf592015-08-22 00:28:19 +0200402 # read all that is there or wait for one byte
Chris Liechti3b454802015-08-26 23:39:59 +0200403 data = self.serial.read(self.serial.in_waiting or 1)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200404 if data:
405 if self.raw:
406 self.console.write_bytes(data)
cliechti6963b262010-01-02 03:01:21 +0000407 else:
Chris Liechtid698af72015-08-24 20:24:55 +0200408 text = self.rx_decoder.decode(data)
Chris Liechtie1384382015-08-15 17:06:05 +0200409 for transformation in self.rx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200410 text = transformation.rx(text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200411 self.console.write(text)
Chris Liechti033f17c2015-08-30 21:28:04 +0200412 except serial.SerialException:
cliechti6963b262010-01-02 03:01:21 +0000413 self.alive = False
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200414 # XXX would be nice if the writer could be interrupted at this
415 # point... to exit completely
cliechti6963b262010-01-02 03:01:21 +0000416 raise
cliechti576de252002-02-28 23:54:44 +0000417
cliechti6385f2c2005-09-21 19:51:19 +0000418 def writer(self):
cliechti8c2ea842011-03-18 01:51:46 +0000419 """\
Chris Liechti442bf512015-08-15 01:42:24 +0200420 Loop and copy console->serial until self.exit_character character is
421 found. When self.menu_character is found, interpret the next key
cliechti8c2ea842011-03-18 01:51:46 +0000422 locally.
cliechti6c8eb2f2009-07-08 02:10:46 +0000423 """
424 menu_active = False
425 try:
426 while self.alive:
427 try:
Chris Liechti89eb2472015-08-08 17:06:25 +0200428 c = self.console.getkey()
cliechti6c8eb2f2009-07-08 02:10:46 +0000429 except KeyboardInterrupt:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200430 c = '\x03'
cliechti6c8eb2f2009-07-08 02:10:46 +0000431 if menu_active:
Chris Liechti7af7c752015-08-12 15:45:19 +0200432 self.handle_menu_key(c)
cliechti6c8eb2f2009-07-08 02:10:46 +0000433 menu_active = False
Chris Liechti442bf512015-08-15 01:42:24 +0200434 elif c == self.menu_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200435 menu_active = True # next char will be for menu
Chris Liechti442bf512015-08-15 01:42:24 +0200436 elif c == self.exit_character:
Chris Liechti7af7c752015-08-12 15:45:19 +0200437 self.stop() # exit app
438 break
cliechti6c8eb2f2009-07-08 02:10:46 +0000439 else:
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200440 #~ if self.raw:
441 text = c
Chris Liechtie1384382015-08-15 17:06:05 +0200442 for transformation in self.tx_transformations:
Chris Liechtid698af72015-08-24 20:24:55 +0200443 text = transformation.tx(text)
Chris Liechtid698af72015-08-24 20:24:55 +0200444 self.serial.write(self.tx_encoder.encode(text))
cliechti6c8eb2f2009-07-08 02:10:46 +0000445 if self.echo:
Chris Liechti3b454802015-08-26 23:39:59 +0200446 echo_text = c
447 for transformation in self.tx_transformations:
448 echo_text = transformation.echo(echo_text)
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200449 self.console.write(echo_text)
cliechti6c8eb2f2009-07-08 02:10:46 +0000450 except:
451 self.alive = False
452 raise
cliechti6385f2c2005-09-21 19:51:19 +0000453
Chris Liechti7af7c752015-08-12 15:45:19 +0200454 def handle_menu_key(self, c):
455 """Implement a simple menu / settings"""
Chris Liechti55ba7d92015-08-15 16:33:51 +0200456 if c == self.menu_character or c == self.exit_character:
457 # Menu/exit character again -> send itself
Chris Liechtid698af72015-08-24 20:24:55 +0200458 self.serial.write(self.tx_encoder.encode(c))
Chris Liechti7af7c752015-08-12 15:45:19 +0200459 if self.echo:
460 self.console.write(c)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200461 elif c == '\x15': # CTRL+U -> upload file
Chris Liechti7af7c752015-08-12 15:45:19 +0200462 sys.stderr.write('\n--- File to upload: ')
463 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200464 with self.console:
465 filename = sys.stdin.readline().rstrip('\r\n')
466 if filename:
467 try:
468 with open(filename, 'rb') as f:
469 sys.stderr.write('--- Sending file {} ---\n'.format(filename))
470 while True:
471 block = f.read(1024)
472 if not block:
473 break
474 self.serial.write(block)
475 # Wait for output buffer to drain.
476 self.serial.flush()
477 sys.stderr.write('.') # Progress indicator.
478 sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
479 except IOError as e:
480 sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200481 elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
Chris Liechti442bf512015-08-15 01:42:24 +0200482 sys.stderr.write(self.get_help_text())
Chris Liechti7af7c752015-08-12 15:45:19 +0200483 elif c == '\x12': # CTRL+R -> Toggle RTS
Chris Liechti3b454802015-08-26 23:39:59 +0200484 self.serial.rts = not self.serial.rts
485 sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200486 elif c == '\x04': # CTRL+D -> Toggle DTR
Chris Liechti3b454802015-08-26 23:39:59 +0200487 self.serial.dtr = not self.serial.dtr
488 sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200489 elif c == '\x02': # CTRL+B -> toggle BREAK condition
Chris Liechti3b454802015-08-26 23:39:59 +0200490 self.serial.break_condition = not self.serial.break_condition
491 sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
Chris Liechti7af7c752015-08-12 15:45:19 +0200492 elif c == '\x05': # CTRL+E -> toggle local echo
493 self.echo = not self.echo
Chris Liechti442bf512015-08-15 01:42:24 +0200494 sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
Chris Liechtib3df13e2015-08-25 02:20:09 +0200495 elif c == '\x06': # CTRL+F -> edit filters
496 sys.stderr.write('\n--- Available Filters:\n')
497 sys.stderr.write('\n'.join(
498 '--- {:<10} = {.__doc__}'.format(k, v)
499 for k, v in sorted(TRANSFORMATIONS.items())))
500 sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
501 with self.console:
502 new_filters = sys.stdin.readline().lower().split()
503 if new_filters:
504 for f in new_filters:
505 if f not in TRANSFORMATIONS:
506 sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
507 break
508 else:
509 self.filters = new_filters
510 self.update_transformations()
511 sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
512 elif c == '\x0c': # CTRL+L -> EOL mode
Chris Liechti033f17c2015-08-30 21:28:04 +0200513 modes = list(EOL_TRANSFORMATIONS) # keys
Chris Liechtib3df13e2015-08-25 02:20:09 +0200514 eol = modes.index(self.eol) + 1
515 if eol >= len(modes):
516 eol = 0
517 self.eol = modes[eol]
518 sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
519 self.update_transformations()
520 elif c == '\x01': # CTRL+A -> set encoding
521 sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
522 with self.console:
523 new_encoding = sys.stdin.readline().strip()
524 if new_encoding:
525 try:
526 codecs.lookup(new_encoding)
527 except LookupError:
528 sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
529 else:
530 self.set_rx_encoding(new_encoding)
531 self.set_tx_encoding(new_encoding)
532 sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
533 sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
Chris Liechti7af7c752015-08-12 15:45:19 +0200534 elif c == '\x09': # CTRL+I -> info
535 self.dump_port_settings()
536 #~ elif c == '\x01': # CTRL+A -> cycle escape mode
537 #~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
538 elif c in 'pP': # P -> change port
Chris Liechti269f77b2015-08-24 01:31:42 +0200539 with self.console:
540 try:
Chris Liechti89313c92015-09-01 02:33:13 +0200541 port = ask_for_port()
Chris Liechti269f77b2015-08-24 01:31:42 +0200542 except KeyboardInterrupt:
543 port = None
Chris Liechti7af7c752015-08-12 15:45:19 +0200544 if port and port != self.serial.port:
545 # reader thread needs to be shut down
546 self._stop_reader()
547 # save settings
548 settings = self.serial.getSettingsDict()
549 try:
550 new_serial = serial.serial_for_url(port, do_not_open=True)
551 # restore settings and open
552 new_serial.applySettingsDict(settings)
Chris Liechti89313c92015-09-01 02:33:13 +0200553 new_serial.rts = self.serial.rts
554 new_serial.dtr = self.serial.dtr
Chris Liechti7af7c752015-08-12 15:45:19 +0200555 new_serial.open()
Chris Liechti89313c92015-09-01 02:33:13 +0200556 new_serial.break_condition = self.serial.break_condition
Chris Liechti7af7c752015-08-12 15:45:19 +0200557 except Exception as e:
Chris Liechti442bf512015-08-15 01:42:24 +0200558 sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
Chris Liechti7af7c752015-08-12 15:45:19 +0200559 new_serial.close()
560 else:
561 self.serial.close()
562 self.serial = new_serial
Chris Liechti442bf512015-08-15 01:42:24 +0200563 sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
Chris Liechti7af7c752015-08-12 15:45:19 +0200564 # and restart the reader thread
565 self._start_reader()
566 elif c in 'bB': # B -> change baudrate
567 sys.stderr.write('\n--- Baudrate: ')
568 sys.stderr.flush()
Chris Liechti269f77b2015-08-24 01:31:42 +0200569 with self.console:
570 backup = self.serial.baudrate
571 try:
572 self.serial.baudrate = int(sys.stdin.readline().strip())
573 except ValueError as e:
574 sys.stderr.write('--- ERROR setting baudrate: %s ---\n'.format(e))
575 self.serial.baudrate = backup
576 else:
577 self.dump_port_settings()
Chris Liechti7af7c752015-08-12 15:45:19 +0200578 elif c == '8': # 8 -> change to 8 bits
579 self.serial.bytesize = serial.EIGHTBITS
580 self.dump_port_settings()
581 elif c == '7': # 7 -> change to 8 bits
582 self.serial.bytesize = serial.SEVENBITS
583 self.dump_port_settings()
584 elif c in 'eE': # E -> change to even parity
585 self.serial.parity = serial.PARITY_EVEN
586 self.dump_port_settings()
587 elif c in 'oO': # O -> change to odd parity
588 self.serial.parity = serial.PARITY_ODD
589 self.dump_port_settings()
590 elif c in 'mM': # M -> change to mark parity
591 self.serial.parity = serial.PARITY_MARK
592 self.dump_port_settings()
593 elif c in 'sS': # S -> change to space parity
594 self.serial.parity = serial.PARITY_SPACE
595 self.dump_port_settings()
596 elif c in 'nN': # N -> change to no parity
597 self.serial.parity = serial.PARITY_NONE
598 self.dump_port_settings()
599 elif c == '1': # 1 -> change to 1 stop bits
600 self.serial.stopbits = serial.STOPBITS_ONE
601 self.dump_port_settings()
602 elif c == '2': # 2 -> change to 2 stop bits
603 self.serial.stopbits = serial.STOPBITS_TWO
604 self.dump_port_settings()
605 elif c == '3': # 3 -> change to 1.5 stop bits
606 self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
607 self.dump_port_settings()
608 elif c in 'xX': # X -> change software flow control
609 self.serial.xonxoff = (c == 'X')
610 self.dump_port_settings()
611 elif c in 'rR': # R -> change hardware flow control
612 self.serial.rtscts = (c == 'R')
613 self.dump_port_settings()
614 else:
Chris Liechti442bf512015-08-15 01:42:24 +0200615 sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
616
617 def get_help_text(self):
Chris Liechti55ba7d92015-08-15 16:33:51 +0200618 # help text, starts with blank line!
Chris Liechti442bf512015-08-15 01:42:24 +0200619 return """
620--- pySerial ({version}) - miniterm - help
621---
622--- {exit:8} Exit program
623--- {menu:8} Menu escape key, followed by:
624--- Menu keys:
625--- {menu:7} Send the menu character itself to remote
626--- {exit:7} Send the exit character itself to remote
627--- {info:7} Show info
628--- {upload:7} Upload file (prompt will be shown)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200629--- {repr:7} encoding
630--- {filter:7} edit filters
Chris Liechti442bf512015-08-15 01:42:24 +0200631--- Toggles:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200632--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
633--- {echo:7} echo {eol:7} EOL
Chris Liechti442bf512015-08-15 01:42:24 +0200634---
Chris Liechti55ba7d92015-08-15 16:33:51 +0200635--- Port settings ({menu} followed by the following):
Chris Liechti442bf512015-08-15 01:42:24 +0200636--- p change port
637--- 7 8 set data bits
Chris Liechtib7550bd2015-08-15 04:09:10 +0200638--- N E O S M change parity (None, Even, Odd, Space, Mark)
Chris Liechti442bf512015-08-15 01:42:24 +0200639--- 1 2 3 set stop bits (1, 2, 1.5)
640--- b change baud rate
641--- x X disable/enable software flow control
642--- r R disable/enable hardware flow control
643""".format(
644 version=getattr(serial, 'VERSION', 'unknown version'),
645 exit=key_description(self.exit_character),
646 menu=key_description(self.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200647 rts=key_description('\x12'),
648 dtr=key_description('\x04'),
649 brk=key_description('\x02'),
650 echo=key_description('\x05'),
651 info=key_description('\x09'),
652 upload=key_description('\x15'),
Chris Liechtib3df13e2015-08-25 02:20:09 +0200653 repr=key_description('\x01'),
654 filter=key_description('\x06'),
655 eol=key_description('\x0c'),
Chris Liechti442bf512015-08-15 01:42:24 +0200656 )
Chris Liechti7af7c752015-08-12 15:45:19 +0200657
658
Chris Liechtib3df13e2015-08-25 02:20:09 +0200659# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chris Liechti55ba7d92015-08-15 16:33:51 +0200660# default args can be used to override when calling main() from an other script
661# e.g to create a miniterm-my-device.py
662def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
Chris Liechtib7550bd2015-08-15 04:09:10 +0200663 import argparse
cliechti6385f2c2005-09-21 19:51:19 +0000664
Chris Liechtib7550bd2015-08-15 04:09:10 +0200665 parser = argparse.ArgumentParser(
666 description="Miniterm - A simple terminal program for the serial port.")
cliechti6385f2c2005-09-21 19:51:19 +0000667
Chris Liechti033f17c2015-08-30 21:28:04 +0200668 parser.add_argument(
669 "port",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200670 nargs='?',
Chris Liechti7cf82762015-09-01 02:56:04 +0200671 help="serial port name ('-' to show port list)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200672 default=default_port)
cliechti5370cee2013-10-13 03:08:19 +0000673
Chris Liechti033f17c2015-08-30 21:28:04 +0200674 parser.add_argument(
675 "baudrate",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200676 nargs='?',
677 type=int,
678 help="set baud rate, default: %(default)s",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200679 default=default_baudrate)
cliechti6385f2c2005-09-21 19:51:19 +0000680
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200681 group = parser.add_argument_group("port settings")
cliechti53edb472009-02-06 21:18:46 +0000682
Chris Liechti033f17c2015-08-30 21:28:04 +0200683 group.add_argument(
684 "--parity",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200685 choices=['N', 'E', 'O', 'S', 'M'],
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200686 type=lambda c: c.upper(),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200687 help="set parity, one of {N E O S M}, default: N",
688 default='N')
cliechti53edb472009-02-06 21:18:46 +0000689
Chris Liechti033f17c2015-08-30 21:28:04 +0200690 group.add_argument(
691 "--rtscts",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200692 action="store_true",
693 help="enable RTS/CTS flow control (default off)",
694 default=False)
cliechti53edb472009-02-06 21:18:46 +0000695
Chris Liechti033f17c2015-08-30 21:28:04 +0200696 group.add_argument(
697 "--xonxoff",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200698 action="store_true",
699 help="enable software flow control (default off)",
700 default=False)
cliechti53edb472009-02-06 21:18:46 +0000701
Chris Liechti033f17c2015-08-30 21:28:04 +0200702 group.add_argument(
703 "--rts",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200704 type=int,
705 help="set initial RTS line state (possible values: 0, 1)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200706 default=default_rts)
cliechti5370cee2013-10-13 03:08:19 +0000707
Chris Liechti033f17c2015-08-30 21:28:04 +0200708 group.add_argument(
709 "--dtr",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200710 type=int,
711 help="set initial DTR line state (possible values: 0, 1)",
Chris Liechti55ba7d92015-08-15 16:33:51 +0200712 default=default_dtr)
cliechti5370cee2013-10-13 03:08:19 +0000713
Chris Liechti00f84282015-12-24 23:40:34 +0100714 group.add_argument(
715 "--ask",
716 action="store_true",
717 help="ask again for port when open fails",
718 default=False)
719
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200720 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000721
Chris Liechti033f17c2015-08-30 21:28:04 +0200722 group.add_argument(
723 "-e", "--echo",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200724 action="store_true",
725 help="enable local echo (default off)",
726 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000727
Chris Liechti033f17c2015-08-30 21:28:04 +0200728 group.add_argument(
729 "--encoding",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200730 dest="serial_port_encoding",
731 metavar="CODEC",
Chris Liechtia7e7b692015-08-25 21:10:28 +0200732 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200733 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000734
Chris Liechti033f17c2015-08-30 21:28:04 +0200735 group.add_argument(
736 "-f", "--filter",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200737 action="append",
738 metavar="NAME",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200739 help="add text transformation",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200740 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200741
Chris Liechti033f17c2015-08-30 21:28:04 +0200742 group.add_argument(
743 "--eol",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200744 choices=['CR', 'LF', 'CRLF'],
745 type=lambda c: c.upper(),
746 help="end of line mode",
747 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000748
Chris Liechti033f17c2015-08-30 21:28:04 +0200749 group.add_argument(
750 "--raw",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200751 action="store_true",
752 help="Do no apply any encodings/transformations",
753 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000754
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200755 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000756
Chris Liechti033f17c2015-08-30 21:28:04 +0200757 group.add_argument(
758 "--exit-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200759 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200760 metavar='NUM',
761 help="Unicode of special character that is used to exit the application, default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200762 default=0x1d # GS/CTRL+]
763 )
cliechtibf6bb7d2006-03-30 00:28:18 +0000764
Chris Liechti033f17c2015-08-30 21:28:04 +0200765 group.add_argument(
766 "--menu-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200767 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200768 metavar='NUM',
769 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200770 default=0x14 # Menu: CTRL+T
771 )
cliechti9c592b32008-06-16 22:00:14 +0000772
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200773 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000774
Chris Liechti033f17c2015-08-30 21:28:04 +0200775 group.add_argument(
776 "-q", "--quiet",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200777 action="store_true",
778 help="suppress non-error messages",
779 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000780
Chris Liechti033f17c2015-08-30 21:28:04 +0200781 group.add_argument(
782 "--develop",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200783 action="store_true",
784 help="show Python traceback on error",
785 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000786
Chris Liechtib7550bd2015-08-15 04:09:10 +0200787 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000788
Chris Liechtib7550bd2015-08-15 04:09:10 +0200789 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000790 parser.error('--exit-char can not be the same as --menu-char')
791
Chris Liechtib3df13e2015-08-25 02:20:09 +0200792 if args.filter:
793 if 'help' in args.filter:
794 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200795 sys.stderr.write('\n'.join(
Chris Liechtib3df13e2015-08-25 02:20:09 +0200796 '{:<10} = {.__doc__}'.format(k, v)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200797 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200798 sys.stderr.write('\n')
799 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200800 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200801 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200802 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200803
Chris Liechti00f84282015-12-24 23:40:34 +0100804 while True:
805 # no port given on command line -> ask user now
806 if args.port is None or args.port == '-':
807 try:
808 args.port = ask_for_port()
809 except KeyboardInterrupt:
810 sys.stderr.write('\n')
811 parser.error('user aborted and port is not given')
812 else:
813 if not args.port:
814 parser.error('port is not given')
815 try:
816 serial_instance = serial.serial_for_url(
817 args.port,
818 args.baudrate,
819 parity=args.parity,
820 rtscts=args.rtscts,
821 xonxoff=args.xonxoff,
822 timeout=1,
823 do_not_open=True)
Chris Liechti3b454802015-08-26 23:39:59 +0200824
Chris Liechti00f84282015-12-24 23:40:34 +0100825 if args.dtr is not None:
826 if not args.quiet:
827 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
828 serial_instance.dtr = args.dtr
829 if args.rts is not None:
830 if not args.quiet:
831 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
832 serial_instance.rts = args.rts
Chris Liechti3b454802015-08-26 23:39:59 +0200833
Chris Liechti00f84282015-12-24 23:40:34 +0100834 serial_instance.open()
835 except serial.SerialException as e:
836 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
837 if args.develop:
838 raise
839 if not args.ask:
840 sys.exit(1)
841 else:
842 args.port = '-'
843 else:
844 break
cliechti6385f2c2005-09-21 19:51:19 +0000845
Chris Liechti3b454802015-08-26 23:39:59 +0200846 miniterm = Miniterm(
847 serial_instance,
848 echo=args.echo,
849 eol=args.eol.lower(),
850 filters=filters)
851 miniterm.exit_character = unichr(args.exit_char)
852 miniterm.menu_character = unichr(args.menu_char)
853 miniterm.raw = args.raw
854 miniterm.set_rx_encoding(args.serial_port_encoding)
855 miniterm.set_tx_encoding(args.serial_port_encoding)
856
Chris Liechtib7550bd2015-08-15 04:09:10 +0200857 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200858 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
859 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200860 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti442bf512015-08-15 01:42:24 +0200861 key_description(miniterm.exit_character),
862 key_description(miniterm.menu_character),
863 key_description(miniterm.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200864 key_description('\x08'),
Chris Liechti442bf512015-08-15 01:42:24 +0200865 ))
cliechti6fa76fb2009-07-08 23:53:39 +0000866
cliechti6385f2c2005-09-21 19:51:19 +0000867 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000868 try:
869 miniterm.join(True)
870 except KeyboardInterrupt:
871 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200872 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000873 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000874 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000875
cliechti5370cee2013-10-13 03:08:19 +0000876# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000877if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000878 main()