blob: 44702c38152572860afd3ba8f2a796e61375317f [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>
cliechtiab90e072009-08-06 01:44:34 +000014# 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
cliechti2a6d5332011-03-04 02:08:32 +000020from serial.serialutil import *
cliechtiab90e072009-08-06 01:44:34 +000021import time
cliechtiab90e072009-08-06 01:44:34 +000022import socket
cliechti77e088a2014-08-04 10:29:24 +000023import select
cliechtic64ba692009-08-12 00:32:47 +000024import logging
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
30# map log level names to constants. used in fromURL()
31LOGGER_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 Liechtief6b7b42015-08-06 22:19:26 +020040class Serial(SerialBase):
cliechtiab90e072009-08-06 01:44:34 +000041 """Serial port implementation for plain sockets."""
42
43 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
44 9600, 19200, 38400, 57600, 115200)
45
46 def open(self):
cliechti7d448562014-08-03 21:57:45 +000047 """\
48 Open port with current settings. This may throw a SerialException
49 if the port cannot be opened.
50 """
cliechti6a300772009-08-12 02:28:56 +000051 self.logger = None
cliechtiab90e072009-08-06 01:44:34 +000052 if self._port is None:
53 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +000054 if self._isOpen:
55 raise SerialException("Port is already open.")
cliechtiab90e072009-08-06 01:44:34 +000056 try:
Chris Liechtia4222112015-08-07 01:03:12 +020057 self._socket = socket.create_connection(self.fromURL(self.portstr))
Chris Liechti68340d72015-08-03 14:15:48 +020058 except Exception as msg:
cliechtiab90e072009-08-06 01:44:34 +000059 self._socket = None
60 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
61
cliechti5d66a952013-10-11 02:27:30 +000062 self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
cliechtiab90e072009-08-06 01:44:34 +000063
64 # not that there anything to configure...
65 self._reconfigurePort()
66 # all things set up get, now a clean start
67 self._isOpen = True
68 if not self._rtscts:
69 self.setRTS(True)
70 self.setDTR(True)
71 self.flushInput()
72 self.flushOutput()
73
74 def _reconfigurePort(self):
cliechti7d448562014-08-03 21:57:45 +000075 """\
76 Set communication parameters on opened port. For the socket://
77 protocol all settings are ignored!
78 """
cliechtiab90e072009-08-06 01:44:34 +000079 if self._socket is None:
80 raise SerialException("Can only operate on open ports")
cliechti6a300772009-08-12 02:28:56 +000081 if self.logger:
82 self.logger.info('ignored port configuration change')
cliechtiab90e072009-08-06 01:44:34 +000083
84 def close(self):
85 """Close port"""
86 if self._isOpen:
87 if self._socket:
88 try:
89 self._socket.shutdown(socket.SHUT_RDWR)
90 self._socket.close()
91 except:
92 # ignore errors.
93 pass
94 self._socket = None
cliechtiab90e072009-08-06 01:44:34 +000095 self._isOpen = False
96 # in case of quick reconnects, give the server some time
97 time.sleep(0.3)
98
99 def makeDeviceName(self, port):
100 raise SerialException("there is no sensible way to turn numbers into URLs")
101
102 def fromURL(self, url):
103 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200104 parts = urlparse.urlsplit(url)
105 if parts.scheme.lower() != "socket":
106 raise SerialException('expected a string in the form "socket://<host>:<port>[/option[/option...]]": not starting with socket:// (%r)' % (parts.scheme,))
cliechtiab90e072009-08-06 01:44:34 +0000107 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200108 # process options now, directly altering self
109 for option in parts.path.lower().split('/'):
110 if '=' in option:
111 option, value = option.split('=', 1)
112 else:
113 value = None
114 if not option:
115 pass
116 elif option == 'logging':
117 logging.basicConfig() # XXX is that good to call it here?
118 self.logger = logging.getLogger('pySerial.socket')
119 self.logger.setLevel(LOGGER_LEVELS[value])
120 self.logger.debug('enabled logging')
121 else:
122 raise ValueError('unknown option: %r' % (option,))
cliechtiab90e072009-08-06 01:44:34 +0000123 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200124 host, port = parts.hostname, parts.port
cliechtiab90e072009-08-06 01:44:34 +0000125 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200126 except ValueError as e:
Chris Liechtia4222112015-08-07 01:03:12 +0200127 raise SerialException('expected a string in the form "socket://<host>:<port>[/option[/option...]]": %s' % e)
cliechtiab90e072009-08-06 01:44:34 +0000128 return (host, port)
129
130 # - - - - - - - - - - - - - - - - - - - - - - - -
131
132 def inWaiting(self):
133 """Return the number of characters currently in the input buffer."""
134 if not self._isOpen: raise portNotOpenError
cliechti77e088a2014-08-04 10:29:24 +0000135 # Poll the socket to see if it is ready for reading.
136 # If ready, at least one byte will be to read.
137 lr, lw, lx = select.select([self._socket], [], [], 0)
138 return len(lr)
cliechtiab90e072009-08-06 01:44:34 +0000139
140 def read(self, size=1):
cliechti7d448562014-08-03 21:57:45 +0000141 """\
142 Read size bytes from the serial port. If a timeout is set it may
cliechtiab90e072009-08-06 01:44:34 +0000143 return less characters as requested. With no timeout it will block
cliechti7d448562014-08-03 21:57:45 +0000144 until the requested number of bytes is read.
145 """
cliechtiab90e072009-08-06 01:44:34 +0000146 if not self._isOpen: raise portNotOpenError
147 data = bytearray()
cliechti20e1fae2013-05-31 01:33:12 +0000148 if self._timeout is not None:
149 timeout = time.time() + self._timeout
150 else:
151 timeout = None
Chris Liechti069d32a2015-08-05 03:21:38 +0200152 while len(data) < size:
cliechtiab90e072009-08-06 01:44:34 +0000153 try:
154 # an implementation with internal buffer would be better
155 # performing...
cliechtifee4e962013-05-31 00:55:43 +0000156 block = self._socket.recv(size - len(data))
157 if block:
cliechti5d66a952013-10-11 02:27:30 +0000158 data.extend(block)
159 else:
160 # no data -> EOF (connection probably closed)
161 break
cliechtiab90e072009-08-06 01:44:34 +0000162 except socket.timeout:
cliechti5d66a952013-10-11 02:27:30 +0000163 # just need to get out of recv from time to time to check if
cliechtiab90e072009-08-06 01:44:34 +0000164 # still alive
165 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200166 except socket.error as e:
cliechtiab90e072009-08-06 01:44:34 +0000167 # connection fails -> terminate loop
168 raise SerialException('connection failed (%s)' % e)
Chris Liechti069d32a2015-08-05 03:21:38 +0200169 if timeout is not None and time.time() > timeout:
170 break
cliechtiab90e072009-08-06 01:44:34 +0000171 return bytes(data)
172
173 def write(self, data):
cliechti7d448562014-08-03 21:57:45 +0000174 """\
175 Output the given string over the serial port. Can block if the
cliechtiab90e072009-08-06 01:44:34 +0000176 connection is blocked. May raise SerialException if the connection is
cliechti7d448562014-08-03 21:57:45 +0000177 closed.
178 """
cliechtiab90e072009-08-06 01:44:34 +0000179 if not self._isOpen: raise portNotOpenError
180 try:
cliechti38077122013-10-16 02:57:27 +0000181 self._socket.sendall(to_bytes(data))
Chris Liechti68340d72015-08-03 14:15:48 +0200182 except socket.error as e:
cliechti5d66a952013-10-11 02:27:30 +0000183 # XXX what exception if socket connection fails
184 raise SerialException("socket connection failed: %s" % e)
cliechtiab90e072009-08-06 01:44:34 +0000185 return len(data)
186
187 def flushInput(self):
188 """Clear input buffer, discarding all that is in the buffer."""
189 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000190 if self.logger:
191 self.logger.info('ignored flushInput')
cliechtiab90e072009-08-06 01:44:34 +0000192
193 def flushOutput(self):
cliechti7d448562014-08-03 21:57:45 +0000194 """\
195 Clear output buffer, aborting the current output and
196 discarding all that is in the buffer.
197 """
cliechtiab90e072009-08-06 01:44:34 +0000198 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000199 if self.logger:
200 self.logger.info('ignored flushOutput')
cliechtiab90e072009-08-06 01:44:34 +0000201
202 def sendBreak(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 """
cliechtiab90e072009-08-06 01:44:34 +0000207 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000208 if self.logger:
209 self.logger.info('ignored sendBreak(%r)' % (duration,))
cliechtiab90e072009-08-06 01:44:34 +0000210
211 def setBreak(self, level=True):
212 """Set break: Controls TXD. When active, to transmitting is
213 possible."""
214 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000215 if self.logger:
216 self.logger.info('ignored setBreak(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000217
218 def setRTS(self, level=True):
219 """Set terminal status line: Request To Send"""
220 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000221 if self.logger:
222 self.logger.info('ignored setRTS(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000223
224 def setDTR(self, level=True):
225 """Set terminal status line: Data Terminal Ready"""
226 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000227 if self.logger:
228 self.logger.info('ignored setDTR(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000229
230 def getCTS(self):
231 """Read terminal status line: Clear To Send"""
232 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000233 if self.logger:
234 self.logger.info('returning dummy for getCTS()')
cliechtiab90e072009-08-06 01:44:34 +0000235 return True
236
237 def getDSR(self):
238 """Read terminal status line: Data Set Ready"""
239 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000240 if self.logger:
241 self.logger.info('returning dummy for getDSR()')
cliechtiab90e072009-08-06 01:44:34 +0000242 return True
243
244 def getRI(self):
245 """Read terminal status line: Ring Indicator"""
246 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000247 if self.logger:
248 self.logger.info('returning dummy for getRI()')
cliechtiab90e072009-08-06 01:44:34 +0000249 return False
250
251 def getCD(self):
252 """Read terminal status line: Carrier Detect"""
253 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000254 if self.logger:
255 self.logger.info('returning dummy for getCD()')
cliechtiab90e072009-08-06 01:44:34 +0000256 return True
257
258 # - - - platform specific - - -
cliechtib869edb2014-07-31 22:13:19 +0000259
260 # works on Linux and probably all the other POSIX systems
261 def fileno(self):
262 """Get the file handle of the underlying socket for use with select"""
263 return self._socket.fileno()
cliechtiab90e072009-08-06 01:44:34 +0000264
265
Chris Liechtief6b7b42015-08-06 22:19:26 +0200266#
cliechtiab90e072009-08-06 01:44:34 +0000267# simple client test
268if __name__ == '__main__':
269 import sys
270 s = Serial('socket://localhost:7000')
271 sys.stdout.write('%s\n' % s)
272
273 sys.stdout.write("write...\n")
274 s.write("hello\n")
275 s.flush()
276 sys.stdout.write("read: %s\n" % s.read(5))
277
278 s.close()