phy_serial.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # -*- coding: utf-8 -*-
  2. #
  3. # PROFIBUS DP - Communication Processor PHY access library
  4. #
  5. # Copyright (c) 2016-2021 Michael Buesch <m@bues.ch>
  6. #
  7. # Licensed under the terms of the GNU General Public License version 2,
  8. # or (at your option) any later version.
  9. #
  10. from __future__ import division, absolute_import, print_function, unicode_literals
  11. from pyprofibus.compat import *
  12. from pyprofibus.phy import *
  13. from pyprofibus.fdl import FdlTelegram
  14. from pyprofibus.util import *
  15. import sys
  16. try:
  17. import serial
  18. except ImportError as e:
  19. if "PyPy" in sys.version and\
  20. sys.version_info[0] == 2:
  21. # We are on PyPy2.
  22. # Try to import CPython2's serial.
  23. import glob
  24. sys.path.extend(glob.glob("/usr/lib/python2*/*-packages/"))
  25. import serial
  26. else:
  27. raise e
  28. try:
  29. import serial.rs485
  30. except ImportError:
  31. pass
  32. __all__ = [
  33. "CpPhySerial",
  34. ]
  35. class CpPhySerial(CpPhy):
  36. """pyserial based PROFIBUS CP PHYsical layer
  37. """
  38. __slots__ = (
  39. "__discardTimeout",
  40. "__rxBuf",
  41. "__serial",
  42. )
  43. PFX = "PHY-serial: "
  44. def __init__(self, port, useRS485Class=False, *args, **kwargs):
  45. """port => "/dev/ttySx"
  46. debug => enable/disable debugging.
  47. useRS485Class => Use serial.rs485.RS485, if True. (might be slower).
  48. """
  49. super(CpPhySerial, self).__init__(*args, **kwargs)
  50. self.__discardTimeout = None
  51. self.__rxBuf = bytearray()
  52. try:
  53. if useRS485Class:
  54. if not hasattr(serial, "rs485"):
  55. raise PhyError("Module serial.rs485 "
  56. "is not available. "
  57. "Please use useRS485Class=False.")
  58. self.__serial = serial.rs485.RS485()
  59. else:
  60. self.__serial = serial.Serial()
  61. self.__serial.port = port
  62. self.__serial.baudrate = CpPhy.BAUD_9600
  63. self.__serial.bytesize = 8
  64. self.__serial.parity = serial.PARITY_EVEN
  65. self.__serial.stopbits = serial.STOPBITS_ONE
  66. self.__serial.timeout = 0
  67. self.__serial.xonxoff = False
  68. self.__serial.rtscts = False
  69. self.__serial.dsrdtr = False
  70. if useRS485Class:
  71. self.__serial.rs485_mode = serial.rs485.RS485Settings(
  72. rts_level_for_tx = True,
  73. rts_level_for_rx = False,
  74. loopback = False,
  75. delay_before_tx = 0.0,
  76. delay_before_rx = 0.0
  77. )
  78. self.__serial.open()
  79. except (serial.SerialException, ValueError) as e:
  80. raise PhyError("Failed to open "
  81. "serial port:\n" + str(e))
  82. def close(self):
  83. try:
  84. self.__serial.close()
  85. except serial.SerialException as e:
  86. pass
  87. self.__rxBuf = bytearray()
  88. super(CpPhySerial, self).close()
  89. def __discard(self):
  90. s = self.__serial
  91. if s:
  92. s.flushInput()
  93. s.flushOutput()
  94. if monotonic_time() >= self.__discardTimeout:
  95. self.__discardTimeout = None
  96. def __startDiscard(self):
  97. self.__discardTimeout = monotonic_time() + 0.01
  98. # Poll for received packet.
  99. # timeout => In seconds. 0.0 = none, Negative = unlimited.
  100. def pollData(self, timeout=0.0):
  101. if timeout > 0.0:
  102. timeoutStamp = monotonic_time() + timeout
  103. ret = None
  104. rxBuf = self.__rxBuf
  105. ser = self.__serial
  106. size = -1
  107. getSize = FdlTelegram.getSizeFromRaw
  108. while self.__discardTimeout is not None:
  109. self.__discard()
  110. if timeout > 0.0 and monotonic_time() >= timeoutStamp:
  111. return None
  112. try:
  113. rxBufLen = len(rxBuf)
  114. while True:
  115. if rxBufLen < 1:
  116. rxBuf.extend(ser.read(1))
  117. elif rxBufLen < 3:
  118. if size < 0:
  119. size = getSize(rxBuf)
  120. readLen = (size if size > 0 else 3) - rxBufLen
  121. if readLen > 0:
  122. rxBuf.extend(ser.read(readLen))
  123. elif rxBufLen >= 3:
  124. if size < 0:
  125. size = getSize(rxBuf)
  126. if size < 0:
  127. if self.debug and rxBuf:
  128. self._debugMsg("RX (fragment) %s" % bytesToHex(rxBuf))
  129. rxBuf = bytearray()
  130. self.__startDiscard()
  131. raise PhyError("PHY-serial: "
  132. "Failed to get received telegram size: "
  133. "Invalid telegram format.")
  134. if rxBufLen < size:
  135. rxBuf.extend(ser.read(size - rxBufLen))
  136. rxBufLen = len(rxBuf)
  137. if rxBufLen == size:
  138. ret = rxBuf
  139. rxBuf = bytearray()
  140. rxBufLen = 0
  141. break
  142. if (timeout == 0.0 or
  143. (timeout > 0.0 and monotonic_time() >= timeoutStamp)):
  144. break
  145. except serial.SerialException as e:
  146. if self.debug and rxBuf:
  147. self._debugMsg("RX (fragment) %s" % bytesToHex(rxBuf))
  148. rxBuf = bytearray()
  149. self.__startDiscard()
  150. raise PhyError("PHY-serial: Failed to receive "
  151. "telegram:\n" + str(e))
  152. finally:
  153. self.__rxBuf = rxBuf
  154. if self.debug and ret:
  155. self._debugMsg("RX %s" % bytesToHex(ret))
  156. return ret
  157. def sendData(self, telegramData, srd):
  158. if self.__discardTimeout is not None:
  159. return
  160. try:
  161. if self.debug:
  162. self._debugMsg("TX %s" % bytesToHex(telegramData))
  163. self.__serial.write(telegramData)
  164. except serial.SerialException as e:
  165. raise PhyError("PHY-serial: Failed to transmit "
  166. "telegram:\n" + str(e))
  167. def setConfig(self, baudrate=CpPhy.BAUD_9600, rtscts=False, dsrdtr=False, *args, **kwargs):
  168. wellSuppBaud = (9600, 19200)
  169. if baudrate not in wellSuppBaud and not isMicropython:
  170. # The hw/driver might silently ignore the baudrate
  171. # and use the already set value from __init__().
  172. self._warningMsg("The configured baud rate %d baud "
  173. "might not be supported by the hardware. "
  174. "Note that some hardware silently falls back "
  175. "to 9600 baud for unsupported rates. "
  176. "Commonly well supported baud rates by serial "
  177. "hardware are: %s." % (
  178. baudrate,
  179. ", ".join(str(b) for b in wellSuppBaud)))
  180. try:
  181. if baudrate != self.__serial.baudrate or rtscts != self.__serial.rtscts or dsrdtr != self.__serial.dsrdtr:
  182. self.__serial.close()
  183. self.__serial.baudrate = baudrate
  184. self.__serial.rtscts = rtscts
  185. self.__serial.dsrdtr = dsrdtr
  186. self.__serial.open()
  187. self.__rxBuf = bytearray()
  188. except (serial.SerialException, ValueError) as e:
  189. raise PhyError("Failed to set CP-PHY "
  190. "configuration:\n" + str(e))
  191. self.__setConfigPiLC(baudrate)
  192. super(CpPhySerial, self).setConfig(baudrate=baudrate,
  193. rtscts=rtscts,
  194. dsrdtr=dsrdtr,
  195. *args, **kwargs)
  196. def __setConfigPiLC(self, baudrate):
  197. """Reconfigure the PiLC HAT, if available.
  198. """
  199. try:
  200. import libpilc.raspi_hat_conf as raspi_hat_conf
  201. except ImportError as e:
  202. return
  203. if not raspi_hat_conf.PilcConf.havePilcHat():
  204. return
  205. try:
  206. conf = raspi_hat_conf.PilcConf()
  207. conf.setBaudrate(baudrate / 1000.0)
  208. except raspi_hat_conf.PilcConf.Error as e:
  209. raise PhyError("Failed to configure PiLC HAT:\n%s" %\
  210. str(e))