blob: 22f690455196f5aa158720d2082eea174f0accc6 [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
cliechtic64ba692009-08-12 00:32:47 +000019import logging
Chris Liechtifbdd8a02015-08-09 02:37:45 +020020import select
21import socket
22import time
Chris Liechtic4bca9e2015-08-07 14:40:41 +020023try:
24 import urlparse
25except ImportError:
26 import urllib.parse as urlparse
cliechtic64ba692009-08-12 00:32:47 +000027
Chris Liechti033f17c2015-08-30 21:28:04 +020028from serial.serialutil import SerialBase, SerialException, portNotOpenError, to_bytes
Chris Liechtifbdd8a02015-08-09 02:37:45 +020029
Chris Liechti3ad62fb2015-08-29 21:53:32 +020030# map log level names to constants. used in from_url()
cliechtic64ba692009-08-12 00:32:47 +000031LOGGER_LEVELS = {
Chris Liechtic4bca9e2015-08-07 14:40:41 +020032 'debug': logging.DEBUG,
33 'info': logging.INFO,
34 'warning': logging.WARNING,
35 'error': logging.ERROR,
36 }
cliechtic64ba692009-08-12 00:32:47 +000037
cliechti5d66a952013-10-11 02:27:30 +000038POLL_TIMEOUT = 2
cliechtiab90e072009-08-06 01:44:34 +000039
Chris Liechti033f17c2015-08-30 21:28:04 +020040
Chris Liechtief6b7b42015-08-06 22:19:26 +020041class Serial(SerialBase):
cliechtiab90e072009-08-06 01:44:34 +000042 """Serial port implementation for plain sockets."""
43
44 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
45 9600, 19200, 38400, 57600, 115200)
46
47 def open(self):
cliechti7d448562014-08-03 21:57:45 +000048 """\
49 Open port with current settings. This may throw a SerialException
50 if the port cannot be opened.
51 """
cliechti6a300772009-08-12 02:28:56 +000052 self.logger = None
cliechtiab90e072009-08-06 01:44:34 +000053 if self._port is None:
54 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +020055 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +000056 raise SerialException("Port is already open.")
cliechtiab90e072009-08-06 01:44:34 +000057 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +020058 self._socket = socket.create_connection(self.from_url(self.portstr))
Chris Liechti68340d72015-08-03 14:15:48 +020059 except Exception as msg:
cliechtiab90e072009-08-06 01:44:34 +000060 self._socket = None
61 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
62
Chris Liechti033f17c2015-08-30 21:28:04 +020063 self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
cliechtiab90e072009-08-06 01:44:34 +000064
65 # not that there anything to configure...
Chris Liechti3ad62fb2015-08-29 21:53:32 +020066 self._reconfigure_port()
cliechtiab90e072009-08-06 01:44:34 +000067 # all things set up get, now a clean start
Chris Liechti3ad62fb2015-08-29 21:53:32 +020068 self.is_open = True
69 if not self._dsrdtr:
Chris Liechtief1fe252015-08-27 23:25:21 +020070 self._update_dtr_state()
cliechtiab90e072009-08-06 01:44:34 +000071 if not self._rtscts:
Chris Liechtief1fe252015-08-27 23:25:21 +020072 self._update_rts_state()
73 self.reset_input_buffer()
74 self.reset_output_buffer()
cliechtiab90e072009-08-06 01:44:34 +000075
Chris Liechti3ad62fb2015-08-29 21:53:32 +020076 def _reconfigure_port(self):
cliechti7d448562014-08-03 21:57:45 +000077 """\
78 Set communication parameters on opened port. For the socket://
79 protocol all settings are ignored!
80 """
cliechtiab90e072009-08-06 01:44:34 +000081 if self._socket is None:
82 raise SerialException("Can only operate on open ports")
cliechti6a300772009-08-12 02:28:56 +000083 if self.logger:
84 self.logger.info('ignored port configuration change')
cliechtiab90e072009-08-06 01:44:34 +000085
86 def close(self):
87 """Close port"""
Chris Liechti3ad62fb2015-08-29 21:53:32 +020088 if self.is_open:
cliechtiab90e072009-08-06 01:44:34 +000089 if self._socket:
90 try:
91 self._socket.shutdown(socket.SHUT_RDWR)
92 self._socket.close()
93 except:
94 # ignore errors.
95 pass
96 self._socket = None
Chris Liechti3ad62fb2015-08-29 21:53:32 +020097 self.is_open = False
cliechtiab90e072009-08-06 01:44:34 +000098 # in case of quick reconnects, give the server some time
99 time.sleep(0.3)
100
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200101 def from_url(self, url):
cliechtiab90e072009-08-06 01:44:34 +0000102 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200103 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200104 if parts.scheme != "socket":
105 raise SerialException('expected a string in the form "socket://<host>:<port>[?logging={debug|info|warning|error}]": not starting with socket:// (%r)' % (parts.scheme,))
cliechtiab90e072009-08-06 01:44:34 +0000106 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200107 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200108 for option, values in urlparse.parse_qs(parts.query, True).items():
109 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200110 logging.basicConfig() # XXX is that good to call it here?
111 self.logger = logging.getLogger('pySerial.socket')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200112 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200113 self.logger.debug('enabled logging')
114 else:
115 raise ValueError('unknown option: %r' % (option,))
cliechtiab90e072009-08-06 01:44:34 +0000116 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200117 host, port = parts.hostname, parts.port
Chris Liechti033f17c2015-08-30 21:28:04 +0200118 if not 0 <= port < 65536:
119 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200120 except ValueError as e:
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200121 raise SerialException('expected a string in the form "socket://<host>:<port>[?logging={debug|info|warning|error}]": %s' % e)
cliechtiab90e072009-08-06 01:44:34 +0000122 return (host, port)
123
124 # - - - - - - - - - - - - - - - - - - - - - - - -
125
Chris Liechtief1fe252015-08-27 23:25:21 +0200126 @property
127 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200128 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200129 if not self.is_open:
130 raise portNotOpenError
cliechti77e088a2014-08-04 10:29:24 +0000131 # Poll the socket to see if it is ready for reading.
132 # If ready, at least one byte will be to read.
133 lr, lw, lx = select.select([self._socket], [], [], 0)
134 return len(lr)
cliechtiab90e072009-08-06 01:44:34 +0000135
136 def read(self, size=1):
cliechti7d448562014-08-03 21:57:45 +0000137 """\
138 Read size bytes from the serial port. If a timeout is set it may
cliechtiab90e072009-08-06 01:44:34 +0000139 return less characters as requested. With no timeout it will block
cliechti7d448562014-08-03 21:57:45 +0000140 until the requested number of bytes is read.
141 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200142 if not self.is_open:
143 raise portNotOpenError
cliechtiab90e072009-08-06 01:44:34 +0000144 data = bytearray()
cliechti20e1fae2013-05-31 01:33:12 +0000145 if self._timeout is not None:
146 timeout = time.time() + self._timeout
147 else:
148 timeout = None
Chris Liechti069d32a2015-08-05 03:21:38 +0200149 while len(data) < size:
cliechtiab90e072009-08-06 01:44:34 +0000150 try:
151 # an implementation with internal buffer would be better
152 # performing...
cliechtifee4e962013-05-31 00:55:43 +0000153 block = self._socket.recv(size - len(data))
154 if block:
cliechti5d66a952013-10-11 02:27:30 +0000155 data.extend(block)
156 else:
157 # no data -> EOF (connection probably closed)
158 break
cliechtiab90e072009-08-06 01:44:34 +0000159 except socket.timeout:
cliechti5d66a952013-10-11 02:27:30 +0000160 # just need to get out of recv from time to time to check if
Chris Liechti2880f0e2015-08-17 03:19:46 +0200161 # still alive and timeout did not expire
162 pass
Chris Liechti68340d72015-08-03 14:15:48 +0200163 except socket.error as e:
cliechtiab90e072009-08-06 01:44:34 +0000164 # connection fails -> terminate loop
165 raise SerialException('connection failed (%s)' % e)
Chris Liechti069d32a2015-08-05 03:21:38 +0200166 if timeout is not None and time.time() > timeout:
167 break
cliechtiab90e072009-08-06 01:44:34 +0000168 return bytes(data)
169
170 def write(self, data):
cliechti7d448562014-08-03 21:57:45 +0000171 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200172 Output the given byte string over the serial port. Can block if the
cliechtiab90e072009-08-06 01:44:34 +0000173 connection is blocked. May raise SerialException if the connection is
cliechti7d448562014-08-03 21:57:45 +0000174 closed.
175 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200176 if not self.is_open:
177 raise portNotOpenError
cliechtiab90e072009-08-06 01:44:34 +0000178 try:
cliechti38077122013-10-16 02:57:27 +0000179 self._socket.sendall(to_bytes(data))
Chris Liechti68340d72015-08-03 14:15:48 +0200180 except socket.error as e:
cliechti5d66a952013-10-11 02:27:30 +0000181 # XXX what exception if socket connection fails
182 raise SerialException("socket connection failed: %s" % e)
cliechtiab90e072009-08-06 01:44:34 +0000183 return len(data)
184
Chris Liechtief1fe252015-08-27 23:25:21 +0200185 def reset_input_buffer(self):
cliechtiab90e072009-08-06 01:44:34 +0000186 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200187 if not self.is_open:
188 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000189 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200190 self.logger.info('ignored reset_input_buffer')
cliechtiab90e072009-08-06 01:44:34 +0000191
Chris Liechtief1fe252015-08-27 23:25:21 +0200192 def reset_output_buffer(self):
cliechti7d448562014-08-03 21:57:45 +0000193 """\
194 Clear output buffer, aborting the current output and
195 discarding all that is in the buffer.
196 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200197 if not self.is_open:
198 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000199 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200200 self.logger.info('ignored reset_output_buffer')
cliechtiab90e072009-08-06 01:44:34 +0000201
Chris Liechtief1fe252015-08-27 23:25:21 +0200202 def send_break(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000203 """\
204 Send break condition. Timed, returns to idle state after given
205 duration.
206 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200207 if not self.is_open:
208 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000209 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200210 self.logger.info('ignored send_break(%r)' % (duration,))
cliechtiab90e072009-08-06 01:44:34 +0000211
Chris Liechtief1fe252015-08-27 23:25:21 +0200212 def _update_break_state(self):
cliechtiab90e072009-08-06 01:44:34 +0000213 """Set break: Controls TXD. When active, to transmitting is
214 possible."""
cliechti6a300772009-08-12 02:28:56 +0000215 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200216 self.logger.info('ignored _update_break_state(%r)' % (self._break_state,))
cliechtiab90e072009-08-06 01:44:34 +0000217
Chris Liechtief1fe252015-08-27 23:25:21 +0200218 def _update_rts_state(self):
cliechtiab90e072009-08-06 01:44:34 +0000219 """Set terminal status line: Request To Send"""
cliechti6a300772009-08-12 02:28:56 +0000220 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200221 self.logger.info('ignored _update_rts_state(%r)' % (self._rts_state,))
cliechtiab90e072009-08-06 01:44:34 +0000222
Chris Liechtief1fe252015-08-27 23:25:21 +0200223 def _update_dtr_state(self):
cliechtiab90e072009-08-06 01:44:34 +0000224 """Set terminal status line: Data Terminal Ready"""
cliechti6a300772009-08-12 02:28:56 +0000225 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200226 self.logger.info('ignored _update_dtr_state(%r)' % (self._dtr_state,))
cliechtiab90e072009-08-06 01:44:34 +0000227
Chris Liechtief1fe252015-08-27 23:25:21 +0200228 @property
229 def cts(self):
cliechtiab90e072009-08-06 01:44:34 +0000230 """Read terminal status line: Clear To Send"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200231 if not self.is_open:
232 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000233 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200234 self.logger.info('returning dummy for cts')
cliechtiab90e072009-08-06 01:44:34 +0000235 return True
236
Chris Liechtief1fe252015-08-27 23:25:21 +0200237 @property
238 def dsr(self):
cliechtiab90e072009-08-06 01:44:34 +0000239 """Read terminal status line: Data Set Ready"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200240 if not self.is_open:
241 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000242 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200243 self.logger.info('returning dummy for dsr')
cliechtiab90e072009-08-06 01:44:34 +0000244 return True
245
Chris Liechtief1fe252015-08-27 23:25:21 +0200246 @property
247 def ri(self):
cliechtiab90e072009-08-06 01:44:34 +0000248 """Read terminal status line: Ring Indicator"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200249 if not self.is_open:
250 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000251 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200252 self.logger.info('returning dummy for ri')
cliechtiab90e072009-08-06 01:44:34 +0000253 return False
254
Chris Liechtief1fe252015-08-27 23:25:21 +0200255 @property
256 def cd(self):
cliechtiab90e072009-08-06 01:44:34 +0000257 """Read terminal status line: Carrier Detect"""
Chris Liechti033f17c2015-08-30 21:28:04 +0200258 if not self.is_open:
259 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000260 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200261 self.logger.info('returning dummy for cd)')
cliechtiab90e072009-08-06 01:44:34 +0000262 return True
263
264 # - - - platform specific - - -
cliechtib869edb2014-07-31 22:13:19 +0000265
266 # works on Linux and probably all the other POSIX systems
267 def fileno(self):
268 """Get the file handle of the underlying socket for use with select"""
269 return self._socket.fileno()
cliechtiab90e072009-08-06 01:44:34 +0000270
271
Chris Liechtief6b7b42015-08-06 22:19:26 +0200272#
cliechtiab90e072009-08-06 01:44:34 +0000273# simple client test
274if __name__ == '__main__':
275 import sys
276 s = Serial('socket://localhost:7000')
277 sys.stdout.write('%s\n' % s)
278
279 sys.stdout.write("write...\n")
Chris Liechtifbdd8a02015-08-09 02:37:45 +0200280 s.write(b"hello\n")
cliechtiab90e072009-08-06 01:44:34 +0000281 s.flush()
282 sys.stdout.write("read: %s\n" % s.read(5))
283
284 s.close()