blob: dc5992342c69cac6b6a3cba2b4b11d0b7de766c3 [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#
cliechti2a6d5332011-03-04 02:08:32 +000013# (C) 2001-2011 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
25
26# map log level names to constants. used in fromURL()
27LOGGER_LEVELS = {
28 'debug': logging.DEBUG,
29 'info': logging.INFO,
30 'warning': logging.WARNING,
31 'error': logging.ERROR,
32 }
33
cliechti5d66a952013-10-11 02:27:30 +000034POLL_TIMEOUT = 2
cliechtiab90e072009-08-06 01:44:34 +000035
36class SocketSerial(SerialBase):
37 """Serial port implementation for plain sockets."""
38
39 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
40 9600, 19200, 38400, 57600, 115200)
41
42 def open(self):
cliechti7d448562014-08-03 21:57:45 +000043 """\
44 Open port with current settings. This may throw a SerialException
45 if the port cannot be opened.
46 """
cliechti6a300772009-08-12 02:28:56 +000047 self.logger = None
cliechtiab90e072009-08-06 01:44:34 +000048 if self._port is None:
49 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +000050 if self._isOpen:
51 raise SerialException("Port is already open.")
cliechtiab90e072009-08-06 01:44:34 +000052 try:
cliechti5d66a952013-10-11 02:27:30 +000053 # XXX in future replace with create_connection (py >=2.6)
cliechtiab90e072009-08-06 01:44:34 +000054 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
55 self._socket.connect(self.fromURL(self.portstr))
56 except Exception, msg:
57 self._socket = None
58 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
59
cliechti5d66a952013-10-11 02:27:30 +000060 self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
cliechtiab90e072009-08-06 01:44:34 +000061
62 # not that there anything to configure...
63 self._reconfigurePort()
64 # all things set up get, now a clean start
65 self._isOpen = True
66 if not self._rtscts:
67 self.setRTS(True)
68 self.setDTR(True)
69 self.flushInput()
70 self.flushOutput()
71
72 def _reconfigurePort(self):
cliechti7d448562014-08-03 21:57:45 +000073 """\
74 Set communication parameters on opened port. For the socket://
75 protocol all settings are ignored!
76 """
cliechtiab90e072009-08-06 01:44:34 +000077 if self._socket is None:
78 raise SerialException("Can only operate on open ports")
cliechti6a300772009-08-12 02:28:56 +000079 if self.logger:
80 self.logger.info('ignored port configuration change')
cliechtiab90e072009-08-06 01:44:34 +000081
82 def close(self):
83 """Close port"""
84 if self._isOpen:
85 if self._socket:
86 try:
87 self._socket.shutdown(socket.SHUT_RDWR)
88 self._socket.close()
89 except:
90 # ignore errors.
91 pass
92 self._socket = None
cliechtiab90e072009-08-06 01:44:34 +000093 self._isOpen = False
94 # in case of quick reconnects, give the server some time
95 time.sleep(0.3)
96
97 def makeDeviceName(self, port):
98 raise SerialException("there is no sensible way to turn numbers into URLs")
99
100 def fromURL(self, url):
101 """extract host and port from an URL string"""
102 if url.lower().startswith("socket://"): url = url[9:]
103 try:
104 # is there a "path" (our options)?
105 if '/' in url:
106 # cut away options
107 url, options = url.split('/', 1)
108 # process options now, directly altering self
109 for option in options.split('/'):
cliechtic64ba692009-08-12 00:32:47 +0000110 if '=' in option:
111 option, value = option.split('=', 1)
112 else:
113 value = None
114 if option == 'logging':
115 logging.basicConfig() # XXX is that good to call it here?
cliechti6a300772009-08-12 02:28:56 +0000116 self.logger = logging.getLogger('pySerial.socket')
117 self.logger.setLevel(LOGGER_LEVELS[value])
118 self.logger.debug('enabled logging')
cliechtiab90e072009-08-06 01:44:34 +0000119 else:
120 raise ValueError('unknown option: %r' % (option,))
121 # get host and port
122 host, port = url.split(':', 1) # may raise ValueError because of unpacking
123 port = int(port) # and this if it's not a number
124 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
125 except ValueError, e:
126 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
127 return (host, port)
128
129 # - - - - - - - - - - - - - - - - - - - - - - - -
130
131 def inWaiting(self):
132 """Return the number of characters currently in the input buffer."""
133 if not self._isOpen: raise portNotOpenError
cliechti77e088a2014-08-04 10:29:24 +0000134 # Poll the socket to see if it is ready for reading.
135 # If ready, at least one byte will be to read.
136 lr, lw, lx = select.select([self._socket], [], [], 0)
137 return len(lr)
cliechtiab90e072009-08-06 01:44:34 +0000138
139 def read(self, size=1):
cliechti7d448562014-08-03 21:57:45 +0000140 """\
141 Read size bytes from the serial port. If a timeout is set it may
cliechtiab90e072009-08-06 01:44:34 +0000142 return less characters as requested. With no timeout it will block
cliechti7d448562014-08-03 21:57:45 +0000143 until the requested number of bytes is read.
144 """
cliechtiab90e072009-08-06 01:44:34 +0000145 if not self._isOpen: raise portNotOpenError
146 data = bytearray()
cliechti20e1fae2013-05-31 01:33:12 +0000147 if self._timeout is not None:
148 timeout = time.time() + self._timeout
149 else:
150 timeout = None
151 while len(data) < size and (timeout is None or time.time() < timeout):
cliechtiab90e072009-08-06 01:44:34 +0000152 try:
153 # an implementation with internal buffer would be better
154 # performing...
cliechti5d66a952013-10-11 02:27:30 +0000155 t = time.time()
cliechtifee4e962013-05-31 00:55:43 +0000156 block = self._socket.recv(size - len(data))
cliechti5d66a952013-10-11 02:27:30 +0000157 duration = time.time() - t
cliechtifee4e962013-05-31 00:55:43 +0000158 if block:
cliechti5d66a952013-10-11 02:27:30 +0000159 data.extend(block)
160 else:
161 # no data -> EOF (connection probably closed)
162 break
cliechtiab90e072009-08-06 01:44:34 +0000163 except socket.timeout:
cliechti5d66a952013-10-11 02:27:30 +0000164 # just need to get out of recv from time to time to check if
cliechtiab90e072009-08-06 01:44:34 +0000165 # still alive
166 continue
167 except socket.error, e:
168 # connection fails -> terminate loop
169 raise SerialException('connection failed (%s)' % e)
170 return bytes(data)
171
172 def write(self, data):
cliechti7d448562014-08-03 21:57:45 +0000173 """\
174 Output the given string over the serial port. Can block if the
cliechtiab90e072009-08-06 01:44:34 +0000175 connection is blocked. May raise SerialException if the connection is
cliechti7d448562014-08-03 21:57:45 +0000176 closed.
177 """
cliechtiab90e072009-08-06 01:44:34 +0000178 if not self._isOpen: raise portNotOpenError
179 try:
cliechti38077122013-10-16 02:57:27 +0000180 self._socket.sendall(to_bytes(data))
cliechtiab90e072009-08-06 01:44:34 +0000181 except socket.error, e:
cliechti5d66a952013-10-11 02:27:30 +0000182 # XXX what exception if socket connection fails
183 raise SerialException("socket connection failed: %s" % e)
cliechtiab90e072009-08-06 01:44:34 +0000184 return len(data)
185
186 def flushInput(self):
187 """Clear input buffer, discarding all that is in the buffer."""
188 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000189 if self.logger:
190 self.logger.info('ignored flushInput')
cliechtiab90e072009-08-06 01:44:34 +0000191
192 def flushOutput(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 """
cliechtiab90e072009-08-06 01:44:34 +0000197 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000198 if self.logger:
199 self.logger.info('ignored flushOutput')
cliechtiab90e072009-08-06 01:44:34 +0000200
201 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000202 """\
203 Send break condition. Timed, returns to idle state after given
204 duration.
205 """
cliechtiab90e072009-08-06 01:44:34 +0000206 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000207 if self.logger:
208 self.logger.info('ignored sendBreak(%r)' % (duration,))
cliechtiab90e072009-08-06 01:44:34 +0000209
210 def setBreak(self, level=True):
211 """Set break: Controls TXD. When active, to transmitting is
212 possible."""
213 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000214 if self.logger:
215 self.logger.info('ignored setBreak(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000216
217 def setRTS(self, level=True):
218 """Set terminal status line: Request To Send"""
219 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000220 if self.logger:
221 self.logger.info('ignored setRTS(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000222
223 def setDTR(self, level=True):
224 """Set terminal status line: Data Terminal Ready"""
225 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000226 if self.logger:
227 self.logger.info('ignored setDTR(%r)' % (level,))
cliechtiab90e072009-08-06 01:44:34 +0000228
229 def getCTS(self):
230 """Read terminal status line: Clear To Send"""
231 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000232 if self.logger:
233 self.logger.info('returning dummy for getCTS()')
cliechtiab90e072009-08-06 01:44:34 +0000234 return True
235
236 def getDSR(self):
237 """Read terminal status line: Data Set Ready"""
238 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000239 if self.logger:
240 self.logger.info('returning dummy for getDSR()')
cliechtiab90e072009-08-06 01:44:34 +0000241 return True
242
243 def getRI(self):
244 """Read terminal status line: Ring Indicator"""
245 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000246 if self.logger:
247 self.logger.info('returning dummy for getRI()')
cliechtiab90e072009-08-06 01:44:34 +0000248 return False
249
250 def getCD(self):
251 """Read terminal status line: Carrier Detect"""
252 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000253 if self.logger:
254 self.logger.info('returning dummy for getCD()')
cliechtiab90e072009-08-06 01:44:34 +0000255 return True
256
257 # - - - platform specific - - -
cliechtib869edb2014-07-31 22:13:19 +0000258
259 # works on Linux and probably all the other POSIX systems
260 def fileno(self):
261 """Get the file handle of the underlying socket for use with select"""
262 return self._socket.fileno()
cliechtiab90e072009-08-06 01:44:34 +0000263
264
265# assemble Serial class with the platform specific implementation and the base
266# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
267# library, derive from io.RawIOBase
268try:
269 import io
270except ImportError:
271 # classic version with our own file-like emulation
272 class Serial(SocketSerial, FileLike):
273 pass
274else:
275 # io library present
276 class Serial(SocketSerial, io.RawIOBase):
277 pass
278
279
280# simple client test
281if __name__ == '__main__':
282 import sys
283 s = Serial('socket://localhost:7000')
284 sys.stdout.write('%s\n' % s)
285
286 sys.stdout.write("write...\n")
287 s.write("hello\n")
288 s.flush()
289 sys.stdout.write("read: %s\n" % s.read(5))
290
291 s.close()