PageRenderTime 31ms CodeModel.GetById 44ms RepoModel.GetById 19ms app.codeStats 0ms

/pyserial/examples/tcp_serial_redirect.py

https://github.com/bewest/pyserial
Python | 326 lines | 316 code | 5 blank | 5 comment | 11 complexity | 37e51f08a201f55ceaa2d79f32e4d5ff MD5 | raw file
  1. #!/usr/bin/env python
  2. # (C) 2002-2009 Chris Liechti <cliechti@gmx.net>
  3. # redirect data from a TCP/IP connection to a serial port and vice versa
  4. # requires Python 2.2 'cause socket.sendall is used
  5. import sys
  6. import os
  7. import time
  8. import threading
  9. import socket
  10. import codecs
  11. import serial
  12. try:
  13. True
  14. except NameError:
  15. True = 1
  16. False = 0
  17. class Redirector:
  18. def __init__(self, serial_instance, socket, ser_newline=None, net_newline=None, spy=False):
  19. self.serial = serial_instance
  20. self.socket = socket
  21. self.ser_newline = ser_newline
  22. self.net_newline = net_newline
  23. self.spy = spy
  24. self._write_lock = threading.Lock()
  25. def shortcut(self):
  26. """connect the serial port to the TCP port by copying everything
  27. from one side to the other"""
  28. self.alive = True
  29. self.thread_read = threading.Thread(target=self.reader)
  30. self.thread_read.setDaemon(True)
  31. self.thread_read.setName('serial->socket')
  32. self.thread_read.start()
  33. self.writer()
  34. def reader(self):
  35. """loop forever and copy serial->socket"""
  36. while self.alive:
  37. try:
  38. data = self.serial.read(1) # read one, blocking
  39. n = self.serial.inWaiting() # look if there is more
  40. if n:
  41. data = data + self.serial.read(n) # and get as much as possible
  42. if data:
  43. # the spy shows what's on the serial port, so log it before converting newlines
  44. if self.spy:
  45. sys.stdout.write(codecs.escape_encode(data)[0])
  46. sys.stdout.flush()
  47. if self.ser_newline and self.net_newline:
  48. # do the newline conversion
  49. # XXX fails for CR+LF in input when it is cut in half at the begin or end of the string
  50. data = net_newline.join(data.split(ser_newline))
  51. # escape outgoing data when needed (Telnet IAC (0xff) character)
  52. self._write_lock.acquire()
  53. try:
  54. self.socket.sendall(data) # send it over TCP
  55. finally:
  56. self._write_lock.release()
  57. except socket.error, msg:
  58. sys.stderr.write('ERROR: %s\n' % msg)
  59. # probably got disconnected
  60. break
  61. self.alive = False
  62. def write(self, data):
  63. """thread safe socket write with no data escaping. used to send telnet stuff"""
  64. self._write_lock.acquire()
  65. try:
  66. self.socket.sendall(data)
  67. finally:
  68. self._write_lock.release()
  69. def writer(self):
  70. """loop forever and copy socket->serial"""
  71. while self.alive:
  72. try:
  73. data = self.socket.recv(1024)
  74. if not data:
  75. break
  76. if self.ser_newline and self.net_newline:
  77. # do the newline conversion
  78. # XXX fails for CR+LF in input when it is cut in half at the begin or end of the string
  79. data = ser_newline.join(data.split(net_newline))
  80. self.serial.write(data) # get a bunch of bytes and send them
  81. # the spy shows what's on the serial port, so log it after converting newlines
  82. if self.spy:
  83. sys.stdout.write(codecs.escape_encode(data)[0])
  84. sys.stdout.flush()
  85. except socket.error, msg:
  86. sys.stderr.write('ERROR: %s\n' % msg)
  87. # probably got disconnected
  88. break
  89. self.alive = False
  90. self.thread_read.join()
  91. def stop(self):
  92. """Stop copying"""
  93. if self.alive:
  94. self.alive = False
  95. self.thread_read.join()
  96. if __name__ == '__main__':
  97. import optparse
  98. parser = optparse.OptionParser(
  99. usage = "%prog [options] [port [baudrate]]",
  100. description = "Simple Serial to Network (TCP/IP) redirector.",
  101. epilog = """\
  102. NOTE: no security measures are implemented. Anyone can remotely connect
  103. to this service over the network.
  104. Only one connection at once is supported. When the connection is terminated
  105. it waits for the next connect.
  106. """)
  107. parser.add_option("-q", "--quiet",
  108. dest = "quiet",
  109. action = "store_true",
  110. help = "suppress non error messages",
  111. default = False
  112. )
  113. parser.add_option("--spy",
  114. dest = "spy",
  115. action = "store_true",
  116. help = "peek at the communication and print all data to the console",
  117. default = False
  118. )
  119. group = optparse.OptionGroup(parser,
  120. "Serial Port",
  121. "Serial port settings"
  122. )
  123. parser.add_option_group(group)
  124. group.add_option("-p", "--port",
  125. dest = "port",
  126. help = "port, a number (default 0) or a device name",
  127. default = None
  128. )
  129. group.add_option("-b", "--baud",
  130. dest = "baudrate",
  131. action = "store",
  132. type = 'int',
  133. help = "set baud rate, default: %default",
  134. default = 9600
  135. )
  136. group.add_option("", "--parity",
  137. dest = "parity",
  138. action = "store",
  139. help = "set parity, one of [N, E, O], default=%default",
  140. default = 'N'
  141. )
  142. group.add_option("--rtscts",
  143. dest = "rtscts",
  144. action = "store_true",
  145. help = "enable RTS/CTS flow control (default off)",
  146. default = False
  147. )
  148. group.add_option("--xonxoff",
  149. dest = "xonxoff",
  150. action = "store_true",
  151. help = "enable software flow control (default off)",
  152. default = False
  153. )
  154. group.add_option("--rts",
  155. dest = "rts_state",
  156. action = "store",
  157. type = 'int',
  158. help = "set initial RTS line state (possible values: 0, 1)",
  159. default = None
  160. )
  161. group.add_option("--dtr",
  162. dest = "dtr_state",
  163. action = "store",
  164. type = 'int',
  165. help = "set initial DTR line state (possible values: 0, 1)",
  166. default = None
  167. )
  168. group = optparse.OptionGroup(parser,
  169. "Network settings",
  170. "Network configuration."
  171. )
  172. parser.add_option_group(group)
  173. group.add_option("-P", "--localport",
  174. dest = "local_port",
  175. action = "store",
  176. type = 'int',
  177. help = "local TCP port",
  178. default = 7777
  179. )
  180. group = optparse.OptionGroup(parser,
  181. "Newline Settings",
  182. "Convert newlines between network and serial port. Conversion is normally disabled and can be enabled by --convert."
  183. )
  184. parser.add_option_group(group)
  185. group.add_option("-c", "--convert",
  186. dest = "convert",
  187. action = "store_true",
  188. help = "enable newline conversion (default off)",
  189. default = False
  190. )
  191. group.add_option("--net-nl",
  192. dest = "net_newline",
  193. action = "store",
  194. help = "type of newlines that are expected on the network (default: %default)",
  195. default = "LF"
  196. )
  197. group.add_option("--ser-nl",
  198. dest = "ser_newline",
  199. action = "store",
  200. help = "type of newlines that are expected on the serial port (default: %default)",
  201. default = "CR+LF"
  202. )
  203. (options, args) = parser.parse_args()
  204. # get port and baud rate from command line arguments or the option switches
  205. port = options.port
  206. baudrate = options.baudrate
  207. if args:
  208. if options.port is not None:
  209. parser.error("no arguments are allowed, options only when --port is given")
  210. port = args.pop(0)
  211. if args:
  212. try:
  213. baudrate = int(args[0])
  214. except ValueError:
  215. parser.error("baud rate must be a number, not %r" % args[0])
  216. args.pop(0)
  217. if args:
  218. parser.error("too many arguments")
  219. else:
  220. if port is None: port = 0
  221. # check newline modes for network connection
  222. mode = options.net_newline.upper()
  223. if mode == 'CR':
  224. net_newline = '\r'
  225. elif mode == 'LF':
  226. net_newline = '\n'
  227. elif mode == 'CR+LF' or mode == 'CRLF':
  228. net_newline = '\r\n'
  229. else:
  230. parser.error("Invalid value for --net-nl. Valid are 'CR', 'LF' and 'CR+LF'/'CRLF'.")
  231. # check newline modes for serial connection
  232. mode = options.ser_newline.upper()
  233. if mode == 'CR':
  234. ser_newline = '\r'
  235. elif mode == 'LF':
  236. ser_newline = '\n'
  237. elif mode == 'CR+LF' or mode == 'CRLF':
  238. ser_newline = '\r\n'
  239. else:
  240. parser.error("Invalid value for --ser-nl. Valid are 'CR', 'LF' and 'CR+LF'/'CRLF'.")
  241. # connect to serial port
  242. ser = serial.Serial()
  243. ser.port = port
  244. ser.baudrate = baudrate
  245. ser.parity = options.parity
  246. ser.rtscts = options.rtscts
  247. ser.xonxoff = options.xonxoff
  248. ser.timeout = 1 # required so that the reader thread can exit
  249. if not options.quiet:
  250. sys.stderr.write("--- TCP/IP to Serial redirector --- type Ctrl-C / BREAK to quit\n")
  251. sys.stderr.write("--- %s %s,%s,%s,%s ---\n" % (ser.portstr, ser.baudrate, 8, ser.parity, 1))
  252. try:
  253. ser.open()
  254. except serial.SerialException, e:
  255. sys.stderr.write("Could not open serial port %s: %s\n" % (ser.portstr, e))
  256. sys.exit(1)
  257. if options.rts_state is not None:
  258. ser.setRTS(options.rts_state)
  259. if options.dtr_state is not None:
  260. ser.setDTR(options.dtr_state)
  261. srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  262. srv.bind( ('', options.local_port) )
  263. srv.listen(1)
  264. while True:
  265. try:
  266. sys.stderr.write("Waiting for connection on %s...\n" % options.local_port)
  267. connection, addr = srv.accept()
  268. sys.stderr.write('Connected by %s\n' % (addr,))
  269. # enter network <-> serial loop
  270. r = Redirector(
  271. ser,
  272. connection,
  273. options.convert and ser_newline or None,
  274. options.convert and net_newline or None,
  275. options.spy,
  276. )
  277. r.shortcut()
  278. if options.spy: sys.stdout.write('\n')
  279. sys.stderr.write('Disconnected\n')
  280. connection.close()
  281. except KeyboardInterrupt:
  282. break
  283. except socket.error, msg:
  284. sys.stderr.write('ERROR: %s\n' % msg)
  285. sys.stderr.write('\n--- exit ---\n')