phy_serial.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. # -*- coding: utf-8 -*-
  2. #
  3. # PROFIBUS DP - Communication Processor PHY access library
  4. #
  5. # Copyright (c) 2016 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. class CpPhySerial(CpPhy):
  33. """pyserial based PROFIBUS CP PHYsical layer
  34. """
  35. def __init__(self, port, debug = False, useRS485Class = False):
  36. """port => "/dev/ttySx"
  37. debug => enable/disable debugging.
  38. useRS485Class => Use serial.rs485.RS485, if True. (might be slower).
  39. """
  40. super(CpPhySerial, self).__init__(debug = debug)
  41. self.__discardTimeout = None
  42. self.__rxBuf = bytearray()
  43. try:
  44. if useRS485Class:
  45. if not hasattr(serial, "rs485"):
  46. raise PhyError("Module serial.rs485 "
  47. "is not available. "
  48. "Please use useRS485Class=False.")
  49. self.__serial = serial.rs485.RS485()
  50. else:
  51. self.__serial = serial.Serial()
  52. self.__serial.port = port
  53. self.__serial.baudrate = CpPhy.BAUD_9600
  54. self.__serial.bytesize = 8
  55. self.__serial.parity = serial.PARITY_EVEN
  56. self.__serial.stopbits = serial.STOPBITS_ONE
  57. self.__serial.timeout = 0
  58. self.__serial.xonxoff = False
  59. self.__serial.rtscts = False
  60. self.__serial.dsrdtr = False
  61. if useRS485Class:
  62. self.__serial.rs485_mode = serial.rs485.RS485Settings(
  63. rts_level_for_tx = True,
  64. rts_level_for_rx = False,
  65. loopback = False,
  66. delay_before_tx = 0.0,
  67. delay_before_rx = 0.0
  68. )
  69. self.__serial.open()
  70. except (serial.SerialException, ValueError) as e:
  71. raise PhyError("Failed to open "
  72. "serial port:\n" + str(e))
  73. def close(self):
  74. try:
  75. self.__serial.close()
  76. except serial.SerialException as e:
  77. pass
  78. self.__rxBuf = bytearray()
  79. super(CpPhySerial, self).close()
  80. def __discard(self):
  81. s = self.__serial
  82. if s:
  83. s.flushInput()
  84. s.flushOutput()
  85. if monotonic_time() >= self.__discardTimeout:
  86. self.__discardTimeout = None
  87. def __startDiscard(self):
  88. self.__discardTimeout = monotonic_time() + 0.01
  89. # Poll for received packet.
  90. # timeout => In seconds. 0 = none, Negative = unlimited.
  91. def pollData(self, timeout = 0):
  92. timeoutStamp = monotonic_time() + timeout
  93. ret, rxBuf, s, size = None, self.__rxBuf, self.__serial, -1
  94. getSize = FdlTelegram.getSizeFromRaw
  95. if self.__discardTimeout is not None:
  96. while self.__discardTimeout is not None:
  97. self.__discard()
  98. if timeout >= 0 and\
  99. monotonic_time() >= timeoutStamp:
  100. return None
  101. try:
  102. while True:
  103. if len(rxBuf) < 1:
  104. rxBuf += s.read(1)
  105. elif len(rxBuf) < 3:
  106. try:
  107. size = getSize(rxBuf)
  108. readLen = size
  109. except ProfibusError:
  110. readLen = 3
  111. rxBuf += s.read(readLen - len(rxBuf))
  112. elif len(rxBuf) >= 3:
  113. try:
  114. size = getSize(rxBuf)
  115. except ProfibusError:
  116. rxBuf = bytearray()
  117. self.__startDiscard()
  118. raise PhyError("PHY-serial: "
  119. "Failed to get received "
  120. "telegram size:\n"
  121. "Invalid telegram format.")
  122. if len(rxBuf) < size:
  123. rxBuf += s.read(size - len(rxBuf))
  124. if len(rxBuf) == size:
  125. ret, rxBuf = rxBuf, bytearray()
  126. break
  127. if timeout >= 0 and\
  128. monotonic_time() >= timeoutStamp:
  129. break
  130. except serial.SerialException as e:
  131. rxBuf = bytearray()
  132. self.__startDiscard()
  133. raise PhyError("PHY-serial: Failed to receive "
  134. "telegram:\n" + str(e))
  135. finally:
  136. self.__rxBuf = rxBuf
  137. if self.debug and ret:
  138. print("PHY-serial: RX %s" % bytesToHex(ret))
  139. return ret
  140. def sendData(self, telegramData, srd):
  141. if self.__discardTimeout is not None:
  142. return
  143. try:
  144. telegramData = bytearray(telegramData)
  145. if self.debug:
  146. print("PHY-serial: TX %s" % bytesToHex(telegramData))
  147. self.__serial.write(telegramData)
  148. except serial.SerialException as e:
  149. raise PhyError("PHY-serial: Failed to transmit "
  150. "telegram:\n" + str(e))
  151. def setConfig(self, baudrate = CpPhy.BAUD_9600, rtscts = False, dsrdtr = False):
  152. wellSuppBaud = (9600, 19200)
  153. if baudrate not in wellSuppBaud:
  154. # The hw/driver might silently ignore the baudrate
  155. # and use the already set value from __init__().
  156. print("PHY-serial: Warning: The configured baud rate %d baud "
  157. "might not be supported by the hardware. "
  158. "Note that some hardware silently falls back "
  159. "to 9600 baud for unsupported rates. "
  160. "Commonly well supported baud rates by serial "
  161. "hardware are: %s." % (
  162. baudrate,
  163. ", ".join(str(b) for b in wellSuppBaud)))
  164. try:
  165. if baudrate != self.__serial.baudrate or rtscts != self.__serial.rtscts or dsrdtr != self.__serial.dsrdtr:
  166. self.__serial.close()
  167. self.__serial.baudrate = baudrate
  168. self.__serial.rtscts = rtscts
  169. self.__serial.dsrdtr = dsrdtr
  170. self.__serial.open()
  171. self.__rxBuf = bytearray()
  172. except (serial.SerialException, ValueError) as e:
  173. raise PhyError("Failed to set CP-PHY "
  174. "configuration:\n" + str(e))
  175. self.__setConfigPiLC(baudrate)
  176. super(CpPhySerial, self).setConfig(baudrate = baudrate)
  177. def __setConfigPiLC(self, baudrate):
  178. """Reconfigure the PiLC HAT, if available.
  179. """
  180. try:
  181. import libpilc.raspi_hat_conf as raspi_hat_conf
  182. except ImportError as e:
  183. return
  184. if not raspi_hat_conf.PilcConf.havePilcHat():
  185. return
  186. try:
  187. conf = raspi_hat_conf.PilcConf()
  188. conf.setBaudrate(baudrate / 1000.0)
  189. except raspi_hat_conf.PilcConf.Error as e:
  190. raise PhyError("Failed to configure PiLC HAT:\n%s" %\
  191. str(e))