From 2a6d533e6f0a025c80ff43dbfbec68e675566307 Mon Sep 17 00:00:00 2001 From: cliechti Date: Fri, 4 Mar 2011 02:08:32 +0000 Subject: implement URL handler as modules and import them at runtime. this allows to create more handlers as plug-in for pySerial --- pyserial/serial/__init__.py | 20 +- pyserial/serial/loopback_connection.py | 260 ------------------------- pyserial/serial/socket_connection.py | 259 ------------------------ pyserial/serial/urlhandler/__init__.py | 0 pyserial/serial/urlhandler/protocol_loop.py | 260 +++++++++++++++++++++++++ pyserial/serial/urlhandler/protocol_rfc2217.py | 11 ++ pyserial/serial/urlhandler/protocol_socket.py | 259 ++++++++++++++++++++++++ 7 files changed, 540 insertions(+), 529 deletions(-) delete mode 100644 pyserial/serial/loopback_connection.py delete mode 100644 pyserial/serial/socket_connection.py create mode 100644 pyserial/serial/urlhandler/__init__.py create mode 100644 pyserial/serial/urlhandler/protocol_loop.py create mode 100644 pyserial/serial/urlhandler/protocol_rfc2217.py create mode 100644 pyserial/serial/urlhandler/protocol_socket.py diff --git a/pyserial/serial/__init__.py b/pyserial/serial/__init__.py index 2c49007..cc5e6dc 100644 --- a/pyserial/serial/__init__.py +++ b/pyserial/serial/__init__.py @@ -38,18 +38,18 @@ def serial_for_url(url, *args, **kwargs): try: url_nocase = url.lower() except AttributeError: - # its not a string, use default + # it's not a string, use default pass else: - if url_nocase.startswith('rfc2217://'): - import rfc2217 # late import, so that users that don't use it don't have to load it - klass = rfc2217.Serial # RFC2217 implementation - elif url_nocase.startswith('socket://'): - import socket_connection # late import, so that users that don't use it don't have to load it - klass = socket_connection.Serial - elif url_nocase.startswith('loop://'): - import loopback_connection # late import, so that users that don't use it don't have to load it - klass = loopback_connection.Serial + if '://' in url_nocase: + protocol = url_nocase.split('://', 1)[0] + module_name = 'serial.urlhandler.protocol_%s' % (protocol,) + try: + handler_module = __import__(module_name) + except ImportError: + raise ValueError('invalid URL, protocol %r not known' % (protocol,)) + else: + klass = sys.modules[module_name].Serial else: klass = Serial # 'native' implementation # instantiate and open when desired diff --git a/pyserial/serial/loopback_connection.py b/pyserial/serial/loopback_connection.py deleted file mode 100644 index 784d7f8..0000000 --- a/pyserial/serial/loopback_connection.py +++ /dev/null @@ -1,260 +0,0 @@ -#! python -# -# Python Serial Port Extension for Win32, Linux, BSD, Jython -# see __init__.py -# -# This module implements a loop back connection receiving itself what it sent. -# -# The purpose of this module is.. well... You can run the unit tests with it. -# and it was so easy to implement ;-) -# -# (C) 2001-2009 Chris Liechti -# this is distributed under a free software license, see license.txt -# -# URL format: loop://[option[/option...]] -# options: -# - "debug" print diagnostic messages - -from serialutil import * -import threading -import time -import logging - -# map log level names to constants. used in fromURL() -LOGGER_LEVELS = { - 'debug': logging.DEBUG, - 'info': logging.INFO, - 'warning': logging.WARNING, - 'error': logging.ERROR, - } - - -class LoopbackSerial(SerialBase): - """Serial port implementation for plain sockets.""" - - BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, - 9600, 19200, 38400, 57600, 115200) - - def open(self): - """Open port with current settings. This may throw a SerialException - if the port cannot be opened.""" - self.logger = None - self.buffer_lock = threading.Lock() - self.loop_buffer = bytearray() - self.cts = False - self.dsr = False - - if self._port is None: - raise SerialException("Port must be configured before it can be used.") - # not that there is anything to open, but the function applies the - # options found in the URL - self.fromURL(self.port) - - # not that there anything to configure... - self._reconfigurePort() - # all things set up get, now a clean start - self._isOpen = True - if not self._rtscts: - self.setRTS(True) - self.setDTR(True) - self.flushInput() - self.flushOutput() - - def _reconfigurePort(self): - """Set communication parameters on opened port. for the loop:// - protocol all settings are ignored!""" - # not that's it of any real use, but it helps in the unit tests - if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32: - raise ValueError("invalid baudrate: %r" % (self._baudrate)) - if self.logger: - self.logger.info('_reconfigurePort()') - - def close(self): - """Close port""" - if self._isOpen: - self._isOpen = False - # in case of quick reconnects, give the server some time - time.sleep(0.3) - - def makeDeviceName(self, port): - raise SerialException("there is no sensible way to turn numbers into URLs") - - def fromURL(self, url): - """extract host and port from an URL string""" - if url.lower().startswith("loop://"): url = url[7:] - try: - # process options now, directly altering self - for option in url.split('/'): - if '=' in option: - option, value = option.split('=', 1) - else: - value = None - if not option: - pass - elif option == 'logging': - logging.basicConfig() # XXX is that good to call it here? - self.logger = logging.getLogger('pySerial.loop') - self.logger.setLevel(LOGGER_LEVELS[value]) - self.logger.debug('enabled logging') - else: - raise ValueError('unknown option: %r' % (option,)) - except ValueError, e: - raise SerialException('expected a string in the form "[loop://][option[/option...]]": %s' % e) - - # - - - - - - - - - - - - - - - - - - - - - - - - - - def inWaiting(self): - """Return the number of characters currently in the input buffer.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - # attention the logged value can differ from return value in - # threaded environments... - self.logger.debug('inWaiting() -> %d' % (len(self.loop_buffer),)) - return len(self.loop_buffer) - - def read(self, size=1): - """Read size bytes from the serial port. If a timeout is set it may - return less characters as requested. With no timeout it will block - until the requested number of bytes is read.""" - if not self._isOpen: raise portNotOpenError - if self._timeout is not None: - timeout = time.time() + self._timeout - else: - timeout = None - data = bytearray() - while len(data) < size: - self.buffer_lock.acquire() - try: - block = to_bytes(self.loop_buffer[:size]) - del self.loop_buffer[:size] - finally: - self.buffer_lock.release() - data += block - # check for timeout now, after data has been read. - # useful for timeout = 0 (non blocking) read - if timeout and time.time() > timeout: - break - return bytes(data) - - def write(self, data): - """Output the given string over the serial port. Can block if the - connection is blocked. May raise SerialException if the connection is - closed.""" - if not self._isOpen: raise portNotOpenError - # calculate aprox time that would be used to send the data - time_used_to_send = 10.0*len(data) / self._baudrate - # when a write timeout is configured check if we would be successful - # (not sending anything, not even the part that would have time) - if self._writeTimeout is not None and time_used_to_send > self._writeTimeout: - time.sleep(self._writeTimeout) # must wait so that unit test succeeds - raise writeTimeoutError - self.buffer_lock.acquire() - try: - self.loop_buffer += bytes(data) - finally: - self.buffer_lock.release() - return len(data) - - def flushInput(self): - """Clear input buffer, discarding all that is in the buffer.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('flushInput()') - self.buffer_lock.acquire() - try: - del self.loop_buffer[:] - finally: - self.buffer_lock.release() - - def flushOutput(self): - """Clear output buffer, aborting the current output and - discarding all that is in the buffer.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('flushOutput()') - - def sendBreak(self, duration=0.25): - """Send break condition. Timed, returns to idle state after given - duration.""" - if not self._isOpen: raise portNotOpenError - - def setBreak(self, level=True): - """Set break: Controls TXD. When active, to transmitting is - possible.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('setBreak(%r)' % (level,)) - - def setRTS(self, level=True): - """Set terminal status line: Request To Send""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('setRTS(%r) -> state of CTS' % (level,)) - self.cts = level - - def setDTR(self, level=True): - """Set terminal status line: Data Terminal Ready""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('setDTR(%r) -> state of DSR' % (level,)) - self.dsr = level - - def getCTS(self): - """Read terminal status line: Clear To Send""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('getCTS() -> state of RTS (%r)' % (self.cts,)) - return self.cts - - def getDSR(self): - """Read terminal status line: Data Set Ready""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('getDSR() -> state of DTR (%r)' % (self.dsr,)) - return self.dsr - - def getRI(self): - """Read terminal status line: Ring Indicator""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('returning dummy for getRI()') - return False - - def getCD(self): - """Read terminal status line: Carrier Detect""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('returning dummy for getCD()') - return True - - # - - - platform specific - - - - # None so far - - -# assemble Serial class with the platform specific implementation and the base -# for file-like behavior. for Python 2.6 and newer, that provide the new I/O -# library, derive from io.RawIOBase -try: - import io -except ImportError: - # classic version with our own file-like emulation - class Serial(LoopbackSerial, FileLike): - pass -else: - # io library present - class Serial(LoopbackSerial, io.RawIOBase): - pass - - -# simple client test -if __name__ == '__main__': - import sys - s = Serial('socket://localhost:7000') - sys.stdout.write('%s\n' % s) - - sys.stdout.write("write...\n") - s.write("hello\n") - s.flush() - sys.stdout.write("read: %s\n" % s.read(5)) - - s.close() diff --git a/pyserial/serial/socket_connection.py b/pyserial/serial/socket_connection.py deleted file mode 100644 index e34fc2c..0000000 --- a/pyserial/serial/socket_connection.py +++ /dev/null @@ -1,259 +0,0 @@ -#! python -# -# Python Serial Port Extension for Win32, Linux, BSD, Jython -# see __init__.py -# -# This module implements a simple socket based client. -# It does not support changing any port parameters and will silently ignore any -# requests to do so. -# -# The purpose of this module is that applications using pySerial can connect to -# TCP/IP to serial port converters that do not support RFC 2217. -# -# (C) 2001-2009 Chris Liechti -# this is distributed under a free software license, see license.txt -# -# URL format: socket://:[/option[/option...]] -# options: -# - "debug" print diagnostic messages - -from serialutil import * -import time -import socket -import logging - -# map log level names to constants. used in fromURL() -LOGGER_LEVELS = { - 'debug': logging.DEBUG, - 'info': logging.INFO, - 'warning': logging.WARNING, - 'error': logging.ERROR, - } - - -class SocketSerial(SerialBase): - """Serial port implementation for plain sockets.""" - - BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, - 9600, 19200, 38400, 57600, 115200) - - def open(self): - """Open port with current settings. This may throw a SerialException - if the port cannot be opened.""" - self.logger = None - if self._port is None: - raise SerialException("Port must be configured before it can be used.") - try: - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self._socket.connect(self.fromURL(self.portstr)) - except Exception, msg: - self._socket = None - raise SerialException("Could not open port %s: %s" % (self.portstr, msg)) - - self._socket.settimeout(2) # used for write timeout support :/ - - # not that there anything to configure... - self._reconfigurePort() - # all things set up get, now a clean start - self._isOpen = True - if not self._rtscts: - self.setRTS(True) - self.setDTR(True) - self.flushInput() - self.flushOutput() - - def _reconfigurePort(self): - """Set communication parameters on opened port. for the socket:// - protocol all settings are ignored!""" - if self._socket is None: - raise SerialException("Can only operate on open ports") - if self.logger: - self.logger.info('ignored port configuration change') - - def close(self): - """Close port""" - if self._isOpen: - if self._socket: - try: - self._socket.shutdown(socket.SHUT_RDWR) - self._socket.close() - except: - # ignore errors. - pass - self._socket = None - self._isOpen = False - # in case of quick reconnects, give the server some time - time.sleep(0.3) - - def makeDeviceName(self, port): - raise SerialException("there is no sensible way to turn numbers into URLs") - - def fromURL(self, url): - """extract host and port from an URL string""" - if url.lower().startswith("socket://"): url = url[9:] - try: - # is there a "path" (our options)? - if '/' in url: - # cut away options - url, options = url.split('/', 1) - # process options now, directly altering self - for option in options.split('/'): - if '=' in option: - option, value = option.split('=', 1) - else: - value = None - if option == 'logging': - logging.basicConfig() # XXX is that good to call it here? - self.logger = logging.getLogger('pySerial.socket') - self.logger.setLevel(LOGGER_LEVELS[value]) - self.logger.debug('enabled logging') - else: - raise ValueError('unknown option: %r' % (option,)) - # get host and port - host, port = url.split(':', 1) # may raise ValueError because of unpacking - port = int(port) # and this if it's not a number - if not 0 <= port < 65536: raise ValueError("port not in range 0...65535") - except ValueError, e: - raise SerialException('expected a string in the form "[rfc2217://]:[/option[/option...]]": %s' % e) - return (host, port) - - # - - - - - - - - - - - - - - - - - - - - - - - - - - def inWaiting(self): - """Return the number of characters currently in the input buffer.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - # set this one to debug as the function could be called often... - self.logger.debug('WARNING: inWaiting returns dummy value') - return 0 # hmmm, see comment in read() - - def read(self, size=1): - """Read size bytes from the serial port. If a timeout is set it may - return less characters as requested. With no timeout it will block - until the requested number of bytes is read.""" - if not self._isOpen: raise portNotOpenError - data = bytearray() - timeout = time.time() + self._timeout - while len(data) < size and time.time() < timeout: - try: - # an implementation with internal buffer would be better - # performing... - data = self._socket.recv(size - len(data)) - except socket.timeout: - # just need to get out of recv form time to time to check if - # still alive - continue - except socket.error, e: - # connection fails -> terminate loop - raise SerialException('connection failed (%s)' % e) - return bytes(data) - - def write(self, data): - """Output the given string over the serial port. Can block if the - connection is blocked. May raise SerialException if the connection is - closed.""" - if not self._isOpen: raise portNotOpenError - try: - self._socket.sendall(data) - except socket.error, e: - raise SerialException("socket connection failed: %s" % e) # XXX what exception if socket connection fails - return len(data) - - def flushInput(self): - """Clear input buffer, discarding all that is in the buffer.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('ignored flushInput') - - def flushOutput(self): - """Clear output buffer, aborting the current output and - discarding all that is in the buffer.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('ignored flushOutput') - - def sendBreak(self, duration=0.25): - """Send break condition. Timed, returns to idle state after given - duration.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('ignored sendBreak(%r)' % (duration,)) - - def setBreak(self, level=True): - """Set break: Controls TXD. When active, to transmitting is - possible.""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('ignored setBreak(%r)' % (level,)) - - def setRTS(self, level=True): - """Set terminal status line: Request To Send""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('ignored setRTS(%r)' % (level,)) - - def setDTR(self, level=True): - """Set terminal status line: Data Terminal Ready""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('ignored setDTR(%r)' % (level,)) - - def getCTS(self): - """Read terminal status line: Clear To Send""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('returning dummy for getCTS()') - return True - - def getDSR(self): - """Read terminal status line: Data Set Ready""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('returning dummy for getDSR()') - return True - - def getRI(self): - """Read terminal status line: Ring Indicator""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('returning dummy for getRI()') - return False - - def getCD(self): - """Read terminal status line: Carrier Detect""" - if not self._isOpen: raise portNotOpenError - if self.logger: - self.logger.info('returning dummy for getCD()') - return True - - # - - - platform specific - - - - # None so far - - -# assemble Serial class with the platform specific implementation and the base -# for file-like behavior. for Python 2.6 and newer, that provide the new I/O -# library, derive from io.RawIOBase -try: - import io -except ImportError: - # classic version with our own file-like emulation - class Serial(SocketSerial, FileLike): - pass -else: - # io library present - class Serial(SocketSerial, io.RawIOBase): - pass - - -# simple client test -if __name__ == '__main__': - import sys - s = Serial('socket://localhost:7000') - sys.stdout.write('%s\n' % s) - - sys.stdout.write("write...\n") - s.write("hello\n") - s.flush() - sys.stdout.write("read: %s\n" % s.read(5)) - - s.close() diff --git a/pyserial/serial/urlhandler/__init__.py b/pyserial/serial/urlhandler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyserial/serial/urlhandler/protocol_loop.py b/pyserial/serial/urlhandler/protocol_loop.py new file mode 100644 index 0000000..eb510da --- /dev/null +++ b/pyserial/serial/urlhandler/protocol_loop.py @@ -0,0 +1,260 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a loop back connection receiving itself what it sent. +# +# The purpose of this module is.. well... You can run the unit tests with it. +# and it was so easy to implement ;-) +# +# (C) 2001-2011 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# URL format: loop://[option[/option...]] +# options: +# - "debug" print diagnostic messages + +from serial.serialutil import * +import threading +import time +import logging + +# map log level names to constants. used in fromURL() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + } + + +class LoopbackSerial(SerialBase): + """Serial port implementation for plain sockets.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """Open port with current settings. This may throw a SerialException + if the port cannot be opened.""" + self.logger = None + self.buffer_lock = threading.Lock() + self.loop_buffer = bytearray() + self.cts = False + self.dsr = False + + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + # not that there is anything to open, but the function applies the + # options found in the URL + self.fromURL(self.port) + + # not that there anything to configure... + self._reconfigurePort() + # all things set up get, now a clean start + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """Set communication parameters on opened port. for the loop:// + protocol all settings are ignored!""" + # not that's it of any real use, but it helps in the unit tests + if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32: + raise ValueError("invalid baudrate: %r" % (self._baudrate)) + if self.logger: + self.logger.info('_reconfigurePort()') + + def close(self): + """Close port""" + if self._isOpen: + self._isOpen = False + # in case of quick reconnects, give the server some time + time.sleep(0.3) + + def makeDeviceName(self, port): + raise SerialException("there is no sensible way to turn numbers into URLs") + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("loop://"): url = url[7:] + try: + # process options now, directly altering self + for option in url.split('/'): + if '=' in option: + option, value = option.split('=', 1) + else: + value = None + if not option: + pass + elif option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.loop') + self.logger.setLevel(LOGGER_LEVELS[value]) + self.logger.debug('enabled logging') + else: + raise ValueError('unknown option: %r' % (option,)) + except ValueError, e: + raise SerialException('expected a string in the form "[loop://][option[/option...]]": %s' % e) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + # attention the logged value can differ from return value in + # threaded environments... + self.logger.debug('inWaiting() -> %d' % (len(self.loop_buffer),)) + return len(self.loop_buffer) + + def read(self, size=1): + """Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read.""" + if not self._isOpen: raise portNotOpenError + if self._timeout is not None: + timeout = time.time() + self._timeout + else: + timeout = None + data = bytearray() + while len(data) < size: + self.buffer_lock.acquire() + try: + block = to_bytes(self.loop_buffer[:size]) + del self.loop_buffer[:size] + finally: + self.buffer_lock.release() + data += block + # check for timeout now, after data has been read. + # useful for timeout = 0 (non blocking) read + if timeout and time.time() > timeout: + break + return bytes(data) + + def write(self, data): + """Output the given string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed.""" + if not self._isOpen: raise portNotOpenError + # calculate aprox time that would be used to send the data + time_used_to_send = 10.0*len(data) / self._baudrate + # when a write timeout is configured check if we would be successful + # (not sending anything, not even the part that would have time) + if self._writeTimeout is not None and time_used_to_send > self._writeTimeout: + time.sleep(self._writeTimeout) # must wait so that unit test succeeds + raise writeTimeoutError + self.buffer_lock.acquire() + try: + self.loop_buffer += bytes(data) + finally: + self.buffer_lock.release() + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('flushInput()') + self.buffer_lock.acquire() + try: + del self.loop_buffer[:] + finally: + self.buffer_lock.release() + + def flushOutput(self): + """Clear output buffer, aborting the current output and + discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('flushOutput()') + + def sendBreak(self, duration=0.25): + """Send break condition. Timed, returns to idle state after given + duration.""" + if not self._isOpen: raise portNotOpenError + + def setBreak(self, level=True): + """Set break: Controls TXD. When active, to transmitting is + possible.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('setBreak(%r)' % (level,)) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('setRTS(%r) -> state of CTS' % (level,)) + self.cts = level + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('setDTR(%r) -> state of DSR' % (level,)) + self.dsr = level + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('getCTS() -> state of RTS (%r)' % (self.cts,)) + return self.cts + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('getDSR() -> state of DTR (%r)' % (self.dsr,)) + return self.dsr + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getRI()') + return False + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getCD()') + return True + + # - - - platform specific - - - + # None so far + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(LoopbackSerial, FileLike): + pass +else: + # io library present + class Serial(LoopbackSerial, io.RawIOBase): + pass + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('socket://localhost:7000') + sys.stdout.write('%s\n' % s) + + sys.stdout.write("write...\n") + s.write("hello\n") + s.flush() + sys.stdout.write("read: %s\n" % s.read(5)) + + s.close() diff --git a/pyserial/serial/urlhandler/protocol_rfc2217.py b/pyserial/serial/urlhandler/protocol_rfc2217.py new file mode 100644 index 0000000..981ba45 --- /dev/null +++ b/pyserial/serial/urlhandler/protocol_rfc2217.py @@ -0,0 +1,11 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see ../__init__.py +# +# This is a thin wrapper to load the rfc2271 implementation. +# +# (C) 2011 Chris Liechti +# this is distributed under a free software license, see license.txt + +from serial.rfc2217 import Serial diff --git a/pyserial/serial/urlhandler/protocol_socket.py b/pyserial/serial/urlhandler/protocol_socket.py new file mode 100644 index 0000000..facd553 --- /dev/null +++ b/pyserial/serial/urlhandler/protocol_socket.py @@ -0,0 +1,259 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a simple socket based client. +# It does not support changing any port parameters and will silently ignore any +# requests to do so. +# +# The purpose of this module is that applications using pySerial can connect to +# TCP/IP to serial port converters that do not support RFC 2217. +# +# (C) 2001-2011 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# URL format: socket://:[/option[/option...]] +# options: +# - "debug" print diagnostic messages + +from serial.serialutil import * +import time +import socket +import logging + +# map log level names to constants. used in fromURL() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + } + + +class SocketSerial(SerialBase): + """Serial port implementation for plain sockets.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """Open port with current settings. This may throw a SerialException + if the port cannot be opened.""" + self.logger = None + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + try: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect(self.fromURL(self.portstr)) + except Exception, msg: + self._socket = None + raise SerialException("Could not open port %s: %s" % (self.portstr, msg)) + + self._socket.settimeout(2) # used for write timeout support :/ + + # not that there anything to configure... + self._reconfigurePort() + # all things set up get, now a clean start + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """Set communication parameters on opened port. for the socket:// + protocol all settings are ignored!""" + if self._socket is None: + raise SerialException("Can only operate on open ports") + if self.logger: + self.logger.info('ignored port configuration change') + + def close(self): + """Close port""" + if self._isOpen: + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + # ignore errors. + pass + self._socket = None + self._isOpen = False + # in case of quick reconnects, give the server some time + time.sleep(0.3) + + def makeDeviceName(self, port): + raise SerialException("there is no sensible way to turn numbers into URLs") + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("socket://"): url = url[9:] + try: + # is there a "path" (our options)? + if '/' in url: + # cut away options + url, options = url.split('/', 1) + # process options now, directly altering self + for option in options.split('/'): + if '=' in option: + option, value = option.split('=', 1) + else: + value = None + if option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.socket') + self.logger.setLevel(LOGGER_LEVELS[value]) + self.logger.debug('enabled logging') + else: + raise ValueError('unknown option: %r' % (option,)) + # get host and port + host, port = url.split(':', 1) # may raise ValueError because of unpacking + port = int(port) # and this if it's not a number + if not 0 <= port < 65536: raise ValueError("port not in range 0...65535") + except ValueError, e: + raise SerialException('expected a string in the form "[rfc2217://]:[/option[/option...]]": %s' % e) + return (host, port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + # set this one to debug as the function could be called often... + self.logger.debug('WARNING: inWaiting returns dummy value') + return 0 # hmmm, see comment in read() + + def read(self, size=1): + """Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read.""" + if not self._isOpen: raise portNotOpenError + data = bytearray() + timeout = time.time() + self._timeout + while len(data) < size and time.time() < timeout: + try: + # an implementation with internal buffer would be better + # performing... + data = self._socket.recv(size - len(data)) + except socket.timeout: + # just need to get out of recv form time to time to check if + # still alive + continue + except socket.error, e: + # connection fails -> terminate loop + raise SerialException('connection failed (%s)' % e) + return bytes(data) + + def write(self, data): + """Output the given string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed.""" + if not self._isOpen: raise portNotOpenError + try: + self._socket.sendall(data) + except socket.error, e: + raise SerialException("socket connection failed: %s" % e) # XXX what exception if socket connection fails + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored flushInput') + + def flushOutput(self): + """Clear output buffer, aborting the current output and + discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored flushOutput') + + def sendBreak(self, duration=0.25): + """Send break condition. Timed, returns to idle state after given + duration.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored sendBreak(%r)' % (duration,)) + + def setBreak(self, level=True): + """Set break: Controls TXD. When active, to transmitting is + possible.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored setBreak(%r)' % (level,)) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored setRTS(%r)' % (level,)) + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('ignored setDTR(%r)' % (level,)) + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getCTS()') + return True + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getDSR()') + return True + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getRI()') + return False + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('returning dummy for getCD()') + return True + + # - - - platform specific - - - + # None so far + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(SocketSerial, FileLike): + pass +else: + # io library present + class Serial(SocketSerial, io.RawIOBase): + pass + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('socket://localhost:7000') + sys.stdout.write('%s\n' % s) + + sys.stdout.write("write...\n") + s.write("hello\n") + s.flush() + sys.stdout.write("read: %s\n" % s.read(5)) + + s.close() -- cgit v1.2.1