Commit b204d4da0e65855788328941750c716205147dc9
1 parent
50fbe57016
Exists in
master
python miniterm script
Showing 1 changed file with 694 additions and 0 deletions Inline Diff
scripts/miniterm.py
View file @
b204d4d
File was created | 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) |