Home | History | Annotate | Download | only in urlhandler
      1 #! python
      2 #
      3 # Python Serial Port Extension for Win32, Linux, BSD, Jython
      4 # see __init__.py
      5 #
      6 # This module implements a simple socket based client.
      7 # It does not support changing any port parameters and will silently ignore any
      8 # requests to do so.
      9 #
     10 # The purpose of this module is that applications using pySerial can connect to
     11 # TCP/IP to serial port converters that do not support RFC 2217.
     12 #
     13 # (C) 2001-2011 Chris Liechti <cliechti (at] gmx.net>
     14 # this is distributed under a free software license, see license.txt
     15 #
     16 # URL format:    socket://<host>:<port>[/option[/option...]]
     17 # options:
     18 # - "debug" print diagnostic messages
     19 
     20 from serial.serialutil import *
     21 import time
     22 import socket
     23 import logging
     24 
     25 # map log level names to constants. used in fromURL()
     26 LOGGER_LEVELS = {
     27     'debug': logging.DEBUG,
     28     'info': logging.INFO,
     29     'warning': logging.WARNING,
     30     'error': logging.ERROR,
     31     }
     32 
     33 POLL_TIMEOUT = 2
     34 
     35 class SocketSerial(SerialBase):
     36     """Serial port implementation for plain sockets."""
     37 
     38     BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
     39                  9600, 19200, 38400, 57600, 115200)
     40 
     41     def open(self):
     42         """Open port with current settings. This may throw a SerialException
     43            if the port cannot be opened."""
     44         self.logger = None
     45         if self._port is None:
     46             raise SerialException("Port must be configured before it can be used.")
     47         if self._isOpen:
     48             raise SerialException("Port is already open.")
     49         try:
     50             # XXX in future replace with create_connection (py >=2.6)
     51             self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     52             self._socket.connect(self.fromURL(self.portstr))
     53         except Exception, msg:
     54             self._socket = None
     55             raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
     56 
     57         self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
     58 
     59         # not that there anything to configure...
     60         self._reconfigurePort()
     61         # all things set up get, now a clean start
     62         self._isOpen = True
     63         if not self._rtscts:
     64             self.setRTS(True)
     65             self.setDTR(True)
     66         self.flushInput()
     67         self.flushOutput()
     68 
     69     def _reconfigurePort(self):
     70         """Set communication parameters on opened port. for the socket://
     71         protocol all settings are ignored!"""
     72         if self._socket is None:
     73             raise SerialException("Can only operate on open ports")
     74         if self.logger:
     75             self.logger.info('ignored port configuration change')
     76 
     77     def close(self):
     78         """Close port"""
     79         if self._isOpen:
     80             if self._socket:
     81                 try:
     82                     self._socket.shutdown(socket.SHUT_RDWR)
     83                     self._socket.close()
     84                 except:
     85                     # ignore errors.
     86                     pass
     87                 self._socket = None
     88             self._isOpen = False
     89             # in case of quick reconnects, give the server some time
     90             time.sleep(0.3)
     91 
     92     def makeDeviceName(self, port):
     93         raise SerialException("there is no sensible way to turn numbers into URLs")
     94 
     95     def fromURL(self, url):
     96         """extract host and port from an URL string"""
     97         if url.lower().startswith("socket://"): url = url[9:]
     98         try:
     99             # is there a "path" (our options)?
    100             if '/' in url:
    101                 # cut away options
    102                 url, options = url.split('/', 1)
    103                 # process options now, directly altering self
    104                 for option in options.split('/'):
    105                     if '=' in option:
    106                         option, value = option.split('=', 1)
    107                     else:
    108                         value = None
    109                     if option == 'logging':
    110                         logging.basicConfig()   # XXX is that good to call it here?
    111                         self.logger = logging.getLogger('pySerial.socket')
    112                         self.logger.setLevel(LOGGER_LEVELS[value])
    113                         self.logger.debug('enabled logging')
    114                     else:
    115                         raise ValueError('unknown option: %r' % (option,))
    116             # get host and port
    117             host, port = url.split(':', 1) # may raise ValueError because of unpacking
    118             port = int(port)               # and this if it's not a number
    119             if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
    120         except ValueError, e:
    121             raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
    122         return (host, port)
    123 
    124     #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
    125 
    126     def inWaiting(self):
    127         """Return the number of characters currently in the input buffer."""
    128         if not self._isOpen: raise portNotOpenError
    129         if self.logger:
    130             # set this one to debug as the function could be called often...
    131             self.logger.debug('WARNING: inWaiting returns dummy value')
    132         return 0 # hmmm, see comment in read()
    133 
    134     def read(self, size=1):
    135         """Read size bytes from the serial port. If a timeout is set it may
    136         return less characters as requested. With no timeout it will block
    137         until the requested number of bytes is read."""
    138         if not self._isOpen: raise portNotOpenError
    139         data = bytearray()
    140         if self._timeout is not None:
    141             timeout = time.time() + self._timeout
    142         else:
    143             timeout = None
    144         while len(data) < size and (timeout is None or time.time() < timeout):
    145             try:
    146                 # an implementation with internal buffer would be better
    147                 # performing...
    148                 t = time.time()
    149                 block = self._socket.recv(size - len(data))
    150                 duration = time.time() - t
    151                 if block:
    152                     data.extend(block)
    153                 else:
    154                     # no data -> EOF (connection probably closed)
    155                     break
    156             except socket.timeout:
    157                 # just need to get out of recv from time to time to check if
    158                 # still alive
    159                 continue
    160             except socket.error, e:
    161                 # connection fails -> terminate loop
    162                 raise SerialException('connection failed (%s)' % e)
    163         return bytes(data)
    164 
    165     def write(self, data):
    166         """Output the given string over the serial port. Can block if the
    167         connection is blocked. May raise SerialException if the connection is
    168         closed."""
    169         if not self._isOpen: raise portNotOpenError
    170         try:
    171             self._socket.sendall(to_bytes(data))
    172         except socket.error, e:
    173             # XXX what exception if socket connection fails
    174             raise SerialException("socket connection failed: %s" % e)
    175         return len(data)
    176 
    177     def flushInput(self):
    178         """Clear input buffer, discarding all that is in the buffer."""
    179         if not self._isOpen: raise portNotOpenError
    180         if self.logger:
    181             self.logger.info('ignored flushInput')
    182 
    183     def flushOutput(self):
    184         """Clear output buffer, aborting the current output and
    185         discarding all that is in the buffer."""
    186         if not self._isOpen: raise portNotOpenError
    187         if self.logger:
    188             self.logger.info('ignored flushOutput')
    189 
    190     def sendBreak(self, duration=0.25):
    191         """Send break condition. Timed, returns to idle state after given
    192         duration."""
    193         if not self._isOpen: raise portNotOpenError
    194         if self.logger:
    195             self.logger.info('ignored sendBreak(%r)' % (duration,))
    196 
    197     def setBreak(self, level=True):
    198         """Set break: Controls TXD. When active, to transmitting is
    199         possible."""
    200         if not self._isOpen: raise portNotOpenError
    201         if self.logger:
    202             self.logger.info('ignored setBreak(%r)' % (level,))
    203 
    204     def setRTS(self, level=True):
    205         """Set terminal status line: Request To Send"""
    206         if not self._isOpen: raise portNotOpenError
    207         if self.logger:
    208             self.logger.info('ignored setRTS(%r)' % (level,))
    209 
    210     def setDTR(self, level=True):
    211         """Set terminal status line: Data Terminal Ready"""
    212         if not self._isOpen: raise portNotOpenError
    213         if self.logger:
    214             self.logger.info('ignored setDTR(%r)' % (level,))
    215 
    216     def getCTS(self):
    217         """Read terminal status line: Clear To Send"""
    218         if not self._isOpen: raise portNotOpenError
    219         if self.logger:
    220             self.logger.info('returning dummy for getCTS()')
    221         return True
    222 
    223     def getDSR(self):
    224         """Read terminal status line: Data Set Ready"""
    225         if not self._isOpen: raise portNotOpenError
    226         if self.logger:
    227             self.logger.info('returning dummy for getDSR()')
    228         return True
    229 
    230     def getRI(self):
    231         """Read terminal status line: Ring Indicator"""
    232         if not self._isOpen: raise portNotOpenError
    233         if self.logger:
    234             self.logger.info('returning dummy for getRI()')
    235         return False
    236 
    237     def getCD(self):
    238         """Read terminal status line: Carrier Detect"""
    239         if not self._isOpen: raise portNotOpenError
    240         if self.logger:
    241             self.logger.info('returning dummy for getCD()')
    242         return True
    243 
    244     # - - - platform specific - - -
    245     # None so far
    246 
    247 
    248 # assemble Serial class with the platform specific implementation and the base
    249 # for file-like behavior. for Python 2.6 and newer, that provide the new I/O
    250 # library, derive from io.RawIOBase
    251 try:
    252     import io
    253 except ImportError:
    254     # classic version with our own file-like emulation
    255     class Serial(SocketSerial, FileLike):
    256         pass
    257 else:
    258     # io library present
    259     class Serial(SocketSerial, io.RawIOBase):
    260         pass
    261 
    262 
    263 # simple client test
    264 if __name__ == '__main__':
    265     import sys
    266     s = Serial('socket://localhost:7000')
    267     sys.stdout.write('%s\n' % s)
    268 
    269     sys.stdout.write("write...\n")
    270     s.write("hello\n")
    271     s.flush()
    272     sys.stdout.write("read: %s\n" % s.read(5))
    273 
    274     s.close()
    275