blob: ad46664f8ee0f863f177038dfa1a0e996fd99779 [file] [log] [blame]
cliechti576de252002-02-28 23:54:44 +00001#!/usr/bin/env python
2
cliechtia128a702004-07-21 22:13:31 +00003# Very simple serial terminal
cliechti9cf26222006-03-24 20:09:21 +00004# (C)2002-2006 Chris Liechti <cliechti@gmx.net>
cliechtifc9eb382002-03-05 01:12:29 +00005
cliechtia128a702004-07-21 22:13:31 +00006# Input characters are sent directly (only LF -> CR/LF/CRLF translation is
cliechtibf6bb7d2006-03-30 00:28:18 +00007# done), received characters are displayed as is (or escaped trough pythons
cliechtia128a702004-07-21 22:13:31 +00008# repr, useful for debug purposes)
cliechti576de252002-02-28 23:54:44 +00009
10
cliechti88028022007-11-13 16:06:46 +000011import sys, os, serial, threading
cliechti576de252002-02-28 23:54:44 +000012
cliechti6385f2c2005-09-21 19:51:19 +000013EXITCHARCTER = '\x1d' #GS/ctrl+]
cliechtia128a702004-07-21 22:13:31 +000014
15#first choose a platform dependant way to read single characters from the console
cliechtifc9eb382002-03-05 01:12:29 +000016if os.name == 'nt':
cliechti576de252002-02-28 23:54:44 +000017 import msvcrt
18 def getkey():
19 while 1:
cliechti6385f2c2005-09-21 19:51:19 +000020 z = msvcrt.getch()
cliechti576de252002-02-28 23:54:44 +000021 if z == '\0' or z == '\xe0': #functions keys
22 msvcrt.getch()
23 else:
cliechti7672d612004-07-21 19:52:33 +000024 if z == '\r':
25 return '\n'
cliechti576de252002-02-28 23:54:44 +000026 return z
27
28elif os.name == 'posix':
cliechtib7466da2004-07-11 20:04:41 +000029 import termios, sys, os
cliechti576de252002-02-28 23:54:44 +000030 fd = sys.stdin.fileno()
31 old = termios.tcgetattr(fd)
32 new = termios.tcgetattr(fd)
cliechtib7466da2004-07-11 20:04:41 +000033 new[3] = new[3] & ~termios.ICANON & ~termios.ECHO
34 new[6][termios.VMIN] = 1
35 new[6][termios.VTIME] = 0
36 termios.tcsetattr(fd, termios.TCSANOW, new)
cliechti576de252002-02-28 23:54:44 +000037 def getkey():
cliechtib7466da2004-07-11 20:04:41 +000038 c = os.read(fd, 1)
cliechti576de252002-02-28 23:54:44 +000039 return c
40 def clenaup_console():
cliechtib7466da2004-07-11 20:04:41 +000041 termios.tcsetattr(fd, termios.TCSAFLUSH, old)
42 sys.exitfunc = clenaup_console #terminal modes have to be restored on exit...
cliechti576de252002-02-28 23:54:44 +000043
44else:
45 raise "Sorry no implementation for your platform (%s) available." % sys.platform
46
cliechtia128a702004-07-21 22:13:31 +000047CONVERT_CRLF = 2
48CONVERT_CR = 1
49CONVERT_LF = 0
cliechtibf6bb7d2006-03-30 00:28:18 +000050NEWLINE_CONVERISON_MAP = ('\n', '\r', '\r\n')
cliechti576de252002-02-28 23:54:44 +000051
cliechti6385f2c2005-09-21 19:51:19 +000052class Miniterm:
cliechti9743e222006-04-04 21:48:56 +000053 def __init__(self, port, baudrate, parity, rtscts, xonxoff, echo=False, convert_outgoing=CONVERT_CRLF, repr_mode=0):
cliechtibf6bb7d2006-03-30 00:28:18 +000054 self.serial = serial.Serial(port, baudrate, parity=parity, rtscts=rtscts, xonxoff=xonxoff, timeout=0.7)
cliechti6385f2c2005-09-21 19:51:19 +000055 self.echo = echo
56 self.repr_mode = repr_mode
57 self.convert_outgoing = convert_outgoing
cliechtibf6bb7d2006-03-30 00:28:18 +000058 self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
cliechti576de252002-02-28 23:54:44 +000059
cliechti6385f2c2005-09-21 19:51:19 +000060 def start(self):
61 self.alive = True
62 #start serial->console thread
63 self.receiver_thread = threading.Thread(target=self.reader)
64 self.receiver_thread.setDaemon(1)
65 self.receiver_thread.start()
66 #enter console->serial loop
67 self.transmitter_thread = threading.Thread(target=self.writer)
68 self.transmitter_thread.setDaemon(1)
69 self.transmitter_thread.start()
70
71 def stop(self):
72 self.alive = False
73
cliechtibf6bb7d2006-03-30 00:28:18 +000074 def join(self, transmit_only=False):
cliechti6385f2c2005-09-21 19:51:19 +000075 self.transmitter_thread.join()
cliechtibf6bb7d2006-03-30 00:28:18 +000076 if not transmit_only:
77 self.receiver_thread.join()
cliechti6385f2c2005-09-21 19:51:19 +000078
79 def reader(self):
80 """loop and copy serial->console"""
81 while self.alive:
82 data = self.serial.read(1)
cliechti9743e222006-04-04 21:48:56 +000083
84 if self.repr_mode == 0:
85 # direct output, just have to care about newline setting
cliechti9cf26222006-03-24 20:09:21 +000086 if data == '\r' and self.convert_outgoing == CONVERT_CR:
87 sys.stdout.write('\n')
88 else:
89 sys.stdout.write(data)
cliechti9743e222006-04-04 21:48:56 +000090 elif self.repr_mode == 1:
91 # escape non-printable, let pass newlines
92 if self.convert_outgoing == CONVERT_CRLF and data in '\r\n':
93 if data == '\n':
94 sys.stdout.write('\n')
95 elif data == '\r':
96 pass
97 elif data == '\n' and self.convert_outgoing == CONVERT_LF:
98 sys.stdout.write('\n')
99 elif data == '\r' and self.convert_outgoing == CONVERT_CR:
100 sys.stdout.write('\n')
101 else:
102 sys.stdout.write(repr(data)[1:-1])
103 elif self.repr_mode == 2:
104 # escape all non-printable, including newline
105 sys.stdout.write(repr(data)[1:-1])
106 elif self.repr_mode == 3:
107 # escape everything (hexdump)
108 for character in data:
109 sys.stdout.write("%s " % character.encode('hex'))
cliechti6385f2c2005-09-21 19:51:19 +0000110 sys.stdout.flush()
cliechti576de252002-02-28 23:54:44 +0000111
cliechti576de252002-02-28 23:54:44 +0000112
cliechti6385f2c2005-09-21 19:51:19 +0000113 def writer(self):
114 """loop and copy console->serial until EXITCHARCTER character is found"""
115 while self.alive:
116 try:
117 c = getkey()
118 except KeyboardInterrupt:
119 c = '\x03'
120 if c == EXITCHARCTER:
121 self.stop()
cliechtibf6bb7d2006-03-30 00:28:18 +0000122 break # exit app
cliechti6385f2c2005-09-21 19:51:19 +0000123 elif c == '\n':
cliechtibf6bb7d2006-03-30 00:28:18 +0000124 self.serial.write(self.newline) # send newline character(s)
125 if self.echo:
126 sys.stdout.write(c) #local echo is a real newline in any case
cliechti6385f2c2005-09-21 19:51:19 +0000127 else:
cliechtibf6bb7d2006-03-30 00:28:18 +0000128 self.serial.write(c) # send character
cliechti6385f2c2005-09-21 19:51:19 +0000129 if self.echo:
130 sys.stdout.write(c)
cliechti8b3ad392002-03-03 20:12:21 +0000131
cliechti8b3ad392002-03-03 20:12:21 +0000132
cliechti6385f2c2005-09-21 19:51:19 +0000133
134def main():
135 import optparse
136
137 parser = optparse.OptionParser(usage="""\
cliechti9743e222006-04-04 21:48:56 +0000138%prog [options] [port [baudrate]]
cliechti6385f2c2005-09-21 19:51:19 +0000139
140Miniterm - A simple terminal program for the serial port.""")
141
142 parser.add_option("-p", "--port", dest="port",
cliechti9743e222006-04-04 21:48:56 +0000143 help="port, a number (default 0) or a device name (deprecated option)",
144 default=None)
cliechti6385f2c2005-09-21 19:51:19 +0000145
146 parser.add_option("-b", "--baud", dest="baudrate", action="store", type='int',
cliechti9743e222006-04-04 21:48:56 +0000147 help="set baudrate, default 9600", default=9600)
cliechti6385f2c2005-09-21 19:51:19 +0000148
149 parser.add_option("", "--parity", dest="parity", action="store",
150 help="set parity, one of [N, E, O], default=N", default='N')
cliechti9cf26222006-03-24 20:09:21 +0000151
152 parser.add_option("-e", "--echo", dest="echo", action="store_true",
153 help="enable local echo (default off)", default=False)
cliechti6385f2c2005-09-21 19:51:19 +0000154
155 parser.add_option("", "--rtscts", dest="rtscts", action="store_true",
156 help="enable RTS/CTS flow control (default off)", default=False)
157
158 parser.add_option("", "--xonxoff", dest="xonxoff", action="store_true",
159 help="enable software flow control (default off)", default=False)
160
cliechti9cf26222006-03-24 20:09:21 +0000161 parser.add_option("", "--cr", dest="cr", action="store_true",
cliechti6385f2c2005-09-21 19:51:19 +0000162 help="do not send CR+LF, send CR only", default=False)
163
cliechti9cf26222006-03-24 20:09:21 +0000164 parser.add_option("", "--lf", dest="lf", action="store_true",
cliechti6385f2c2005-09-21 19:51:19 +0000165 help="do not send CR+LF, send LF only", default=False)
166
cliechti9743e222006-04-04 21:48:56 +0000167 parser.add_option("-D", "--debug", dest="repr_mode", action="count",
168 help="""debug received data (escape non-printable chars)
169--debug can be given multiple times:
1700: just print what is received
1711: escape non-printable characters, do newlines as ususal
1722: escape non-printable characters, newlines too
1733: hex dump everything""", default=0)
cliechti6385f2c2005-09-21 19:51:19 +0000174
cliechtib7d746d2006-03-28 22:44:30 +0000175 parser.add_option("", "--rts", dest="rts_state", action="store", type='int',
176 help="set initial RTS line state (possible values: 0, 1)", default=None)
177
178 parser.add_option("", "--dtr", dest="dtr_state", action="store", type='int',
179 help="set initial DTR line state (possible values: 0, 1)", default=None)
180
cliechtibf6bb7d2006-03-30 00:28:18 +0000181 parser.add_option("-q", "--quiet", dest="quiet", action="store_true",
182 help="suppress non error messages", default=False)
183
cliechti6385f2c2005-09-21 19:51:19 +0000184
185 (options, args) = parser.parse_args()
186
cliechti9cf26222006-03-24 20:09:21 +0000187 if options.cr and options.lf:
188 parser.error("ony one of --cr or --lf can be specified")
189
cliechti9743e222006-04-04 21:48:56 +0000190 port = options.port
191 baudrate = options.baudrate
cliechti9cf26222006-03-24 20:09:21 +0000192 if args:
cliechti9743e222006-04-04 21:48:56 +0000193 if options.port is not None:
194 parser.error("no arguments are allowed, options only when --port is given")
195 port = args.pop(0)
196 if args:
197 try:
198 baudrate = int(args[0])
199 except ValueError:
200 parser.error("baudrate must be a number, not %r" % args[0])
201 args.pop(0)
202 if args:
203 parser.error("too many arguments")
204 else:
205 if port is None: port = 0
cliechti6385f2c2005-09-21 19:51:19 +0000206
207 convert_outgoing = CONVERT_CRLF
cliechti9cf26222006-03-24 20:09:21 +0000208 if options.cr:
cliechti6385f2c2005-09-21 19:51:19 +0000209 convert_outgoing = CONVERT_CR
cliechti9cf26222006-03-24 20:09:21 +0000210 elif options.lf:
cliechti6385f2c2005-09-21 19:51:19 +0000211 convert_outgoing = CONVERT_LF
212
213 try:
214 miniterm = Miniterm(
cliechti9743e222006-04-04 21:48:56 +0000215 port,
216 baudrate,
cliechti6385f2c2005-09-21 19:51:19 +0000217 options.parity,
218 rtscts=options.rtscts,
219 xonxoff=options.xonxoff,
220 echo=options.echo,
221 convert_outgoing=convert_outgoing,
222 repr_mode=options.repr_mode,
223 )
224 except serial.SerialException:
cliechti9743e222006-04-04 21:48:56 +0000225 sys.stderr.write("could not open port %r" % port)
cliechti6385f2c2005-09-21 19:51:19 +0000226 sys.exit(1)
227
cliechtibf6bb7d2006-03-30 00:28:18 +0000228 if not options.quiet:
cliechti9743e222006-04-04 21:48:56 +0000229 sys.stderr.write('--- Miniterm on %s: %d,%s,%s,%s. Type Ctrl-] to quit. ---\n' % (
230 miniterm.serial.portstr,
231 miniterm.serial.baudrate,
232 miniterm.serial.bytesize,
233 miniterm.serial.parity,
234 miniterm.serial.stopbits,
235 ))
cliechtib7d746d2006-03-28 22:44:30 +0000236 if options.dtr_state is not None:
cliechtibf6bb7d2006-03-30 00:28:18 +0000237 if not options.quiet:
238 sys.stderr.write('--- forcing DTR %s\n' % (options.dtr_state and 'active' or 'inactive'))
cliechtib7d746d2006-03-28 22:44:30 +0000239 miniterm.serial.setDTR(options.dtr_state)
cliechtibf6bb7d2006-03-30 00:28:18 +0000240 if options.rts_state is not None:
241 if not options.quiet:
242 sys.stderr.write('--- forcing RTS %s\n' % (options.rts_state and 'active' or 'inactive'))
243 miniterm.serial.setRTS(options.rts_state)
cliechtib7d746d2006-03-28 22:44:30 +0000244
cliechti6385f2c2005-09-21 19:51:19 +0000245 miniterm.start()
cliechtibf6bb7d2006-03-30 00:28:18 +0000246 miniterm.join(True)
247 if not options.quiet:
248 sys.stderr.write("\n--- exit ---\n")
cliechti6385f2c2005-09-21 19:51:19 +0000249 miniterm.join()
cliechtibf6bb7d2006-03-30 00:28:18 +0000250
cliechti8b3ad392002-03-03 20:12:21 +0000251
252if __name__ == '__main__':
cliechti6385f2c2005-09-21 19:51:19 +0000253 main()