summaryrefslogtreecommitdiff
path: root/examples/rfc2217_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/rfc2217_server.py')
-rw-r--r--examples/rfc2217_server.py204
1 files changed, 204 insertions, 0 deletions
diff --git a/examples/rfc2217_server.py b/examples/rfc2217_server.py
new file mode 100644
index 0000000..81fb520
--- /dev/null
+++ b/examples/rfc2217_server.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+
+# (C) 2009 Chris Liechti <cliechti@gmx.net>
+# redirect data from a TCP/IP connection to a serial port and vice versa
+# using RFC 2217
+
+
+import sys
+import os
+import threading
+import time
+import socket
+import serial
+import serial.rfc2217
+import logging
+
+class Redirector:
+ def __init__(self, serial_instance, socket, debug=None):
+ self.serial = serial_instance
+ self.socket = socket
+ self._write_lock = threading.Lock()
+ self.rfc2217 = serial.rfc2217.PortManager(
+ self.serial,
+ self,
+ logger = (debug and logging.getLogger('rfc2217.server'))
+ )
+ self.log = logging.getLogger('redirector')
+
+ def statusline_poller(self):
+ self.log.debug('status line poll thread started')
+ while self.alive:
+ time.sleep(1)
+ self.rfc2217.check_modem_lines()
+ self.log.debug('status line poll thread terminated')
+
+ def shortcut(self):
+ """connect the serial port to the TCP port by copying everything
+ from one side to the other"""
+ self.alive = True
+ self.thread_read = threading.Thread(target=self.reader)
+ self.thread_read.setDaemon(True)
+ self.thread_read.setName('serial->socket')
+ self.thread_read.start()
+ self.thread_poll = threading.Thread(target=self.statusline_poller)
+ self.thread_poll.setDaemon(True)
+ self.thread_poll.setName('status line poll')
+ self.thread_poll.start()
+ self.writer()
+
+ def reader(self):
+ """loop forever and copy serial->socket"""
+ self.log.debug('reader thread started')
+ while self.alive:
+ try:
+ data = self.serial.read(1) # read one, blocking
+ n = self.serial.inWaiting() # look if there is more
+ if n:
+ data = data + self.serial.read(n) # and get as much as possible
+ if data:
+ # escape outgoing data when needed (Telnet IAC (0xff) character)
+ data = serial.to_bytes(self.rfc2217.escape(data))
+ self._write_lock.acquire()
+ try:
+ self.socket.sendall(data) # send it over TCP
+ finally:
+ self._write_lock.release()
+ except socket.error, msg:
+ self.log.error('%s' % (msg,))
+ # probably got disconnected
+ break
+ self.alive = False
+ self.log.debug('reader thread terminated')
+
+ def write(self, data):
+ """thread safe socket write with no data escaping. used to send telnet stuff"""
+ self._write_lock.acquire()
+ try:
+ self.socket.sendall(data)
+ finally:
+ self._write_lock.release()
+
+ def writer(self):
+ """loop forever and copy socket->serial"""
+ while self.alive:
+ try:
+ data = self.socket.recv(1024)
+ if not data:
+ break
+ self.serial.write(serial.to_bytes(self.rfc2217.filter(data)))
+ except socket.error, msg:
+ self.log.error('%s' % (msg,))
+ # probably got disconnected
+ break
+ self.stop()
+
+ def stop(self):
+ """Stop copying"""
+ self.log.debug('stopping')
+ if self.alive:
+ self.alive = False
+ self.thread_read.join()
+ self.thread_poll.join()
+
+
+if __name__ == '__main__':
+ import optparse
+
+ parser = optparse.OptionParser(
+ usage = "%prog [options] port",
+ description = "RFC 2217 Serial to Network (TCP/IP) redirector.",
+ epilog = """\
+NOTE: no security measures are implemented. Anyone can remotely connect
+to this service over the network.
+
+Only one connection at once is supported. When the connection is terminated
+it waits for the next connect.
+""")
+
+ parser.add_option("-p", "--localport",
+ dest = "local_port",
+ action = "store",
+ type = 'int',
+ help = "local TCP port",
+ default = 2217
+ )
+
+ parser.add_option("-v", "--verbose",
+ dest = "verbosity",
+ action = "count",
+ help = "print more diagnostic messages (option can be given multiple times)",
+ default = 0
+ )
+
+ (options, args) = parser.parse_args()
+
+ if len(args) != 1:
+ parser.error('serial port name required as argument')
+
+ if options.verbosity > 3:
+ options.verbosity = 3
+ level = (
+ logging.WARNING,
+ logging.INFO,
+ logging.DEBUG,
+ logging.NOTSET,
+ )[options.verbosity]
+ logging.basicConfig(level=logging.INFO)
+ logging.getLogger('root').setLevel(logging.INFO)
+ logging.getLogger('rfc2217').setLevel(level)
+
+ # connect to serial port
+ ser = serial.Serial()
+ ser.port = args[0]
+ ser.timeout = 3 # required so that the reader thread can exit
+
+ logging.info("RFC 2217 TCP/IP to Serial redirector - type Ctrl-C / BREAK to quit")
+
+ try:
+ ser.open()
+ except serial.SerialException, e:
+ logging.error("Could not open serial port %s: %s" % (ser.portstr, e))
+ sys.exit(1)
+
+ logging.info("Serving serial port: %s" % (ser.portstr,))
+ settings = ser.getSettingsDict()
+ # reset contol line as no _remote_ "terminal" has been connected yet
+ ser.setDTR(False)
+ ser.setRTS(False)
+
+ srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ srv.bind( ('', options.local_port) )
+ srv.listen(1)
+ logging.info("TCP/IP port: %s" % (options.local_port,))
+ while True:
+ try:
+ connection, addr = srv.accept()
+ logging.info('Connected by %s:%s' % (addr[0], addr[1]))
+ connection.setsockopt( socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ ser.setRTS(True)
+ ser.setDTR(True)
+ # enter network <-> serial loop
+ r = Redirector(
+ ser,
+ connection,
+ options.verbosity > 0
+ )
+ try:
+ r.shortcut()
+ finally:
+ logging.info('Disconnected')
+ r.stop()
+ connection.close()
+ ser.setDTR(False)
+ ser.setRTS(False)
+ # Restore port settings (may have been changed by RFC 2217 capable
+ # client)
+ ser.applySettingsDict(settings)
+ except KeyboardInterrupt:
+ break
+ except socket.error, msg:
+ logging.error('%s' % (msg,))
+
+ logging.info('--- exit ---')