blob: e25b14c21122a03ddb00f3ac91e082e85e5a112b [file] [log] [blame]
cliechtiab90e072009-08-06 01:44:34 +00001#! python
2#
cliechtiab90e072009-08-06 01:44:34 +00003# 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#
Chris Liechti3e02f702015-12-16 23:06:04 +010010# This file is part of pySerial. https://github.com/pyserial/pyserial
Chris Liechtia4222112015-08-07 01:03:12 +020011# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +020012#
13# SPDX-License-Identifier: BSD-3-Clause
cliechtiab90e072009-08-06 01:44:34 +000014#
15# URL format: socket://<host>:<port>[/option[/option...]]
16# options:
17# - "debug" print diagnostic messages
18
Chris Liechtib10daf42016-01-26 00:27:10 +010019import errno
cliechtic64ba692009-08-12 00:32:47 +000020import logging
Chris Liechtifbdd8a02015-08-09 02:37:45 +020021import select
22import socket
23import time
Chris Liechtic4bca9e2015-08-07 14:40:41 +020024try:
25 import urlparse
26except ImportError:
27 import urllib.parse as urlparse
cliechtic64ba692009-08-12 00:32:47 +000028
Chris Liechti033f17c2015-08-30 21:28:04 +020029from serial.serialutil import SerialBase, SerialException, portNotOpenError, to_bytes
Chris Liechtifbdd8a02015-08-09 02:37:45 +020030
Chris Liechti3ad62fb2015-08-29 21:53:32 +020031# map log level names to constants. used in from_url()
cliechtic64ba692009-08-12 00:32:47 +000032LOGGER_LEVELS = {
Chris Liechti6594df62016-02-04 21:13:41 +010033 'debug': logging.DEBUG,
34 'info': logging.INFO,
35 'warning': logging.WARNING,
36 'error': logging.ERROR,
37}
cliechtic64ba692009-08-12 00:32:47 +000038
cliechti5d66a952013-10-11 02:27:30 +000039POLL_TIMEOUT = 2
cliechtiab90e072009-08-06 01:44:34 +000040
Chris Liechti033f17c2015-08-30 21:28:04 +020041
Chris Liechtief6b7b42015-08-06 22:19:26 +020042class Serial(SerialBase):
cliechtiab90e072009-08-06 01:44:34 +000043 """Serial port implementation for plain sockets."""
44
45 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
46 9600, 19200, 38400, 57600, 115200)
47
48 def open(self):
cliechti7d448562014-08-03 21:57:45 +000049 """\
50 Open port with current settings. This may throw a SerialException
51 if the port cannot be opened.
52 """
cliechti6a300772009-08-12 02:28:56 +000053 self.logger = None
cliechtiab90e072009-08-06 01:44:34 +000054 if self._port is None:
55 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +020056 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +000057 raise SerialException("Port is already open.")
cliechtiab90e072009-08-06 01:44:34 +000058 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +020059 self._socket = socket.create_connection(self.from_url(self.portstr))
Chris Liechti68340d72015-08-03 14:15:48 +020060 except Exception as msg:
cliechtiab90e072009-08-06 01:44:34 +000061 self._socket = None
Chris Liechti4daa9d52016-03-22 00:32:01 +010062 raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
cliechtiab90e072009-08-06 01:44:34 +000063
Chris Liechti033f17c2015-08-30 21:28:04 +020064 self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
cliechtiab90e072009-08-06 01:44:34 +000065
66 # not that there anything to configure...
Chris Liechti3ad62fb2015-08-29 21:53:32 +020067 self._reconfigure_port()
cliechtiab90e072009-08-06 01:44:34 +000068 # all things set up get, now a clean start
Chris Liechti3ad62fb2015-08-29 21:53:32 +020069 self.is_open = True
70 if not self._dsrdtr:
Chris Liechtief1fe252015-08-27 23:25:21 +020071 self._update_dtr_state()
cliechtiab90e072009-08-06 01:44:34 +000072 if not self._rtscts:
Chris Liechtief1fe252015-08-27 23:25:21 +020073 self._update_rts_state()
74 self.reset_input_buffer()
75 self.reset_output_buffer()
cliechtiab90e072009-08-06 01:44:34 +000076
Chris Liechti3ad62fb2015-08-29 21:53:32 +020077 def _reconfigure_port(self):
cliechti7d448562014-08-03 21:57:45 +000078 """\
79 Set communication parameters on opened port. For the socket://
80 protocol all settings are ignored!
81 """
cliechtiab90e072009-08-06 01:44:34 +000082 if self._socket is None:
83 raise SerialException("Can only operate on open ports")
cliechti6a300772009-08-12 02:28:56 +000084 if self.logger:
85 self.logger.info('ignored port configuration change')
cliechtiab90e072009-08-06 01:44:34 +000086
87 def close(self):
88 """Close port"""
Chris Liechti3ad62fb2015-08-29 21:53:32 +020089 if self.is_open:
cliechtiab90e072009-08-06 01:44:34 +000090 if self._socket:
91 try:
92 self._socket.shutdown(socket.SHUT_RDWR)
93 self._socket.close()
94 except:
95 # ignore errors.
96 pass
97 self._socket = None
Chris Liechti3ad62fb2015-08-29 21:53:32 +020098 self.is_open = False
cliechtiab90e072009-08-06 01:44:34 +000099 # in case of quick reconnects, give the server some time
100 time.sleep(0.3)
101
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200102 def from_url(self, url):
cliechtiab90e072009-08-06 01:44:34 +0000103 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200104 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200105 if parts.scheme != "socket":
Chris Liechti4daa9d52016-03-22 00:32:01 +0100106 raise SerialException(
107 'expected a string in the form '
108 '"socket://<host>:<port>[?logging={debug|info|warning|error}]": '
109 'not starting with socket:// ({!r})'.format(parts.scheme))
cliechtiab90e072009-08-06 01:44:34 +0000110 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200111 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200112 for option, values in urlparse.parse_qs(parts.query, True).items():
113 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200114 logging.basicConfig() # XXX is that good to call it here?
115 self.logger = logging.getLogger('pySerial.socket')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200116 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200117 self.logger.debug('enabled logging')
118 else:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100119 raise ValueError('unknown option: {!r}'.format(option))
120 if not 0 <= parts.port < 65536:
Chris Liechti033f17c2015-08-30 21:28:04 +0200121 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200122 except ValueError as e:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100123 raise SerialException(
124 'expected a string in the form '
125 '"socket://<host>:<port>[?logging={debug|info|warning|error}]": {}'.format(e))
126
127 return (parts.hostname, parts.port)
cliechtiab90e072009-08-06 01:44:34 +0000128
129 # - - - - - - - - - - - - - - - - - - - - - - - -
130
Chris Liechtief1fe252015-08-27 23:25:21 +0200131 @property
132 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200133 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200134 if not self.is_open:
135 raise portNotOpenError
cliechti77e088a2014-08-04 10:29:24 +0000136 # Poll the socket to see if it is ready for reading.
137 # If ready, at least one byte will be to read.
138 lr, lw, lx = select.select([self._socket], [], [], 0)
139 return len(lr)
cliechtiab90e072009-08-06 01:44:34 +0000140
Chris Liechti6032cf52016-01-15 23:12:20 +0100141 # select based implementation, similar to posix, but only using socket API
142 # to be portable, additionally handle socket timeout which is used to
143 # emulate write timeouts
cliechtiab90e072009-08-06 01:44:34 +0000144 def read(self, size=1):
cliechti7d448562014-08-03 21:57:45 +0000145 """\
146 Read size bytes from the serial port. If a timeout is set it may
cliechtiab90e072009-08-06 01:44:34 +0000147 return less characters as requested. With no timeout it will block
cliechti7d448562014-08-03 21:57:45 +0000148 until the requested number of bytes is read.
149 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200150 if not self.is_open:
151 raise portNotOpenError
Chris Liechti6032cf52016-01-15 23:12:20 +0100152 read = bytearray()
153 timeout = self._timeout
154 while len(read) < size:
cliechtiab90e072009-08-06 01:44:34 +0000155 try:
Chris Liechti6032cf52016-01-15 23:12:20 +0100156 start_time = time.time()
157 ready, _, _ = select.select([self._socket], [], [], timeout)
158 # If select was used with a timeout, and the timeout occurs, it
159 # returns with empty lists -> thus abort read operation.
160 # For timeout == 0 (non-blocking operation) also abort when
161 # there is nothing to read.
162 if not ready:
163 break # timeout
164 buf = self._socket.recv(size - len(read))
165 # read should always return some data as select reported it was
166 # ready to read when we get to this point, unless it is EOF
167 if not buf:
168 raise SerialException('socket disconnected')
169 read.extend(buf)
170 if timeout is not None:
171 timeout -= time.time() - start_time
172 if timeout <= 0:
173 break
cliechtiab90e072009-08-06 01:44:34 +0000174 except socket.timeout:
Chris Liechti6032cf52016-01-15 23:12:20 +0100175 # timeout is used for write support, just go reading again
Chris Liechti2880f0e2015-08-17 03:19:46 +0200176 pass
Chris Liechti68340d72015-08-03 14:15:48 +0200177 except socket.error as e:
cliechtiab90e072009-08-06 01:44:34 +0000178 # connection fails -> terminate loop
Chris Liechti4daa9d52016-03-22 00:32:01 +0100179 raise SerialException('connection failed ({})'.format(e))
Chris Liechti6032cf52016-01-15 23:12:20 +0100180 except OSError as e:
181 # this is for Python 3.x where select.error is a subclass of
182 # OSError ignore EAGAIN errors. all other errors are shown
183 if e.errno != errno.EAGAIN:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100184 raise SerialException('read failed: {}'.format(e))
Chris Liechti6032cf52016-01-15 23:12:20 +0100185 except select.error as e:
186 # this is for Python 2.x
187 # ignore EAGAIN errors. all other errors are shown
188 # see also http://www.python.org/dev/peps/pep-3151/#select
189 if e[0] != errno.EAGAIN:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100190 raise SerialException('read failed: {}'.format(e))
Chris Liechti6032cf52016-01-15 23:12:20 +0100191 return bytes(read)
cliechtiab90e072009-08-06 01:44:34 +0000192
193 def write(self, data):
cliechti7d448562014-08-03 21:57:45 +0000194 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200195 Output the given byte string over the serial port. Can block if the
cliechtiab90e072009-08-06 01:44:34 +0000196 connection is blocked. May raise SerialException if the connection is
cliechti7d448562014-08-03 21:57:45 +0000197 closed.
198 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200199 if not self.is_open:
200 raise portNotOpenError
cliechtiab90e072009-08-06 01:44:34 +0000201 try:
cliechti38077122013-10-16 02:57:27 +0000202 self._socket.sendall(to_bytes(data))
Chris Liechti68340d72015-08-03 14:15:48 +0200203 except socket.error as e:
cliechti5d66a952013-10-11 02:27:30 +0000204 # XXX what exception if socket connection fails
Chris Liechti4daa9d52016-03-22 00:32:01 +0100205 raise SerialException("socket connection failed: {}".format(e))
cliechtiab90e072009-08-06 01:44:34 +0000206 return len(data)
207
Chris Liechtief1fe252015-08-27 23:25:21 +0200208 def reset_input_buffer(self):
cliechtiab90e072009-08-06 01:44:34 +0000209 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200210 if not self.is_open:
211 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000212 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200213 self.logger.info('ignored reset_input_buffer')
cliechtiab90e072009-08-06 01:44:34 +0000214
Chris Liechtief1fe252015-08-27 23:25:21 +0200215 def reset_output_buffer(self):
cliechti7d448562014-08-03 21:57:45 +0000216 """\
217 Clear output buffer, aborting the current output and
218 discarding all that is in the buffer.
219 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200220 if not self.is_open:
221 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000222 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200223 self.logger.info('ignored reset_output_buffer')
cliechtiab90e072009-08-06 01:44:34 +0000224
Chris Liechtief1fe252015-08-27 23:25:21 +0200225 def send_break(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000226 """\
227 Send break condition. Timed, returns to idle state after given
228 duration.
229 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200230 if not self.is_open:
231 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000232 if self.logger:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100233 self.logger.info('ignored send_break({!r})'.format(duration))
cliechtiab90e072009-08-06 01:44:34 +0000234
Chris Liechtief1fe252015-08-27 23:25:21 +0200235 def _update_break_state(self):
cliechtiab90e072009-08-06 01:44:34 +0000236 """Set break: Controls TXD. When active, to transmitting is
237 possible."""
cliechti6a300772009-08-12 02:28:56 +0000238 if self.logger:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100239 self.logger.info('ignored _update_break_state({!r})'.format(self._break_state))
cliechtiab90e072009-08-06 01:44:34 +0000240
Chris Liechtief1fe252015-08-27 23:25:21 +0200241 def _update_rts_state(self):
cliechtiab90e072009-08-06 01:44:34 +0000242 """Set terminal status line: Request To Send"""
cliechti6a300772009-08-12 02:28:56 +0000243 if self.logger:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100244 self.logger.info('ignored _update_rts_state({!r})'.format(self._rts_state))
cliechtiab90e072009-08-06 01:44:34 +0000245
Chris Liechtief1fe252015-08-27 23:25:21 +0200246 def _update_dtr_state(self):
cliechtiab90e072009-08-06 01:44:34 +0000247 """Set terminal status line: Data Terminal Ready"""
cliechti6a300772009-08-12 02:28:56 +0000248 if self.logger:
Chris Liechti4daa9d52016-03-22 00:32:01 +0100249 self.logger.info('ignored _update_dtr_state({!r})'.format(self._dtr_state))
cliechtiab90e072009-08-06 01:44:34 +0000250
Chris Liechtief1fe252015-08-27 23:25:21 +0200251 @property
252 def cts(self):
cliechtiab90e072009-08-06 01:44:34 +0000253 """Read terminal status line: Clear To Send"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200254 if not self.is_open:
255 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000256 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200257 self.logger.info('returning dummy for cts')
cliechtiab90e072009-08-06 01:44:34 +0000258 return True
259
Chris Liechtief1fe252015-08-27 23:25:21 +0200260 @property
261 def dsr(self):
cliechtiab90e072009-08-06 01:44:34 +0000262 """Read terminal status line: Data Set Ready"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200263 if not self.is_open:
264 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000265 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200266 self.logger.info('returning dummy for dsr')
cliechtiab90e072009-08-06 01:44:34 +0000267 return True
268
Chris Liechtief1fe252015-08-27 23:25:21 +0200269 @property
270 def ri(self):
cliechtiab90e072009-08-06 01:44:34 +0000271 """Read terminal status line: Ring Indicator"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200272 if not self.is_open:
273 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000274 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200275 self.logger.info('returning dummy for ri')
cliechtiab90e072009-08-06 01:44:34 +0000276 return False
277
Chris Liechtief1fe252015-08-27 23:25:21 +0200278 @property
279 def cd(self):
cliechtiab90e072009-08-06 01:44:34 +0000280 """Read terminal status line: Carrier Detect"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200281 if not self.is_open:
282 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000283 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200284 self.logger.info('returning dummy for cd)')
cliechtiab90e072009-08-06 01:44:34 +0000285 return True
286
287 # - - - platform specific - - -
cliechtib869edb2014-07-31 22:13:19 +0000288
289 # works on Linux and probably all the other POSIX systems
290 def fileno(self):
291 """Get the file handle of the underlying socket for use with select"""
292 return self._socket.fileno()
cliechtiab90e072009-08-06 01:44:34 +0000293
294
Chris Liechtief6b7b42015-08-06 22:19:26 +0200295#
cliechtiab90e072009-08-06 01:44:34 +0000296# simple client test
297if __name__ == '__main__':
298 import sys
299 s = Serial('socket://localhost:7000')
Chris Liechti4daa9d52016-03-22 00:32:01 +0100300 sys.stdout.write('{}\n'.format(s))
cliechtiab90e072009-08-06 01:44:34 +0000301
302 sys.stdout.write("write...\n")
Chris Liechtifbdd8a02015-08-09 02:37:45 +0200303 s.write(b"hello\n")
cliechtiab90e072009-08-06 01:44:34 +0000304 s.flush()
Chris Liechti4daa9d52016-03-22 00:32:01 +0100305 sys.stdout.write("read: {}\n".format(s.read(5)))
cliechtiab90e072009-08-06 01:44:34 +0000306
307 s.close()