protocol_socket.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #! python
  2. #
  3. # 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. #
  10. # This file is part of pySerial. https://github.com/pyserial/pyserial
  11. # (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
  12. #
  13. # SPDX-License-Identifier: BSD-3-Clause
  14. #
  15. # URL format: socket://<host>:<port>[/option[/option...]]
  16. # options:
  17. # - "debug" print diagnostic messages
  18. import errno
  19. import logging
  20. import select
  21. import socket
  22. import time
  23. try:
  24. import urlparse
  25. except ImportError:
  26. import urllib.parse as urlparse
  27. from serial.serialutil import SerialBase, SerialException, to_bytes, \
  28. portNotOpenError, writeTimeoutError, Timeout
  29. # map log level names to constants. used in from_url()
  30. LOGGER_LEVELS = {
  31. 'debug': logging.DEBUG,
  32. 'info': logging.INFO,
  33. 'warning': logging.WARNING,
  34. 'error': logging.ERROR,
  35. }
  36. POLL_TIMEOUT = 5
  37. class Serial(SerialBase):
  38. """Serial port implementation for plain sockets."""
  39. BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
  40. 9600, 19200, 38400, 57600, 115200)
  41. def open(self):
  42. """\
  43. Open port with current settings. This may throw a SerialException
  44. if the port cannot be opened.
  45. """
  46. self.logger = None
  47. if self._port is None:
  48. raise SerialException("Port must be configured before it can be used.")
  49. if self.is_open:
  50. raise SerialException("Port is already open.")
  51. try:
  52. # timeout is used for write timeout support :/ and to get an initial connection timeout
  53. self._socket = socket.create_connection(self.from_url(self.portstr), timeout=POLL_TIMEOUT)
  54. except Exception as msg:
  55. self._socket = None
  56. raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
  57. # after connecting, switch to non-blocking, we're using select
  58. self._socket.setblocking(False)
  59. # not that there is anything to configure...
  60. self._reconfigure_port()
  61. # all things set up get, now a clean start
  62. self.is_open = True
  63. if not self._dsrdtr:
  64. self._update_dtr_state()
  65. if not self._rtscts:
  66. self._update_rts_state()
  67. self.reset_input_buffer()
  68. self.reset_output_buffer()
  69. def _reconfigure_port(self):
  70. """\
  71. Set communication parameters on opened port. For the socket://
  72. protocol all settings are ignored!
  73. """
  74. if self._socket is None:
  75. raise SerialException("Can only operate on open ports")
  76. if self.logger:
  77. self.logger.info('ignored port configuration change')
  78. def close(self):
  79. """Close port"""
  80. if self.is_open:
  81. if self._socket:
  82. try:
  83. self._socket.shutdown(socket.SHUT_RDWR)
  84. self._socket.close()
  85. except:
  86. # ignore errors.
  87. pass
  88. self._socket = None
  89. self.is_open = False
  90. # in case of quick reconnects, give the server some time
  91. time.sleep(0.3)
  92. def from_url(self, url):
  93. """extract host and port from an URL string"""
  94. parts = urlparse.urlsplit(url)
  95. if parts.scheme != "socket":
  96. raise SerialException(
  97. 'expected a string in the form '
  98. '"socket://<host>:<port>[?logging={debug|info|warning|error}]": '
  99. 'not starting with socket:// ({!r})'.format(parts.scheme))
  100. try:
  101. # process options now, directly altering self
  102. for option, values in urlparse.parse_qs(parts.query, True).items():
  103. if option == 'logging':
  104. logging.basicConfig() # XXX is that good to call it here?
  105. self.logger = logging.getLogger('pySerial.socket')
  106. self.logger.setLevel(LOGGER_LEVELS[values[0]])
  107. self.logger.debug('enabled logging')
  108. else:
  109. raise ValueError('unknown option: {!r}'.format(option))
  110. if not 0 <= parts.port < 65536:
  111. raise ValueError("port not in range 0...65535")
  112. except ValueError as e:
  113. raise SerialException(
  114. 'expected a string in the form '
  115. '"socket://<host>:<port>[?logging={debug|info|warning|error}]": {}'.format(e))
  116. return (parts.hostname, parts.port)
  117. # - - - - - - - - - - - - - - - - - - - - - - - -
  118. @property
  119. def in_waiting(self):
  120. """Return the number of bytes currently in the input buffer."""
  121. if not self.is_open:
  122. raise portNotOpenError
  123. # Poll the socket to see if it is ready for reading.
  124. # If ready, at least one byte will be to read.
  125. lr, lw, lx = select.select([self._socket], [], [], 0)
  126. return len(lr)
  127. # select based implementation, similar to posix, but only using socket API
  128. # to be portable, additionally handle socket timeout which is used to
  129. # emulate write timeouts
  130. def read(self, size=1):
  131. """\
  132. Read size bytes from the serial port. If a timeout is set it may
  133. return less characters as requested. With no timeout it will block
  134. until the requested number of bytes is read.
  135. """
  136. if not self.is_open:
  137. raise portNotOpenError
  138. read = bytearray()
  139. timeout = Timeout(self._timeout)
  140. while len(read) < size:
  141. try:
  142. ready, _, _ = select.select([self._socket], [], [], timeout.time_left())
  143. # If select was used with a timeout, and the timeout occurs, it
  144. # returns with empty lists -> thus abort read operation.
  145. # For timeout == 0 (non-blocking operation) also abort when
  146. # there is nothing to read.
  147. if not ready:
  148. break # timeout
  149. buf = self._socket.recv(size - len(read))
  150. # read should always return some data as select reported it was
  151. # ready to read when we get to this point, unless it is EOF
  152. if not buf:
  153. raise SerialException('socket disconnected')
  154. read.extend(buf)
  155. except OSError as e:
  156. # this is for Python 3.x where select.error is a subclass of
  157. # OSError ignore BlockingIOErrors and EINTR. other errors are shown
  158. # https://www.python.org/dev/peps/pep-0475.
  159. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
  160. raise SerialException('read failed: {}'.format(e))
  161. except (select.error, socket.error) as e:
  162. # this is for Python 2.x
  163. # ignore BlockingIOErrors and EINTR. all errors are shown
  164. # see also http://www.python.org/dev/peps/pep-3151/#select
  165. if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
  166. raise SerialException('read failed: {}'.format(e))
  167. if timeout.expired():
  168. break
  169. return bytes(read)
  170. def write(self, data):
  171. """\
  172. Output the given byte string over the serial port. Can block if the
  173. connection is blocked. May raise SerialException if the connection is
  174. closed.
  175. """
  176. if not self.is_open:
  177. raise portNotOpenError
  178. d = to_bytes(data)
  179. tx_len = length = len(d)
  180. timeout = Timeout(self._write_timeout)
  181. while tx_len > 0:
  182. try:
  183. n = self._socket.send(d)
  184. if timeout.is_non_blocking:
  185. # Zero timeout indicates non-blocking - simply return the
  186. # number of bytes of data actually written
  187. return n
  188. elif not timeout.is_infinite:
  189. # when timeout is set, use select to wait for being ready
  190. # with the time left as timeout
  191. if timeout.expired():
  192. raise writeTimeoutError
  193. _, ready, _ = select.select([], [self._socket], [], timeout.time_left())
  194. if not ready:
  195. raise writeTimeoutError
  196. else:
  197. assert timeout.time_left() is None
  198. # wait for write operation
  199. _, ready, _ = select.select([], [self._socket], [], None)
  200. if not ready:
  201. raise SerialException('write failed (select)')
  202. d = d[n:]
  203. tx_len -= n
  204. except SerialException:
  205. raise
  206. except OSError as e:
  207. # this is for Python 3.x where select.error is a subclass of
  208. # OSError ignore BlockingIOErrors and EINTR. other errors are shown
  209. # https://www.python.org/dev/peps/pep-0475.
  210. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
  211. raise SerialException('write failed: {}'.format(e))
  212. except select.error as e:
  213. # this is for Python 2.x
  214. # ignore BlockingIOErrors and EINTR. all errors are shown
  215. # see also http://www.python.org/dev/peps/pep-3151/#select
  216. if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
  217. raise SerialException('write failed: {}'.format(e))
  218. if not timeout.is_non_blocking and timeout.expired():
  219. raise writeTimeoutError
  220. return length - len(d)
  221. def reset_input_buffer(self):
  222. """Clear input buffer, discarding all that is in the buffer."""
  223. if not self.is_open:
  224. raise portNotOpenError
  225. # just use recv to remove input, while there is some
  226. ready = True
  227. while ready:
  228. ready, _, _ = select.select([self._socket], [], [], 0)
  229. try:
  230. self._socket.recv(4096)
  231. except OSError as e:
  232. # this is for Python 3.x where select.error is a subclass of
  233. # OSError ignore BlockingIOErrors and EINTR. other errors are shown
  234. # https://www.python.org/dev/peps/pep-0475.
  235. if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
  236. raise SerialException('read failed: {}'.format(e))
  237. except (select.error, socket.error) as e:
  238. # this is for Python 2.x
  239. # ignore BlockingIOErrors and EINTR. all errors are shown
  240. # see also http://www.python.org/dev/peps/pep-3151/#select
  241. if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
  242. raise SerialException('read failed: {}'.format(e))
  243. def reset_output_buffer(self):
  244. """\
  245. Clear output buffer, aborting the current output and
  246. discarding all that is in the buffer.
  247. """
  248. if not self.is_open:
  249. raise portNotOpenError
  250. if self.logger:
  251. self.logger.info('ignored reset_output_buffer')
  252. def send_break(self, duration=0.25):
  253. """\
  254. Send break condition. Timed, returns to idle state after given
  255. duration.
  256. """
  257. if not self.is_open:
  258. raise portNotOpenError
  259. if self.logger:
  260. self.logger.info('ignored send_break({!r})'.format(duration))
  261. def _update_break_state(self):
  262. """Set break: Controls TXD. When active, to transmitting is
  263. possible."""
  264. if self.logger:
  265. self.logger.info('ignored _update_break_state({!r})'.format(self._break_state))
  266. def _update_rts_state(self):
  267. """Set terminal status line: Request To Send"""
  268. if self.logger:
  269. self.logger.info('ignored _update_rts_state({!r})'.format(self._rts_state))
  270. def _update_dtr_state(self):
  271. """Set terminal status line: Data Terminal Ready"""
  272. if self.logger:
  273. self.logger.info('ignored _update_dtr_state({!r})'.format(self._dtr_state))
  274. @property
  275. def cts(self):
  276. """Read terminal status line: Clear To Send"""
  277. if not self.is_open:
  278. raise portNotOpenError
  279. if self.logger:
  280. self.logger.info('returning dummy for cts')
  281. return True
  282. @property
  283. def dsr(self):
  284. """Read terminal status line: Data Set Ready"""
  285. if not self.is_open:
  286. raise portNotOpenError
  287. if self.logger:
  288. self.logger.info('returning dummy for dsr')
  289. return True
  290. @property
  291. def ri(self):
  292. """Read terminal status line: Ring Indicator"""
  293. if not self.is_open:
  294. raise portNotOpenError
  295. if self.logger:
  296. self.logger.info('returning dummy for ri')
  297. return False
  298. @property
  299. def cd(self):
  300. """Read terminal status line: Carrier Detect"""
  301. if not self.is_open:
  302. raise portNotOpenError
  303. if self.logger:
  304. self.logger.info('returning dummy for cd)')
  305. return True
  306. # - - - platform specific - - -
  307. # works on Linux and probably all the other POSIX systems
  308. def fileno(self):
  309. """Get the file handle of the underlying socket for use with select"""
  310. return self._socket.fileno()
  311. #
  312. # simple client test
  313. if __name__ == '__main__':
  314. import sys
  315. s = Serial('socket://localhost:7000')
  316. sys.stdout.write('{}\n'.format(s))
  317. sys.stdout.write("write...\n")
  318. s.write(b"hello\n")
  319. s.flush()
  320. sys.stdout.write("read: {}\n".format(s.read(5)))
  321. s.close()