blob: 3dedb48d52f3ba66cad3139635c65585c3b5e623 [file] [log] [blame]
cliechtiab90e072009-08-06 01:44:34 +00001#! 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#
Chris Liechtia4222112015-08-07 01:03:12 +020013# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +020014#
15# SPDX-License-Identifier: BSD-3-Clause
cliechtiab90e072009-08-06 01:44:34 +000016#
17# URL format: socket://<host>:<port>[/option[/option...]]
18# options:
19# - "debug" print diagnostic messages
20
cliechtic64ba692009-08-12 00:32:47 +000021import logging
Chris Liechtifbdd8a02015-08-09 02:37:45 +020022import select
23import socket
24import time
Chris Liechtic4bca9e2015-08-07 14:40:41 +020025try:
26 import urlparse
27except ImportError:
28 import urllib.parse as urlparse
cliechtic64ba692009-08-12 00:32:47 +000029
Chris Liechtifbdd8a02015-08-09 02:37:45 +020030from serial.serialutil import *
31
cliechtic64ba692009-08-12 00:32:47 +000032# map log level names to constants. used in fromURL()
33LOGGER_LEVELS = {
Chris Liechtic4bca9e2015-08-07 14:40:41 +020034 'debug': logging.DEBUG,
35 'info': logging.INFO,
36 'warning': logging.WARNING,
37 'error': logging.ERROR,
38 }
cliechtic64ba692009-08-12 00:32:47 +000039
cliechti5d66a952013-10-11 02:27:30 +000040POLL_TIMEOUT = 2
cliechtiab90e072009-08-06 01:44:34 +000041
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.")
cliechti8f69e702011-03-19 00:22:32 +000056 if self._isOpen:
57 raise SerialException("Port is already open.")
cliechtiab90e072009-08-06 01:44:34 +000058 try:
Chris Liechtia4222112015-08-07 01:03:12 +020059 self._socket = socket.create_connection(self.fromURL(self.portstr))
Chris Liechti68340d72015-08-03 14:15:48 +020060 except Exception as msg:
cliechtiab90e072009-08-06 01:44:34 +000061 self._socket = None
62 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
63
cliechti5d66a952013-10-11 02:27:30 +000064 self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
cliechtiab90e072009-08-06 01:44:34 +000065
66 # not that there anything to configure...
67 self._reconfigurePort()
68 # all things set up get, now a clean start
69 self._isOpen = True
70 if not self._rtscts:
71 self.setRTS(True)
72 self.setDTR(True)
73 self.flushInput()
74 self.flushOutput()
75
76 def _reconfigurePort(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"""
88 if self._isOpen:
89 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
cliechtiab90e072009-08-06 01:44:34 +000097 self._isOpen = False
98 # in case of quick reconnects, give the server some time
99 time.sleep(0.3)
100
101 def makeDeviceName(self, port):
102 raise SerialException("there is no sensible way to turn numbers into URLs")
103
104 def fromURL(self, url):
105 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200106 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200107 if parts.scheme != "socket":
108 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 +0000109 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200110 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200111 for option, values in urlparse.parse_qs(parts.query, True).items():
112 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200113 logging.basicConfig() # XXX is that good to call it here?
114 self.logger = logging.getLogger('pySerial.socket')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200115 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200116 self.logger.debug('enabled logging')
117 else:
118 raise ValueError('unknown option: %r' % (option,))
cliechtiab90e072009-08-06 01:44:34 +0000119 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200120 host, port = parts.hostname, parts.port
cliechtiab90e072009-08-06 01:44:34 +0000121 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200122 except ValueError as e:
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200123 raise SerialException('expected a string in the form "socket://<host>:<port>[?logging={debug|info|warning|error}]": %s' % e)
cliechtiab90e072009-08-06 01:44:34 +0000124 return (host, port)
125
126 # - - - - - - - - - - - - - - - - - - - - - - - -
127
128 def inWaiting(self):
129 """Return the number of characters currently in the input buffer."""
130 if not self._isOpen: 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 """
cliechtiab90e072009-08-06 01:44:34 +0000142 if not self._isOpen: raise portNotOpenError
143 data = bytearray()
cliechti20e1fae2013-05-31 01:33:12 +0000144 if self._timeout is not None:
145 timeout = time.time() + self._timeout
146 else:
147 timeout = None
Chris Liechti069d32a2015-08-05 03:21:38 +0200148 while len(data) < size:
cliechtiab90e072009-08-06 01:44:34 +0000149 try:
150 # an implementation with internal buffer would be better
151 # performing...
cliechtifee4e962013-05-31 00:55:43 +0000152 block = self._socket.recv(size - len(data))
153 if block:
cliechti5d66a952013-10-11 02:27:30 +0000154 data.extend(block)
155 else:
156 # no data -> EOF (connection probably closed)
157 break
cliechtiab90e072009-08-06 01:44:34 +0000158 except socket.timeout:
cliechti5d66a952013-10-11 02:27:30 +0000159 # just need to get out of recv from time to time to check if
Chris Liechti2880f0e2015-08-17 03:19:46 +0200160 # still alive and timeout did not expire
161 pass
Chris Liechti68340d72015-08-03 14:15:48 +0200162 except socket.error as e:
cliechtiab90e072009-08-06 01:44:34 +0000163 # connection fails -> terminate loop
164 raise SerialException('connection failed (%s)' % e)
Chris Liechti069d32a2015-08-05 03:21:38 +0200165 if timeout is not None and time.time() > timeout:
166 break
cliechtiab90e072009-08-06 01:44:34 +0000167 return bytes(data)
168
169 def write(self, data):
cliechti7d448562014-08-03 21:57:45 +0000170 """\
171 Output the given string over the serial port. Can block if the
cliechtiab90e072009-08-06 01:44:34 +0000172 connection is blocked. May raise SerialException if the connection is
cliechti7d448562014-08-03 21:57:45 +0000173 closed.
174 """
cliechtiab90e072009-08-06 01:44:34 +0000175 if not self._isOpen: raise portNotOpenError
176 try:
cliechti38077122013-10-16 02:57:27 +0000177 self._socket.sendall(to_bytes(data))
Chris Liechti68340d72015-08-03 14:15:48 +0200178 except socket.error as e:
cliechti5d66a952013-10-11 02:27:30 +0000179 # XXX what exception if socket connection fails
180 raise SerialException("socket connection failed: %s" % e)
cliechtiab90e072009-08-06 01:44:34 +0000181 return len(data)
182
183 def flushInput(self):
184 """Clear input buffer, discarding all that is in the buffer."""
185 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000186 if self.logger:
187 self.logger.info('ignored flushInput')
cliechtiab90e072009-08-06 01:44:34 +0000188
189 def flushOutput(self):
cliechti7d448562014-08-03 21:57:45 +0000190 """\
191 Clear output buffer, aborting the current output and
192 discarding all that is in the buffer.
193 """
cliechtiab90e072009-08-06 01:44:34 +0000194 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000195 if self.logger:
196 self.logger.info('ignored flushOutput')
cliechtiab90e072009-08-06 01:44:34 +0000197
198 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000199 """\
200 Send break condition. Timed, returns to idle state after given
201 duration.
202 """
cliechtiab90e072009-08-06 01:44:34 +0000203 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000204 if self.logger:
205 self.logger.info('ignored sendBreak(%r)' % (duration,))
cliechtiab90e072009-08-06 01:44:34 +0000206
207 def setBreak(self, level=True):
208 """Set break: Controls TXD. When active, to transmitting is
209 possible."""
210 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000211 if self.logger:
212 self.logger.info('ignored setBreak(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000213
214 def setRTS(self, level=True):
215 """Set terminal status line: Request To Send"""
216 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000217 if self.logger:
218 self.logger.info('ignored setRTS(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000219
220 def setDTR(self, level=True):
221 """Set terminal status line: Data Terminal Ready"""
222 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000223 if self.logger:
224 self.logger.info('ignored setDTR(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000225
226 def getCTS(self):
227 """Read terminal status line: Clear To Send"""
228 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000229 if self.logger:
230 self.logger.info('returning dummy for getCTS()')
cliechtiab90e072009-08-06 01:44:34 +0000231 return True
232
233 def getDSR(self):
234 """Read terminal status line: Data Set Ready"""
235 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000236 if self.logger:
237 self.logger.info('returning dummy for getDSR()')
cliechtiab90e072009-08-06 01:44:34 +0000238 return True
239
240 def getRI(self):
241 """Read terminal status line: Ring Indicator"""
242 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000243 if self.logger:
244 self.logger.info('returning dummy for getRI()')
cliechtiab90e072009-08-06 01:44:34 +0000245 return False
246
247 def getCD(self):
248 """Read terminal status line: Carrier Detect"""
249 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000250 if self.logger:
251 self.logger.info('returning dummy for getCD()')
cliechtiab90e072009-08-06 01:44:34 +0000252 return True
253
254 # - - - platform specific - - -
cliechtib869edb2014-07-31 22:13:19 +0000255
256 # works on Linux and probably all the other POSIX systems
257 def fileno(self):
258 """Get the file handle of the underlying socket for use with select"""
259 return self._socket.fileno()
cliechtiab90e072009-08-06 01:44:34 +0000260
261
Chris Liechtief6b7b42015-08-06 22:19:26 +0200262#
cliechtiab90e072009-08-06 01:44:34 +0000263# simple client test
264if __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")
Chris Liechtifbdd8a02015-08-09 02:37:45 +0200270 s.write(b"hello\n")
cliechtiab90e072009-08-06 01:44:34 +0000271 s.flush()
272 sys.stdout.write("read: %s\n" % s.read(5))
273
274 s.close()