blob: 151d3c3545c22d7a0d369fa84a842ad77df3a85f [file] [log] [blame]
cliechtief39b8b2009-08-07 18:22:49 +00001#!/usr/bin/env python
2
3# (C) 2009 Chris Liechti <cliechti@gmx.net>
4# redirect data from a TCP/IP connection to a serial port and vice versa
5# using RFC 2217
6
7
8import sys
9import os
10import threading
11import time
12import socket
13import serial
14import serial.rfc2217
15
16class Redirector:
17 def __init__(self, serial_instance, socket):
18 self.serial = serial_instance
19 self.socket = socket
20 self._write_lock = threading.Lock()
21 self.rfc2217 = serial.rfc2217.PortManager(self.serial, self, debug_output=False)
22
23 def statusline_poller(self):
24 while self.alive:
25 time.sleep(1)
26 self.rfc2217.check_modem_lines()
27
28 def shortcut(self):
29 """connect the serial port to the TCP port by copying everything
30 from one side to the other"""
31 self.alive = True
32 self.thread_read = threading.Thread(target=self.reader)
33 self.thread_read.setDaemon(True)
34 self.thread_read.setName('serial->socket')
35 self.thread_read.start()
36 self.thread_poll = threading.Thread(target=self.statusline_poller)
37 self.thread_poll.setDaemon(True)
38 self.thread_poll.setName('status line poll')
39 self.thread_poll.start()
40 self.writer()
41
42 def reader(self):
43 """loop forever and copy serial->socket"""
44 while self.alive:
45 try:
46 data = self.serial.read(1) # read one, blocking
47 n = self.serial.inWaiting() # look if there is more
48 if n:
49 data = data + self.serial.read(n) # and get as much as possible
50 if data:
51 # escape outgoing data when needed (Telnet IAC (0xff) character)
52 data = serial.to_bytes(self.rfc2217.escape(data))
53 self._write_lock.acquire()
54 try:
55 self.socket.sendall(data) # send it over TCP
56 finally:
57 self._write_lock.release()
58 except socket.error, msg:
59 sys.stderr.write('ERROR: %s\n' % msg)
60 # probably got disconnected
61 break
62 self.alive = False
63
64 def write(self, data):
65 """thread safe socket write with no data escaping. used to send telnet stuff"""
66 self._write_lock.acquire()
67 try:
68 self.socket.sendall(data)
69 finally:
70 self._write_lock.release()
71
72 def writer(self):
73 """loop forever and copy socket->serial"""
74 while self.alive:
75 try:
76 data = self.socket.recv(1024)
77 if not data:
78 break
79 self.serial.write(serial.to_bytes(self.rfc2217.filter(data)))
80 except socket.error, msg:
81 sys.stderr.write('ERROR: %s\n' % msg)
82 # probably got disconnected
83 break
84 self.alive = False
85 self.thread_read.join()
86
87 def stop(self):
88 """Stop copying"""
89 if self.alive:
90 self.alive = False
91 self.thread_read.join()
92
93
94if __name__ == '__main__':
95 import optparse
96
97 parser = optparse.OptionParser(
98 usage = "%prog [options] [port [baudrate]]",
99 description = "RFC 2217 Serial to Network (TCP/IP) redirector.",
100 epilog = """\
101NOTE: no security measures are implemented. Anyone can remotely connect
102to this service over the network.
103
104Only one connection at once is supported. When the connection is terminated
105it waits for the next connect.
106""")
107
108 parser.add_option("-p", "--localport",
109 dest = "local_port",
110 action = "store",
111 type = 'int',
112 help = "local TCP port",
113 default = 2217
114 )
115
116 (options, args) = parser.parse_args()
117
118 if len(args) != 1:
119 parser.error('serial port name required as argument')
120
121 # connect to serial port
122 ser = serial.Serial()
123 ser.port = args[0]
124 ser.timeout = 1 # required so that the reader thread can exit
125
126 sys.stderr.write("--- RFC 2217 TCP/IP to Serial redirector --- type Ctrl-C / BREAK to quit\n")
127
128 try:
129 ser.open()
130 except serial.SerialException, e:
131 sys.stderr.write("Could not open serial port %s: %s\n" % (ser.portstr, e))
132 sys.exit(1)
133
134 sys.stderr.write("--- Serving serial port: %s\n" % (ser.portstr,))
135
136 srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
137 srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
138 srv.bind( ('', options.local_port) )
139 srv.listen(1)
140 sys.stderr.write("--- TCP/IP port: %s\n" % (options.local_port,))
141 while True:
142 try:
143 connection, addr = srv.accept()
144 sys.stderr.write('Connected by %s\n' % (addr,))
145 # enter console->serial loop
146 r = Redirector(
147 ser,
148 connection,
149 )
150 r.shortcut()
151 sys.stderr.write('Disconnected\n')
152 connection.close()
153 except KeyboardInterrupt:
154 break
155 except socket.error, msg:
156 sys.stderr.write('ERROR: %s\n' % msg)
157
158 sys.stderr.write('\n--- exit ---\n')
159