protocol_socket.py 14 KB

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