OLD | NEW |
| (Empty) |
1 #! 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 # | |
13 # (C) 2001-2011 Chris Liechti <cliechti@gmx.net> | |
14 # 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 | |
20 from serial.serialutil import * | |
21 import time | |
22 import socket | |
23 import logging | |
24 | |
25 # map log level names to constants. used in fromURL() | |
26 LOGGER_LEVELS = { | |
27 'debug': logging.DEBUG, | |
28 'info': logging.INFO, | |
29 'warning': logging.WARNING, | |
30 'error': logging.ERROR, | |
31 } | |
32 | |
33 POLL_TIMEOUT = 2 | |
34 | |
35 class SocketSerial(SerialBase): | |
36 """Serial port implementation for plain sockets.""" | |
37 | |
38 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, | |
39 9600, 19200, 38400, 57600, 115200) | |
40 | |
41 def open(self): | |
42 """Open port with current settings. This may throw a SerialException | |
43 if the port cannot be opened.""" | |
44 self.logger = None | |
45 if self._port is None: | |
46 raise SerialException("Port must be configured before it can be used
.") | |
47 if self._isOpen: | |
48 raise SerialException("Port is already open.") | |
49 try: | |
50 # XXX in future replace with create_connection (py >=2.6) | |
51 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
52 self._socket.connect(self.fromURL(self.portstr)) | |
53 except Exception, msg: | |
54 self._socket = None | |
55 raise SerialException("Could not open port %s: %s" % (self.portstr,
msg)) | |
56 | |
57 self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :
/ | |
58 | |
59 # not that there anything to configure... | |
60 self._reconfigurePort() | |
61 # all things set up get, now a clean start | |
62 self._isOpen = True | |
63 if not self._rtscts: | |
64 self.setRTS(True) | |
65 self.setDTR(True) | |
66 self.flushInput() | |
67 self.flushOutput() | |
68 | |
69 def _reconfigurePort(self): | |
70 """Set communication parameters on opened port. for the socket:// | |
71 protocol all settings are ignored!""" | |
72 if self._socket is None: | |
73 raise SerialException("Can only operate on open ports") | |
74 if self.logger: | |
75 self.logger.info('ignored port configuration change') | |
76 | |
77 def close(self): | |
78 """Close port""" | |
79 if self._isOpen: | |
80 if self._socket: | |
81 try: | |
82 self._socket.shutdown(socket.SHUT_RDWR) | |
83 self._socket.close() | |
84 except: | |
85 # ignore errors. | |
86 pass | |
87 self._socket = None | |
88 self._isOpen = False | |
89 # in case of quick reconnects, give the server some time | |
90 time.sleep(0.3) | |
91 | |
92 def makeDeviceName(self, port): | |
93 raise SerialException("there is no sensible way to turn numbers into URL
s") | |
94 | |
95 def fromURL(self, url): | |
96 """extract host and port from an URL string""" | |
97 if url.lower().startswith("socket://"): url = url[9:] | |
98 try: | |
99 # is there a "path" (our options)? | |
100 if '/' in url: | |
101 # cut away options | |
102 url, options = url.split('/', 1) | |
103 # process options now, directly altering self | |
104 for option in options.split('/'): | |
105 if '=' in option: | |
106 option, value = option.split('=', 1) | |
107 else: | |
108 value = None | |
109 if option == 'logging': | |
110 logging.basicConfig() # XXX is that good to call it he
re? | |
111 self.logger = logging.getLogger('pySerial.socket') | |
112 self.logger.setLevel(LOGGER_LEVELS[value]) | |
113 self.logger.debug('enabled logging') | |
114 else: | |
115 raise ValueError('unknown option: %r' % (option,)) | |
116 # get host and port | |
117 host, port = url.split(':', 1) # may raise ValueError because of unp
acking | |
118 port = int(port) # and this if it's not a number | |
119 if not 0 <= port < 65536: raise ValueError("port not in range 0...65
535") | |
120 except ValueError, e: | |
121 raise SerialException('expected a string in the form "[rfc2217://]<h
ost>:<port>[/option[/option...]]": %s' % e) | |
122 return (host, port) | |
123 | |
124 # - - - - - - - - - - - - - - - - - - - - - - - - | |
125 | |
126 def inWaiting(self): | |
127 """Return the number of characters currently in the input buffer.""" | |
128 if not self._isOpen: raise portNotOpenError | |
129 if self.logger: | |
130 # set this one to debug as the function could be called often... | |
131 self.logger.debug('WARNING: inWaiting returns dummy value') | |
132 return 0 # hmmm, see comment in read() | |
133 | |
134 def read(self, size=1): | |
135 """Read size bytes from the serial port. If a timeout is set it may | |
136 return less characters as requested. With no timeout it will block | |
137 until the requested number of bytes is read.""" | |
138 if not self._isOpen: raise portNotOpenError | |
139 data = bytearray() | |
140 if self._timeout is not None: | |
141 timeout = time.time() + self._timeout | |
142 else: | |
143 timeout = None | |
144 while len(data) < size and (timeout is None or time.time() < timeout): | |
145 try: | |
146 # an implementation with internal buffer would be better | |
147 # performing... | |
148 t = time.time() | |
149 block = self._socket.recv(size - len(data)) | |
150 duration = time.time() - t | |
151 if block: | |
152 data.extend(block) | |
153 else: | |
154 # no data -> EOF (connection probably closed) | |
155 break | |
156 except socket.timeout: | |
157 # just need to get out of recv from time to time to check if | |
158 # still alive | |
159 continue | |
160 except socket.error, e: | |
161 # connection fails -> terminate loop | |
162 raise SerialException('connection failed (%s)' % e) | |
163 return bytes(data) | |
164 | |
165 def write(self, data): | |
166 """Output the given string over the serial port. Can block if the | |
167 connection is blocked. May raise SerialException if the connection is | |
168 closed.""" | |
169 if not self._isOpen: raise portNotOpenError | |
170 try: | |
171 self._socket.sendall(to_bytes(data)) | |
172 except socket.error, e: | |
173 # XXX what exception if socket connection fails | |
174 raise SerialException("socket connection failed: %s" % e) | |
175 return len(data) | |
176 | |
177 def flushInput(self): | |
178 """Clear input buffer, discarding all that is in the buffer.""" | |
179 if not self._isOpen: raise portNotOpenError | |
180 if self.logger: | |
181 self.logger.info('ignored flushInput') | |
182 | |
183 def flushOutput(self): | |
184 """Clear output buffer, aborting the current output and | |
185 discarding all that is in the buffer.""" | |
186 if not self._isOpen: raise portNotOpenError | |
187 if self.logger: | |
188 self.logger.info('ignored flushOutput') | |
189 | |
190 def sendBreak(self, duration=0.25): | |
191 """Send break condition. Timed, returns to idle state after given | |
192 duration.""" | |
193 if not self._isOpen: raise portNotOpenError | |
194 if self.logger: | |
195 self.logger.info('ignored sendBreak(%r)' % (duration,)) | |
196 | |
197 def setBreak(self, level=True): | |
198 """Set break: Controls TXD. When active, to transmitting is | |
199 possible.""" | |
200 if not self._isOpen: raise portNotOpenError | |
201 if self.logger: | |
202 self.logger.info('ignored setBreak(%r)' % (level,)) | |
203 | |
204 def setRTS(self, level=True): | |
205 """Set terminal status line: Request To Send""" | |
206 if not self._isOpen: raise portNotOpenError | |
207 if self.logger: | |
208 self.logger.info('ignored setRTS(%r)' % (level,)) | |
209 | |
210 def setDTR(self, level=True): | |
211 """Set terminal status line: Data Terminal Ready""" | |
212 if not self._isOpen: raise portNotOpenError | |
213 if self.logger: | |
214 self.logger.info('ignored setDTR(%r)' % (level,)) | |
215 | |
216 def getCTS(self): | |
217 """Read terminal status line: Clear To Send""" | |
218 if not self._isOpen: raise portNotOpenError | |
219 if self.logger: | |
220 self.logger.info('returning dummy for getCTS()') | |
221 return True | |
222 | |
223 def getDSR(self): | |
224 """Read terminal status line: Data Set Ready""" | |
225 if not self._isOpen: raise portNotOpenError | |
226 if self.logger: | |
227 self.logger.info('returning dummy for getDSR()') | |
228 return True | |
229 | |
230 def getRI(self): | |
231 """Read terminal status line: Ring Indicator""" | |
232 if not self._isOpen: raise portNotOpenError | |
233 if self.logger: | |
234 self.logger.info('returning dummy for getRI()') | |
235 return False | |
236 | |
237 def getCD(self): | |
238 """Read terminal status line: Carrier Detect""" | |
239 if not self._isOpen: raise portNotOpenError | |
240 if self.logger: | |
241 self.logger.info('returning dummy for getCD()') | |
242 return True | |
243 | |
244 # - - - platform specific - - - | |
245 # None so far | |
246 | |
247 | |
248 # assemble Serial class with the platform specific implementation and the base | |
249 # for file-like behavior. for Python 2.6 and newer, that provide the new I/O | |
250 # library, derive from io.RawIOBase | |
251 try: | |
252 import io | |
253 except ImportError: | |
254 # classic version with our own file-like emulation | |
255 class Serial(SocketSerial, FileLike): | |
256 pass | |
257 else: | |
258 # io library present | |
259 class Serial(SocketSerial, io.RawIOBase): | |
260 pass | |
261 | |
262 | |
263 # simple client test | |
264 if __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") | |
270 s.write("hello\n") | |
271 s.flush() | |
272 sys.stdout.write("read: %s\n" % s.read(5)) | |
273 | |
274 s.close() | |
OLD | NEW |