Commit b204d4da0e65855788328941750c716205147dc9
1 parent
50fbe57016
Exists in
master
python miniterm script
Showing 1 changed file with 694 additions and 0 deletions Side-by-side Diff
scripts/miniterm.py
View file @
b204d4d
1 | +#!/usr/bin/env python | |
2 | + | |
3 | +# Very simple serial terminal | |
4 | +# (C)2002-2011 Chris Liechti <cliechti@gmx.net> | |
5 | + | |
6 | +# Input characters are sent directly (only LF -> CR/LF/CRLF translation is | |
7 | +# done), received characters are displayed as is (or escaped trough pythons | |
8 | +# repr, useful for debug purposes) | |
9 | + | |
10 | + | |
11 | +import sys, os, serial, threading | |
12 | +try: | |
13 | + from serial.tools.list_ports import comports | |
14 | +except ImportError: | |
15 | + comports = None | |
16 | + | |
17 | +EXITCHARCTER = serial.to_bytes([0x1d]) # GS/CTRL+] | |
18 | +MENUCHARACTER = serial.to_bytes([0x14]) # Menu: CTRL+T | |
19 | + | |
20 | +DEFAULT_PORT = None | |
21 | +DEFAULT_BAUDRATE = 9600 | |
22 | +DEFAULT_RTS = None | |
23 | +DEFAULT_DTR = None | |
24 | + | |
25 | + | |
26 | +def key_description(character): | |
27 | + """generate a readable description for a key""" | |
28 | + ascii_code = ord(character) | |
29 | + if ascii_code < 32: | |
30 | + return 'Ctrl+%c' % (ord('@') + ascii_code) | |
31 | + else: | |
32 | + return repr(character) | |
33 | + | |
34 | + | |
35 | +# help text, starts with blank line! it's a function so that the current values | |
36 | +# for the shortcut keys is used and not the value at program start | |
37 | +def get_help_text(): | |
38 | + return """ | |
39 | +--- pySerial (%(version)s) - miniterm - help | |
40 | +--- | |
41 | +--- %(exit)-8s Exit program | |
42 | +--- %(menu)-8s Menu escape key, followed by: | |
43 | +--- Menu keys: | |
44 | +--- %(itself)-7s Send the menu character itself to remote | |
45 | +--- %(exchar)-7s Send the exit character itself to remote | |
46 | +--- %(info)-7s Show info | |
47 | +--- %(upload)-7s Upload file (prompt will be shown) | |
48 | +--- Toggles: | |
49 | +--- %(rts)-7s RTS %(echo)-7s local echo | |
50 | +--- %(dtr)-7s DTR %(break)-7s BREAK | |
51 | +--- %(lfm)-7s line feed %(repr)-7s Cycle repr mode | |
52 | +--- | |
53 | +--- Port settings (%(menu)s followed by the following): | |
54 | +--- p change port | |
55 | +--- 7 8 set data bits | |
56 | +--- n e o s m change parity (None, Even, Odd, Space, Mark) | |
57 | +--- 1 2 3 set stop bits (1, 2, 1.5) | |
58 | +--- b change baud rate | |
59 | +--- x X disable/enable software flow control | |
60 | +--- r R disable/enable hardware flow control | |
61 | +""" % { | |
62 | + 'version': getattr(serial, 'VERSION', 'unknown version'), | |
63 | + 'exit': key_description(EXITCHARCTER), | |
64 | + 'menu': key_description(MENUCHARACTER), | |
65 | + 'rts': key_description('\x12'), | |
66 | + 'repr': key_description('\x01'), | |
67 | + 'dtr': key_description('\x04'), | |
68 | + 'lfm': key_description('\x0c'), | |
69 | + 'break': key_description('\x02'), | |
70 | + 'echo': key_description('\x05'), | |
71 | + 'info': key_description('\x09'), | |
72 | + 'upload': key_description('\x15'), | |
73 | + 'itself': key_description(MENUCHARACTER), | |
74 | + 'exchar': key_description(EXITCHARCTER), | |
75 | +} | |
76 | + | |
77 | +if sys.version_info >= (3, 0): | |
78 | + def character(b): | |
79 | + return b.decode('latin1') | |
80 | +else: | |
81 | + def character(b): | |
82 | + return b | |
83 | + | |
84 | +LF = serial.to_bytes([10]) | |
85 | +CR = serial.to_bytes([13]) | |
86 | +CRLF = serial.to_bytes([13, 10]) | |
87 | + | |
88 | +X00 = serial.to_bytes([0]) | |
89 | +X0E = serial.to_bytes([0x0e]) | |
90 | + | |
91 | +# first choose a platform dependant way to read single characters from the console | |
92 | +global console | |
93 | + | |
94 | +if os.name == 'nt': | |
95 | + import msvcrt | |
96 | + class Console(object): | |
97 | + def __init__(self): | |
98 | + pass | |
99 | + | |
100 | + def setup(self): | |
101 | + pass # Do nothing for 'nt' | |
102 | + | |
103 | + def cleanup(self): | |
104 | + pass # Do nothing for 'nt' | |
105 | + | |
106 | + def getkey(self): | |
107 | + while True: | |
108 | + z = msvcrt.getch() | |
109 | + if z == X00 or z == X0E: # functions keys, ignore | |
110 | + msvcrt.getch() | |
111 | + else: | |
112 | + if z == CR: | |
113 | + return LF | |
114 | + return z | |
115 | + | |
116 | + console = Console() | |
117 | + | |
118 | +elif os.name == 'posix': | |
119 | + import termios, sys, os | |
120 | + class Console(object): | |
121 | + def __init__(self): | |
122 | + self.fd = sys.stdin.fileno() | |
123 | + self.old = None | |
124 | + | |
125 | + def setup(self): | |
126 | + self.old = termios.tcgetattr(self.fd) | |
127 | + new = termios.tcgetattr(self.fd) | |
128 | + new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG | |
129 | + new[6][termios.VMIN] = 1 | |
130 | + new[6][termios.VTIME] = 0 | |
131 | + termios.tcsetattr(self.fd, termios.TCSANOW, new) | |
132 | + | |
133 | + def getkey(self): | |
134 | + c = os.read(self.fd, 1) | |
135 | + return c | |
136 | + | |
137 | + def cleanup(self): | |
138 | + if self.old is not None: | |
139 | + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) | |
140 | + | |
141 | + console = Console() | |
142 | + | |
143 | + def cleanup_console(): | |
144 | + console.cleanup() | |
145 | + | |
146 | + sys.exitfunc = cleanup_console # terminal modes have to be restored on exit... | |
147 | + | |
148 | +else: | |
149 | + raise NotImplementedError("Sorry no implementation for your platform (%s) available." % sys.platform) | |
150 | + | |
151 | + | |
152 | +def dump_port_list(): | |
153 | + if comports: | |
154 | + sys.stderr.write('\n--- Available ports:\n') | |
155 | + for port, desc, hwid in sorted(comports()): | |
156 | + #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid)) | |
157 | + sys.stderr.write('--- %-20s %s\n' % (port, desc)) | |
158 | + | |
159 | + | |
160 | +CONVERT_CRLF = 2 | |
161 | +CONVERT_CR = 1 | |
162 | +CONVERT_LF = 0 | |
163 | +NEWLINE_CONVERISON_MAP = (LF, CR, CRLF) | |
164 | +LF_MODES = ('LF', 'CR', 'CR/LF') | |
165 | + | |
166 | +REPR_MODES = ('raw', 'some control', 'all control', 'hex') | |
167 | + | |
168 | +class Miniterm(object): | |
169 | + def __init__(self, port, baudrate, parity, rtscts, xonxoff, echo=False, convert_outgoing=CONVERT_CRLF, repr_mode=0): | |
170 | + try: | |
171 | + self.serial = serial.serial_for_url(port, baudrate, parity=parity, rtscts=rtscts, xonxoff=xonxoff, timeout=1) | |
172 | + except AttributeError: | |
173 | + # happens when the installed pyserial is older than 2.5. use the | |
174 | + # Serial class directly then. | |
175 | + self.serial = serial.Serial(port, baudrate, parity=parity, rtscts=rtscts, xonxoff=xonxoff, timeout=1) | |
176 | + self.echo = echo | |
177 | + self.repr_mode = repr_mode | |
178 | + self.convert_outgoing = convert_outgoing | |
179 | + self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing] | |
180 | + self.dtr_state = True | |
181 | + self.rts_state = True | |
182 | + self.break_state = False | |
183 | + | |
184 | + def _start_reader(self): | |
185 | + """Start reader thread""" | |
186 | + self._reader_alive = True | |
187 | + # start serial->console thread | |
188 | + self.receiver_thread = threading.Thread(target=self.reader) | |
189 | + self.receiver_thread.setDaemon(True) | |
190 | + self.receiver_thread.start() | |
191 | + | |
192 | + def _stop_reader(self): | |
193 | + """Stop reader thread only, wait for clean exit of thread""" | |
194 | + self._reader_alive = False | |
195 | + self.receiver_thread.join() | |
196 | + | |
197 | + | |
198 | + def start(self): | |
199 | + self.alive = True | |
200 | + self._start_reader() | |
201 | + # enter console->serial loop | |
202 | + self.transmitter_thread = threading.Thread(target=self.writer) | |
203 | + self.transmitter_thread.setDaemon(True) | |
204 | + self.transmitter_thread.start() | |
205 | + | |
206 | + def stop(self): | |
207 | + self.alive = False | |
208 | + | |
209 | + def join(self, transmit_only=False): | |
210 | + self.transmitter_thread.join() | |
211 | + if not transmit_only: | |
212 | + self.receiver_thread.join() | |
213 | + | |
214 | + def dump_port_settings(self): | |
215 | + sys.stderr.write("\n--- Settings: %s %s,%s,%s,%s\n" % ( | |
216 | + self.serial.portstr, | |
217 | + self.serial.baudrate, | |
218 | + self.serial.bytesize, | |
219 | + self.serial.parity, | |
220 | + self.serial.stopbits)) | |
221 | + sys.stderr.write('--- RTS: %-8s DTR: %-8s BREAK: %-8s\n' % ( | |
222 | + (self.rts_state and 'active' or 'inactive'), | |
223 | + (self.dtr_state and 'active' or 'inactive'), | |
224 | + (self.break_state and 'active' or 'inactive'))) | |
225 | + try: | |
226 | + sys.stderr.write('--- CTS: %-8s DSR: %-8s RI: %-8s CD: %-8s\n' % ( | |
227 | + (self.serial.getCTS() and 'active' or 'inactive'), | |
228 | + (self.serial.getDSR() and 'active' or 'inactive'), | |
229 | + (self.serial.getRI() and 'active' or 'inactive'), | |
230 | + (self.serial.getCD() and 'active' or 'inactive'))) | |
231 | + except serial.SerialException: | |
232 | + # on RFC 2217 ports it can happen to no modem state notification was | |
233 | + # yet received. ignore this error. | |
234 | + pass | |
235 | + sys.stderr.write('--- software flow control: %s\n' % (self.serial.xonxoff and 'active' or 'inactive')) | |
236 | + sys.stderr.write('--- hardware flow control: %s\n' % (self.serial.rtscts and 'active' or 'inactive')) | |
237 | + sys.stderr.write('--- data escaping: %s linefeed: %s\n' % ( | |
238 | + REPR_MODES[self.repr_mode], | |
239 | + LF_MODES[self.convert_outgoing])) | |
240 | + | |
241 | + def reader(self): | |
242 | + """loop and copy serial->console""" | |
243 | + try: | |
244 | + while self.alive and self._reader_alive: | |
245 | + data = character(self.serial.read(1)) | |
246 | + | |
247 | + if self.repr_mode == 0: | |
248 | + # direct output, just have to care about newline setting | |
249 | + if data == '\r' and self.convert_outgoing == CONVERT_CR: | |
250 | + sys.stdout.write('\n') | |
251 | + else: | |
252 | + sys.stdout.write(data) | |
253 | + elif self.repr_mode == 1: | |
254 | + # escape non-printable, let pass newlines | |
255 | + if self.convert_outgoing == CONVERT_CRLF and data in '\r\n': | |
256 | + if data == '\n': | |
257 | + sys.stdout.write('\n') | |
258 | + elif data == '\r': | |
259 | + pass | |
260 | + elif data == '\n' and self.convert_outgoing == CONVERT_LF: | |
261 | + sys.stdout.write('\n') | |
262 | + elif data == '\r' and self.convert_outgoing == CONVERT_CR: | |
263 | + sys.stdout.write('\n') | |
264 | + else: | |
265 | + sys.stdout.write(repr(data)[1:-1]) | |
266 | + elif self.repr_mode == 2: | |
267 | + # escape all non-printable, including newline | |
268 | + sys.stdout.write(repr(data)[1:-1]) | |
269 | + elif self.repr_mode == 3: | |
270 | + # escape everything (hexdump) | |
271 | + for c in data: | |
272 | + sys.stdout.write("%s " % c.encode('hex')) | |
273 | + sys.stdout.flush() | |
274 | + except serial.SerialException, e: | |
275 | + self.alive = False | |
276 | + # would be nice if the console reader could be interruptted at this | |
277 | + # point... | |
278 | + raise | |
279 | + | |
280 | + | |
281 | + def writer(self): | |
282 | + """\ | |
283 | + Loop and copy console->serial until EXITCHARCTER character is | |
284 | + found. When MENUCHARACTER is found, interpret the next key | |
285 | + locally. | |
286 | + """ | |
287 | + menu_active = False | |
288 | + try: | |
289 | + while self.alive: | |
290 | + try: | |
291 | + b = console.getkey() | |
292 | + except KeyboardInterrupt: | |
293 | + b = serial.to_bytes([3]) | |
294 | + c = character(b) | |
295 | + if menu_active: | |
296 | + if c == MENUCHARACTER or c == EXITCHARCTER: # Menu character again/exit char -> send itself | |
297 | + self.serial.write(b) # send character | |
298 | + if self.echo: | |
299 | + sys.stdout.write(c) | |
300 | + elif c == '\x15': # CTRL+U -> upload file | |
301 | + sys.stderr.write('\n--- File to upload: ') | |
302 | + sys.stderr.flush() | |
303 | + console.cleanup() | |
304 | + filename = sys.stdin.readline().rstrip('\r\n') | |
305 | + if filename: | |
306 | + try: | |
307 | + file = open(filename, 'r') | |
308 | + sys.stderr.write('--- Sending file %s ---\n' % filename) | |
309 | + while True: | |
310 | + line = file.readline().rstrip('\r\n') | |
311 | + if not line: | |
312 | + break | |
313 | + self.serial.write(line) | |
314 | + self.serial.write('\r\n') | |
315 | + # Wait for output buffer to drain. | |
316 | + self.serial.flush() | |
317 | + sys.stderr.write('.') # Progress indicator. | |
318 | + sys.stderr.write('\n--- File %s sent ---\n' % filename) | |
319 | + except IOError, e: | |
320 | + sys.stderr.write('--- ERROR opening file %s: %s ---\n' % (filename, e)) | |
321 | + console.setup() | |
322 | + elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help | |
323 | + sys.stderr.write(get_help_text()) | |
324 | + elif c == '\x12': # CTRL+R -> Toggle RTS | |
325 | + self.rts_state = not self.rts_state | |
326 | + self.serial.setRTS(self.rts_state) | |
327 | + sys.stderr.write('--- RTS %s ---\n' % (self.rts_state and 'active' or 'inactive')) | |
328 | + elif c == '\x04': # CTRL+D -> Toggle DTR | |
329 | + self.dtr_state = not self.dtr_state | |
330 | + self.serial.setDTR(self.dtr_state) | |
331 | + sys.stderr.write('--- DTR %s ---\n' % (self.dtr_state and 'active' or 'inactive')) | |
332 | + elif c == '\x02': # CTRL+B -> toggle BREAK condition | |
333 | + self.break_state = not self.break_state | |
334 | + self.serial.setBreak(self.break_state) | |
335 | + sys.stderr.write('--- BREAK %s ---\n' % (self.break_state and 'active' or 'inactive')) | |
336 | + elif c == '\x05': # CTRL+E -> toggle local echo | |
337 | + self.echo = not self.echo | |
338 | + sys.stderr.write('--- local echo %s ---\n' % (self.echo and 'active' or 'inactive')) | |
339 | + elif c == '\x09': # CTRL+I -> info | |
340 | + self.dump_port_settings() | |
341 | + elif c == '\x01': # CTRL+A -> cycle escape mode | |
342 | + self.repr_mode += 1 | |
343 | + if self.repr_mode > 3: | |
344 | + self.repr_mode = 0 | |
345 | + sys.stderr.write('--- escape data: %s ---\n' % ( | |
346 | + REPR_MODES[self.repr_mode], | |
347 | + )) | |
348 | + elif c == '\x0c': # CTRL+L -> cycle linefeed mode | |
349 | + self.convert_outgoing += 1 | |
350 | + if self.convert_outgoing > 2: | |
351 | + self.convert_outgoing = 0 | |
352 | + self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing] | |
353 | + sys.stderr.write('--- line feed %s ---\n' % ( | |
354 | + LF_MODES[self.convert_outgoing], | |
355 | + )) | |
356 | + elif c in 'pP': # P -> change port | |
357 | + dump_port_list() | |
358 | + sys.stderr.write('--- Enter port name: ') | |
359 | + sys.stderr.flush() | |
360 | + console.cleanup() | |
361 | + try: | |
362 | + port = sys.stdin.readline().strip() | |
363 | + except KeyboardInterrupt: | |
364 | + port = None | |
365 | + console.setup() | |
366 | + if port and port != self.serial.port: | |
367 | + # reader thread needs to be shut down | |
368 | + self._stop_reader() | |
369 | + # save settings | |
370 | + settings = self.serial.getSettingsDict() | |
371 | + try: | |
372 | + try: | |
373 | + new_serial = serial.serial_for_url(port, do_not_open=True) | |
374 | + except AttributeError: | |
375 | + # happens when the installed pyserial is older than 2.5. use the | |
376 | + # Serial class directly then. | |
377 | + new_serial = serial.Serial() | |
378 | + new_serial.port = port | |
379 | + # restore settings and open | |
380 | + new_serial.applySettingsDict(settings) | |
381 | + new_serial.open() | |
382 | + new_serial.setRTS(self.rts_state) | |
383 | + new_serial.setDTR(self.dtr_state) | |
384 | + new_serial.setBreak(self.break_state) | |
385 | + except Exception, e: | |
386 | + sys.stderr.write('--- ERROR opening new port: %s ---\n' % (e,)) | |
387 | + new_serial.close() | |
388 | + else: | |
389 | + self.serial.close() | |
390 | + self.serial = new_serial | |
391 | + sys.stderr.write('--- Port changed to: %s ---\n' % (self.serial.port,)) | |
392 | + # and restart the reader thread | |
393 | + self._start_reader() | |
394 | + elif c in 'bB': # B -> change baudrate | |
395 | + sys.stderr.write('\n--- Baudrate: ') | |
396 | + sys.stderr.flush() | |
397 | + console.cleanup() | |
398 | + backup = self.serial.baudrate | |
399 | + try: | |
400 | + self.serial.baudrate = int(sys.stdin.readline().strip()) | |
401 | + except ValueError, e: | |
402 | + sys.stderr.write('--- ERROR setting baudrate: %s ---\n' % (e,)) | |
403 | + self.serial.baudrate = backup | |
404 | + else: | |
405 | + self.dump_port_settings() | |
406 | + console.setup() | |
407 | + elif c == '8': # 8 -> change to 8 bits | |
408 | + self.serial.bytesize = serial.EIGHTBITS | |
409 | + self.dump_port_settings() | |
410 | + elif c == '7': # 7 -> change to 8 bits | |
411 | + self.serial.bytesize = serial.SEVENBITS | |
412 | + self.dump_port_settings() | |
413 | + elif c in 'eE': # E -> change to even parity | |
414 | + self.serial.parity = serial.PARITY_EVEN | |
415 | + self.dump_port_settings() | |
416 | + elif c in 'oO': # O -> change to odd parity | |
417 | + self.serial.parity = serial.PARITY_ODD | |
418 | + self.dump_port_settings() | |
419 | + elif c in 'mM': # M -> change to mark parity | |
420 | + self.serial.parity = serial.PARITY_MARK | |
421 | + self.dump_port_settings() | |
422 | + elif c in 'sS': # S -> change to space parity | |
423 | + self.serial.parity = serial.PARITY_SPACE | |
424 | + self.dump_port_settings() | |
425 | + elif c in 'nN': # N -> change to no parity | |
426 | + self.serial.parity = serial.PARITY_NONE | |
427 | + self.dump_port_settings() | |
428 | + elif c == '1': # 1 -> change to 1 stop bits | |
429 | + self.serial.stopbits = serial.STOPBITS_ONE | |
430 | + self.dump_port_settings() | |
431 | + elif c == '2': # 2 -> change to 2 stop bits | |
432 | + self.serial.stopbits = serial.STOPBITS_TWO | |
433 | + self.dump_port_settings() | |
434 | + elif c == '3': # 3 -> change to 1.5 stop bits | |
435 | + self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE | |
436 | + self.dump_port_settings() | |
437 | + elif c in 'xX': # X -> change software flow control | |
438 | + self.serial.xonxoff = (c == 'X') | |
439 | + self.dump_port_settings() | |
440 | + elif c in 'rR': # R -> change hardware flow control | |
441 | + self.serial.rtscts = (c == 'R') | |
442 | + self.dump_port_settings() | |
443 | + else: | |
444 | + sys.stderr.write('--- unknown menu character %s --\n' % key_description(c)) | |
445 | + menu_active = False | |
446 | + elif c == MENUCHARACTER: # next char will be for menu | |
447 | + menu_active = True | |
448 | + elif c == EXITCHARCTER: | |
449 | + self.stop() | |
450 | + break # exit app | |
451 | + elif c == '\n': | |
452 | + self.serial.write(self.newline) # send newline character(s) | |
453 | + if self.echo: | |
454 | + sys.stdout.write(c) # local echo is a real newline in any case | |
455 | + sys.stdout.flush() | |
456 | + else: | |
457 | + self.serial.write(b) # send byte | |
458 | + if self.echo: | |
459 | + sys.stdout.write(c) | |
460 | + sys.stdout.flush() | |
461 | + except: | |
462 | + self.alive = False | |
463 | + raise | |
464 | + | |
465 | +def main(): | |
466 | + import optparse | |
467 | + | |
468 | + parser = optparse.OptionParser( | |
469 | + usage = "%prog [options] [port [baudrate]]", | |
470 | + description = "Miniterm - A simple terminal program for the serial port." | |
471 | + ) | |
472 | + | |
473 | + group = optparse.OptionGroup(parser, "Port settings") | |
474 | + | |
475 | + group.add_option("-p", "--port", | |
476 | + dest = "port", | |
477 | + help = "port, a number or a device name. (deprecated option, use parameter instead)", | |
478 | + default = DEFAULT_PORT | |
479 | + ) | |
480 | + | |
481 | + group.add_option("-b", "--baud", | |
482 | + dest = "baudrate", | |
483 | + action = "store", | |
484 | + type = 'int', | |
485 | + help = "set baud rate, default %default", | |
486 | + default = DEFAULT_BAUDRATE | |
487 | + ) | |
488 | + | |
489 | + group.add_option("--parity", | |
490 | + dest = "parity", | |
491 | + action = "store", | |
492 | + help = "set parity, one of [N, E, O, S, M], default=N", | |
493 | + default = 'N' | |
494 | + ) | |
495 | + | |
496 | + group.add_option("--rtscts", | |
497 | + dest = "rtscts", | |
498 | + action = "store_true", | |
499 | + help = "enable RTS/CTS flow control (default off)", | |
500 | + default = False | |
501 | + ) | |
502 | + | |
503 | + group.add_option("--xonxoff", | |
504 | + dest = "xonxoff", | |
505 | + action = "store_true", | |
506 | + help = "enable software flow control (default off)", | |
507 | + default = False | |
508 | + ) | |
509 | + | |
510 | + group.add_option("--rts", | |
511 | + dest = "rts_state", | |
512 | + action = "store", | |
513 | + type = 'int', | |
514 | + help = "set initial RTS line state (possible values: 0, 1)", | |
515 | + default = DEFAULT_RTS | |
516 | + ) | |
517 | + | |
518 | + group.add_option("--dtr", | |
519 | + dest = "dtr_state", | |
520 | + action = "store", | |
521 | + type = 'int', | |
522 | + help = "set initial DTR line state (possible values: 0, 1)", | |
523 | + default = DEFAULT_DTR | |
524 | + ) | |
525 | + | |
526 | + parser.add_option_group(group) | |
527 | + | |
528 | + group = optparse.OptionGroup(parser, "Data handling") | |
529 | + | |
530 | + group.add_option("-e", "--echo", | |
531 | + dest = "echo", | |
532 | + action = "store_true", | |
533 | + help = "enable local echo (default off)", | |
534 | + default = False | |
535 | + ) | |
536 | + | |
537 | + group.add_option("--cr", | |
538 | + dest = "cr", | |
539 | + action = "store_true", | |
540 | + help = "do not send CR+LF, send CR only", | |
541 | + default = False | |
542 | + ) | |
543 | + | |
544 | + group.add_option("--lf", | |
545 | + dest = "lf", | |
546 | + action = "store_true", | |
547 | + help = "do not send CR+LF, send LF only", | |
548 | + default = False | |
549 | + ) | |
550 | + | |
551 | + group.add_option("-D", "--debug", | |
552 | + dest = "repr_mode", | |
553 | + action = "count", | |
554 | + help = """debug received data (escape non-printable chars) | |
555 | +--debug can be given multiple times: | |
556 | +0: just print what is received | |
557 | +1: escape non-printable characters, do newlines as unusual | |
558 | +2: escape non-printable characters, newlines too | |
559 | +3: hex dump everything""", | |
560 | + default = 0 | |
561 | + ) | |
562 | + | |
563 | + parser.add_option_group(group) | |
564 | + | |
565 | + | |
566 | + group = optparse.OptionGroup(parser, "Hotkeys") | |
567 | + | |
568 | + group.add_option("--exit-char", | |
569 | + dest = "exit_char", | |
570 | + action = "store", | |
571 | + type = 'int', | |
572 | + help = "ASCII code of special character that is used to exit the application", | |
573 | + default = 0x1d | |
574 | + ) | |
575 | + | |
576 | + group.add_option("--menu-char", | |
577 | + dest = "menu_char", | |
578 | + action = "store", | |
579 | + type = 'int', | |
580 | + help = "ASCII code of special character that is used to control miniterm (menu)", | |
581 | + default = 0x14 | |
582 | + ) | |
583 | + | |
584 | + parser.add_option_group(group) | |
585 | + | |
586 | + group = optparse.OptionGroup(parser, "Diagnostics") | |
587 | + | |
588 | + group.add_option("-q", "--quiet", | |
589 | + dest = "quiet", | |
590 | + action = "store_true", | |
591 | + help = "suppress non-error messages", | |
592 | + default = False | |
593 | + ) | |
594 | + | |
595 | + parser.add_option_group(group) | |
596 | + | |
597 | + | |
598 | + (options, args) = parser.parse_args() | |
599 | + | |
600 | + options.parity = options.parity.upper() | |
601 | + if options.parity not in 'NEOSM': | |
602 | + parser.error("invalid parity") | |
603 | + | |
604 | + if options.cr and options.lf: | |
605 | + parser.error("only one of --cr or --lf can be specified") | |
606 | + | |
607 | + if options.menu_char == options.exit_char: | |
608 | + parser.error('--exit-char can not be the same as --menu-char') | |
609 | + | |
610 | + global EXITCHARCTER, MENUCHARACTER | |
611 | + EXITCHARCTER = chr(options.exit_char) | |
612 | + MENUCHARACTER = chr(options.menu_char) | |
613 | + | |
614 | + port = options.port | |
615 | + baudrate = options.baudrate | |
616 | + if args: | |
617 | + if options.port is not None: | |
618 | + parser.error("no arguments are allowed, options only when --port is given") | |
619 | + port = args.pop(0) | |
620 | + if args: | |
621 | + try: | |
622 | + baudrate = int(args[0]) | |
623 | + except ValueError: | |
624 | + parser.error("baud rate must be a number, not %r" % args[0]) | |
625 | + args.pop(0) | |
626 | + if args: | |
627 | + parser.error("too many arguments") | |
628 | + else: | |
629 | + # noport given on command line -> ask user now | |
630 | + if port is None: | |
631 | + dump_port_list() | |
632 | + port = raw_input('Enter port name:') | |
633 | + | |
634 | + convert_outgoing = CONVERT_CRLF | |
635 | + if options.cr: | |
636 | + convert_outgoing = CONVERT_CR | |
637 | + elif options.lf: | |
638 | + convert_outgoing = CONVERT_LF | |
639 | + | |
640 | + try: | |
641 | + miniterm = Miniterm( | |
642 | + port, | |
643 | + baudrate, | |
644 | + options.parity, | |
645 | + rtscts=options.rtscts, | |
646 | + xonxoff=options.xonxoff, | |
647 | + echo=options.echo, | |
648 | + convert_outgoing=convert_outgoing, | |
649 | + repr_mode=options.repr_mode, | |
650 | + ) | |
651 | + except serial.SerialException, e: | |
652 | + sys.stderr.write("could not open port %r: %s\n" % (port, e)) | |
653 | + sys.exit(1) | |
654 | + | |
655 | + if not options.quiet: | |
656 | + sys.stderr.write('--- Miniterm on %s: %d,%s,%s,%s ---\n' % ( | |
657 | + miniterm.serial.portstr, | |
658 | + miniterm.serial.baudrate, | |
659 | + miniterm.serial.bytesize, | |
660 | + miniterm.serial.parity, | |
661 | + miniterm.serial.stopbits, | |
662 | + )) | |
663 | + sys.stderr.write('--- Quit: %s | Menu: %s | Help: %s followed by %s ---\n' % ( | |
664 | + key_description(EXITCHARCTER), | |
665 | + key_description(MENUCHARACTER), | |
666 | + key_description(MENUCHARACTER), | |
667 | + key_description('\x08'), | |
668 | + )) | |
669 | + | |
670 | + if options.dtr_state is not None: | |
671 | + if not options.quiet: | |
672 | + sys.stderr.write('--- forcing DTR %s\n' % (options.dtr_state and 'active' or 'inactive')) | |
673 | + miniterm.serial.setDTR(options.dtr_state) | |
674 | + miniterm.dtr_state = options.dtr_state | |
675 | + if options.rts_state is not None: | |
676 | + if not options.quiet: | |
677 | + sys.stderr.write('--- forcing RTS %s\n' % (options.rts_state and 'active' or 'inactive')) | |
678 | + miniterm.serial.setRTS(options.rts_state) | |
679 | + miniterm.rts_state = options.rts_state | |
680 | + | |
681 | + console.setup() | |
682 | + miniterm.start() | |
683 | + try: | |
684 | + miniterm.join(True) | |
685 | + except KeyboardInterrupt: | |
686 | + pass | |
687 | + if not options.quiet: | |
688 | + sys.stderr.write("\n--- exit ---\n") | |
689 | + miniterm.join() | |
690 | + #~ console.cleanup() | |
691 | + | |
692 | +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
693 | +if __name__ == '__main__': | |
694 | + main() |