1#! python 2# 3# This module implements a simple socket based client. 4# It does not support changing any port parameters and will silently ignore any 5# requests to do so. 6# 7# The purpose of this module is that applications using pySerial can connect to 8# TCP/IP to serial port converters that do not support RFC 2217. 9# 10# This file is part of pySerial. https://github.com/pyserial/pyserial 11# (C) 2001-2015 Chris Liechti <[email protected]> 12# 13# SPDX-License-Identifier: BSD-3-Clause 14# 15# URL format: socket://<host>:<port>[/option[/option...]] 16# options: 17# - "debug" print diagnostic messages 18 19from __future__ import absolute_import 20 21import errno 22import logging 23import select 24import socket 25import time 26try: 27 import urlparse 28except ImportError: 29 import urllib.parse as urlparse 30 31from serial.serialutil import SerialBase, SerialException, to_bytes, \ 32 PortNotOpenError, SerialTimeoutException, Timeout 33 34# map log level names to constants. used in from_url() 35LOGGER_LEVELS = { 36 'debug': logging.DEBUG, 37 'info': logging.INFO, 38 'warning': logging.WARNING, 39 'error': logging.ERROR, 40} 41 42POLL_TIMEOUT = 5 43 44 45class Serial(SerialBase): 46 """Serial port implementation for plain sockets.""" 47 48 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 49 9600, 19200, 38400, 57600, 115200) 50 51 def open(self): 52 """\ 53 Open port with current settings. This may throw a SerialException 54 if the port cannot be opened. 55 """ 56 self.logger = None 57 if self._port is None: 58 raise SerialException("Port must be configured before it can be used.") 59 if self.is_open: 60 raise SerialException("Port is already open.") 61 try: 62 # timeout is used for write timeout support :/ and to get an initial connection timeout 63 self._socket = socket.create_connection(self.from_url(self.portstr), timeout=POLL_TIMEOUT) 64 except Exception as msg: 65 self._socket = None 66 raise SerialException("Could not open port {}: {}".format(self.portstr, msg)) 67 # after connecting, switch to non-blocking, we're using select 68 self._socket.setblocking(False) 69 70 # not that there is anything to configure... 71 self._reconfigure_port() 72 # all things set up get, now a clean start 73 self.is_open = True 74 if not self._dsrdtr: 75 self._update_dtr_state() 76 if not self._rtscts: 77 self._update_rts_state() 78 self.reset_input_buffer() 79 self.reset_output_buffer() 80 81 def _reconfigure_port(self): 82 """\ 83 Set communication parameters on opened port. For the socket:// 84 protocol all settings are ignored! 85 """ 86 if self._socket is None: 87 raise SerialException("Can only operate on open ports") 88 if self.logger: 89 self.logger.info('ignored port configuration change') 90 91 def close(self): 92 """Close port""" 93 if self.is_open: 94 if self._socket: 95 try: 96 self._socket.shutdown(socket.SHUT_RDWR) 97 self._socket.close() 98 except: 99 # ignore errors. 100 pass 101 self._socket = None 102 self.is_open = False 103 # in case of quick reconnects, give the server some time 104 time.sleep(0.3) 105 106 def from_url(self, url): 107 """extract host and port from an URL string""" 108 parts = urlparse.urlsplit(url) 109 if parts.scheme != "socket": 110 raise SerialException( 111 'expected a string in the form ' 112 '"socket://<host>:<port>[?logging={debug|info|warning|error}]": ' 113 'not starting with socket:// ({!r})'.format(parts.scheme)) 114 try: 115 # process options now, directly altering self 116 for option, values in urlparse.parse_qs(parts.query, True).items(): 117 if option == 'logging': 118 logging.basicConfig() # XXX is that good to call it here? 119 self.logger = logging.getLogger('pySerial.socket') 120 self.logger.setLevel(LOGGER_LEVELS[values[0]]) 121 self.logger.debug('enabled logging') 122 else: 123 raise ValueError('unknown option: {!r}'.format(option)) 124 if not 0 <= parts.port < 65536: 125 raise ValueError("port not in range 0...65535") 126 except ValueError as e: 127 raise SerialException( 128 'expected a string in the form ' 129 '"socket://<host>:<port>[?logging={debug|info|warning|error}]": {}'.format(e)) 130 131 return (parts.hostname, parts.port) 132 133 # - - - - - - - - - - - - - - - - - - - - - - - - 134 135 @property 136 def in_waiting(self): 137 """Return the number of bytes currently in the input buffer.""" 138 if not self.is_open: 139 raise PortNotOpenError() 140 # Poll the socket to see if it is ready for reading. 141 # If ready, at least one byte will be to read. 142 lr, lw, lx = select.select([self._socket], [], [], 0) 143 return len(lr) 144 145 # select based implementation, similar to posix, but only using socket API 146 # to be portable, additionally handle socket timeout which is used to 147 # emulate write timeouts 148 def read(self, size=1): 149 """\ 150 Read size bytes from the serial port. If a timeout is set it may 151 return less characters as requested. With no timeout it will block 152 until the requested number of bytes is read. 153 """ 154 if not self.is_open: 155 raise PortNotOpenError() 156 read = bytearray() 157 timeout = Timeout(self._timeout) 158 while len(read) < size: 159 try: 160 ready, _, _ = select.select([self._socket], [], [], timeout.time_left()) 161 # If select was used with a timeout, and the timeout occurs, it 162 # returns with empty lists -> thus abort read operation. 163 # For timeout == 0 (non-blocking operation) also abort when 164 # there is nothing to read. 165 if not ready: 166 break # timeout 167 buf = self._socket.recv(size - len(read)) 168 # read should always return some data as select reported it was 169 # ready to read when we get to this point, unless it is EOF 170 if not buf: 171 raise SerialException('socket disconnected') 172 read.extend(buf) 173 except OSError as e: 174 # this is for Python 3.x where select.error is a subclass of 175 # OSError ignore BlockingIOErrors and EINTR. other errors are shown 176 # https://www.python.org/dev/peps/pep-0475. 177 if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): 178 raise SerialException('read failed: {}'.format(e)) 179 except (select.error, socket.error) as e: 180 # this is for Python 2.x 181 # ignore BlockingIOErrors and EINTR. all errors are shown 182 # see also http://www.python.org/dev/peps/pep-3151/#select 183 if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): 184 raise SerialException('read failed: {}'.format(e)) 185 if timeout.expired(): 186 break 187 return bytes(read) 188 189 def write(self, data): 190 """\ 191 Output the given byte string over the serial port. Can block if the 192 connection is blocked. May raise SerialException if the connection is 193 closed. 194 """ 195 if not self.is_open: 196 raise PortNotOpenError() 197 198 d = to_bytes(data) 199 tx_len = length = len(d) 200 timeout = Timeout(self._write_timeout) 201 while tx_len > 0: 202 try: 203 n = self._socket.send(d) 204 if timeout.is_non_blocking: 205 # Zero timeout indicates non-blocking - simply return the 206 # number of bytes of data actually written 207 return n 208 elif not timeout.is_infinite: 209 # when timeout is set, use select to wait for being ready 210 # with the time left as timeout 211 if timeout.expired(): 212 raise SerialTimeoutException('Write timeout') 213 _, ready, _ = select.select([], [self._socket], [], timeout.time_left()) 214 if not ready: 215 raise SerialTimeoutException('Write timeout') 216 else: 217 assert timeout.time_left() is None 218 # wait for write operation 219 _, ready, _ = select.select([], [self._socket], [], None) 220 if not ready: 221 raise SerialException('write failed (select)') 222 d = d[n:] 223 tx_len -= n 224 except SerialException: 225 raise 226 except OSError as e: 227 # this is for Python 3.x where select.error is a subclass of 228 # OSError ignore BlockingIOErrors and EINTR. other errors are shown 229 # https://www.python.org/dev/peps/pep-0475. 230 if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): 231 raise SerialException('write failed: {}'.format(e)) 232 except select.error as e: 233 # this is for Python 2.x 234 # ignore BlockingIOErrors and EINTR. all errors are shown 235 # see also http://www.python.org/dev/peps/pep-3151/#select 236 if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): 237 raise SerialException('write failed: {}'.format(e)) 238 if not timeout.is_non_blocking and timeout.expired(): 239 raise SerialTimeoutException('Write timeout') 240 return length - len(d) 241 242 def reset_input_buffer(self): 243 """Clear input buffer, discarding all that is in the buffer.""" 244 if not self.is_open: 245 raise PortNotOpenError() 246 247 # just use recv to remove input, while there is some 248 ready = True 249 while ready: 250 ready, _, _ = select.select([self._socket], [], [], 0) 251 try: 252 if ready: 253 ready = self._socket.recv(4096) 254 except OSError as e: 255 # this is for Python 3.x where select.error is a subclass of 256 # OSError ignore BlockingIOErrors and EINTR. other errors are shown 257 # https://www.python.org/dev/peps/pep-0475. 258 if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): 259 raise SerialException('read failed: {}'.format(e)) 260 except (select.error, socket.error) as e: 261 # this is for Python 2.x 262 # ignore BlockingIOErrors and EINTR. all errors are shown 263 # see also http://www.python.org/dev/peps/pep-3151/#select 264 if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR): 265 raise SerialException('read failed: {}'.format(e)) 266 267 def reset_output_buffer(self): 268 """\ 269 Clear output buffer, aborting the current output and 270 discarding all that is in the buffer. 271 """ 272 if not self.is_open: 273 raise PortNotOpenError() 274 if self.logger: 275 self.logger.info('ignored reset_output_buffer') 276 277 def send_break(self, duration=0.25): 278 """\ 279 Send break condition. Timed, returns to idle state after given 280 duration. 281 """ 282 if not self.is_open: 283 raise PortNotOpenError() 284 if self.logger: 285 self.logger.info('ignored send_break({!r})'.format(duration)) 286 287 def _update_break_state(self): 288 """Set break: Controls TXD. When active, to transmitting is 289 possible.""" 290 if self.logger: 291 self.logger.info('ignored _update_break_state({!r})'.format(self._break_state)) 292 293 def _update_rts_state(self): 294 """Set terminal status line: Request To Send""" 295 if self.logger: 296 self.logger.info('ignored _update_rts_state({!r})'.format(self._rts_state)) 297 298 def _update_dtr_state(self): 299 """Set terminal status line: Data Terminal Ready""" 300 if self.logger: 301 self.logger.info('ignored _update_dtr_state({!r})'.format(self._dtr_state)) 302 303 @property 304 def cts(self): 305 """Read terminal status line: Clear To Send""" 306 if not self.is_open: 307 raise PortNotOpenError() 308 if self.logger: 309 self.logger.info('returning dummy for cts') 310 return True 311 312 @property 313 def dsr(self): 314 """Read terminal status line: Data Set Ready""" 315 if not self.is_open: 316 raise PortNotOpenError() 317 if self.logger: 318 self.logger.info('returning dummy for dsr') 319 return True 320 321 @property 322 def ri(self): 323 """Read terminal status line: Ring Indicator""" 324 if not self.is_open: 325 raise PortNotOpenError() 326 if self.logger: 327 self.logger.info('returning dummy for ri') 328 return False 329 330 @property 331 def cd(self): 332 """Read terminal status line: Carrier Detect""" 333 if not self.is_open: 334 raise PortNotOpenError() 335 if self.logger: 336 self.logger.info('returning dummy for cd)') 337 return True 338 339 # - - - platform specific - - - 340 341 # works on Linux and probably all the other POSIX systems 342 def fileno(self): 343 """Get the file handle of the underlying socket for use with select""" 344 return self._socket.fileno() 345 346 347# 348# simple client test 349if __name__ == '__main__': 350 import sys 351 s = Serial('socket://localhost:7000') 352 sys.stdout.write('{}\n'.format(s)) 353 354 sys.stdout.write("write...\n") 355 s.write(b"hello\n") 356 s.flush() 357 sys.stdout.write("read: {}\n".format(s.read(5))) 358 359 s.close() 360