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