blob: ee4b14e7837f0d28dcd157182f903c76fb8a39f1 [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 Liechti1f7ac6c2015-08-15 15:16:37 +0200714 group = parser.add_argument_group("data handling")
cliechti5370cee2013-10-13 03:08:19 +0000715
Chris Liechti033f17c2015-08-30 21:28:04 +0200716 group.add_argument(
717 "-e", "--echo",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200718 action="store_true",
719 help="enable local echo (default off)",
720 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000721
Chris Liechti033f17c2015-08-30 21:28:04 +0200722 group.add_argument(
723 "--encoding",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200724 dest="serial_port_encoding",
725 metavar="CODEC",
Chris Liechtia7e7b692015-08-25 21:10:28 +0200726 help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200727 default='UTF-8')
cliechti5370cee2013-10-13 03:08:19 +0000728
Chris Liechti033f17c2015-08-30 21:28:04 +0200729 group.add_argument(
730 "-f", "--filter",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200731 action="append",
732 metavar="NAME",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200733 help="add text transformation",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200734 default=[])
Chris Liechti2b1b3552015-08-12 15:35:33 +0200735
Chris Liechti033f17c2015-08-30 21:28:04 +0200736 group.add_argument(
737 "--eol",
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200738 choices=['CR', 'LF', 'CRLF'],
739 type=lambda c: c.upper(),
740 help="end of line mode",
741 default='CRLF')
cliechti53edb472009-02-06 21:18:46 +0000742
Chris Liechti033f17c2015-08-30 21:28:04 +0200743 group.add_argument(
744 "--raw",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200745 action="store_true",
746 help="Do no apply any encodings/transformations",
747 default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000748
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200749 group = parser.add_argument_group("hotkeys")
cliechtib7d746d2006-03-28 22:44:30 +0000750
Chris Liechti033f17c2015-08-30 21:28:04 +0200751 group.add_argument(
752 "--exit-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200753 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200754 metavar='NUM',
755 help="Unicode of special character that is used to exit the application, default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200756 default=0x1d # GS/CTRL+]
757 )
cliechtibf6bb7d2006-03-30 00:28:18 +0000758
Chris Liechti033f17c2015-08-30 21:28:04 +0200759 group.add_argument(
760 "--menu-char",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200761 type=int,
Chris Liechti55ba7d92015-08-15 16:33:51 +0200762 metavar='NUM',
763 help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200764 default=0x14 # Menu: CTRL+T
765 )
cliechti9c592b32008-06-16 22:00:14 +0000766
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200767 group = parser.add_argument_group("diagnostics")
cliechti6385f2c2005-09-21 19:51:19 +0000768
Chris Liechti033f17c2015-08-30 21:28:04 +0200769 group.add_argument(
770 "-q", "--quiet",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200771 action="store_true",
772 help="suppress non-error messages",
773 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000774
Chris Liechti033f17c2015-08-30 21:28:04 +0200775 group.add_argument(
776 "--develop",
Chris Liechtib7550bd2015-08-15 04:09:10 +0200777 action="store_true",
778 help="show Python traceback on error",
779 default=False)
cliechti5370cee2013-10-13 03:08:19 +0000780
Chris Liechtib7550bd2015-08-15 04:09:10 +0200781 args = parser.parse_args()
cliechti5370cee2013-10-13 03:08:19 +0000782
Chris Liechtib7550bd2015-08-15 04:09:10 +0200783 if args.menu_char == args.exit_char:
cliechti6c8eb2f2009-07-08 02:10:46 +0000784 parser.error('--exit-char can not be the same as --menu-char')
785
Chris Liechtib7550bd2015-08-15 04:09:10 +0200786 # no port given on command line -> ask user now
Chris Liechti7cf82762015-09-01 02:56:04 +0200787 if args.port is None or args.port == '-':
Chris Liechti89313c92015-09-01 02:33:13 +0200788 try:
789 args.port = ask_for_port()
790 except KeyboardInterrupt:
791 sys.stderr.write('\n')
792 parser.error('user aborted and port is not given')
793 else:
794 if not args.port:
795 parser.error('port is not given')
cliechti53edb472009-02-06 21:18:46 +0000796
Chris Liechtib3df13e2015-08-25 02:20:09 +0200797 if args.filter:
798 if 'help' in args.filter:
799 sys.stderr.write('Available filters:\n')
Chris Liechti442bf512015-08-15 01:42:24 +0200800 sys.stderr.write('\n'.join(
Chris Liechtib3df13e2015-08-25 02:20:09 +0200801 '{:<10} = {.__doc__}'.format(k, v)
Chris Liechtib7550bd2015-08-15 04:09:10 +0200802 for k, v in sorted(TRANSFORMATIONS.items())))
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200803 sys.stderr.write('\n')
804 sys.exit(1)
Chris Liechtib3df13e2015-08-25 02:20:09 +0200805 filters = args.filter
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200806 else:
Chris Liechtib3df13e2015-08-25 02:20:09 +0200807 filters = ['default']
Chris Liechtic7a5d4c2015-08-11 23:32:20 +0200808
cliechti6385f2c2005-09-21 19:51:19 +0000809 try:
Chris Liechti3b454802015-08-26 23:39:59 +0200810 serial_instance = serial.serial_for_url(
Chris Liechtib7550bd2015-08-15 04:09:10 +0200811 args.port,
812 args.baudrate,
Chris Liechti3b454802015-08-26 23:39:59 +0200813 parity=args.parity,
Chris Liechtib7550bd2015-08-15 04:09:10 +0200814 rtscts=args.rtscts,
815 xonxoff=args.xonxoff,
Chris Liechti3b454802015-08-26 23:39:59 +0200816 timeout=1,
817 do_not_open=True)
818
819 if args.dtr is not None:
820 if not args.quiet:
821 sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
822 serial_instance.dtr = args.dtr
823 if args.rts is not None:
824 if not args.quiet:
825 sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
826 serial_instance.rts = args.rts
827
828 serial_instance.open()
Chris Liechti68340d72015-08-03 14:15:48 +0200829 except serial.SerialException as e:
Chris Liechtiaccd2012015-08-17 03:09:23 +0200830 sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200831 if args.develop:
Chris Liechti91090912015-08-05 02:36:14 +0200832 raise
cliechti6385f2c2005-09-21 19:51:19 +0000833 sys.exit(1)
834
Chris Liechti3b454802015-08-26 23:39:59 +0200835 miniterm = Miniterm(
836 serial_instance,
837 echo=args.echo,
838 eol=args.eol.lower(),
839 filters=filters)
840 miniterm.exit_character = unichr(args.exit_char)
841 miniterm.menu_character = unichr(args.menu_char)
842 miniterm.raw = args.raw
843 miniterm.set_rx_encoding(args.serial_port_encoding)
844 miniterm.set_tx_encoding(args.serial_port_encoding)
845
Chris Liechtib7550bd2015-08-15 04:09:10 +0200846 if not args.quiet:
Chris Liechti1f7ac6c2015-08-15 15:16:37 +0200847 sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
848 p=miniterm.serial))
Chris Liechtib7550bd2015-08-15 04:09:10 +0200849 sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
Chris Liechti442bf512015-08-15 01:42:24 +0200850 key_description(miniterm.exit_character),
851 key_description(miniterm.menu_character),
852 key_description(miniterm.menu_character),
Chris Liechtib7550bd2015-08-15 04:09:10 +0200853 key_description('\x08'),
Chris Liechti442bf512015-08-15 01:42:24 +0200854 ))
cliechti6fa76fb2009-07-08 23:53:39 +0000855
cliechti6385f2c2005-09-21 19:51:19 +0000856 miniterm.start()
cliechti258ab0a2011-03-21 23:03:45 +0000857 try:
858 miniterm.join(True)
859 except KeyboardInterrupt:
860 pass
Chris Liechtib7550bd2015-08-15 04:09:10 +0200861 if not args.quiet:
cliechtibf6bb7d2006-03-30 00:28:18 +0000862 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000863 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000864
cliechti5370cee2013-10-13 03:08:19 +0000865# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cliechti8b3ad392002-03-03 20:12:21 +0000866if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000867 main()