modbus_rtu.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. import struct
  4. import time
  5. from utils.modbus import LOGGER
  6. from utils.modbus.modbus import (
  7. Databank, Query, Master, Server,
  8. InvalidArgumentError, ModbusInvalidResponseError, ModbusInvalidRequestError
  9. )
  10. from utils.modbus.hooks import call_hooks
  11. from utils.modbus import utils
  12. class RtuQuery(Query):
  13. def __init__(self):
  14. super(RtuQuery, self).__init__()
  15. self._request_address = 0
  16. self._response_address = 0
  17. def build_request(self, pdu, slave):
  18. self._request_address = slave
  19. if (self._request_address < 0) or (self._request_address > 255):
  20. raise InvalidArgumentError("Invalid address {0}".format(self._request_address))
  21. data = struct.pack(">B", self._request_address) + pdu
  22. crc = struct.pack(">H", utils.calculate_crc(data))
  23. return data + crc
  24. def parse_response(self, response):
  25. if len(response) < 3:
  26. raise ModbusInvalidResponseError("Response length is invalid {0}".format(len(response)))
  27. (self._response_address, ) = struct.unpack(">B", response[0:1])
  28. if self._request_address != self._response_address:
  29. raise ModbusInvalidResponseError(
  30. "Response address {0} is different from request address {1}".format(
  31. self._response_address, self._request_address
  32. )
  33. )
  34. (crc, ) = struct.unpack(">H", response[-2:])
  35. if crc != utils.calculate_crc(response[:-2]):
  36. raise ModbusInvalidResponseError("Invalid CRC in response")
  37. return response[1:-2]
  38. def parse_request(self, request):
  39. if len(request) < 3:
  40. raise ModbusInvalidRequestError("Request length is invalid {0}".format(len(request)))
  41. (self._request_address, ) = struct.unpack(">B", request[0:1])
  42. (crc, ) = struct.unpack(">H", request[-2:])
  43. if crc != utils.calculate_crc(request[:-2]):
  44. raise ModbusInvalidRequestError("Invalid CRC in request")
  45. return self._request_address, request[1:-2]
  46. def build_response(self, response_pdu):
  47. self._response_address = self._request_address
  48. data = struct.pack(">B", self._response_address) + response_pdu
  49. crc = struct.pack(">H", utils.calculate_crc(data))
  50. return data + crc
  51. class RtuMaster(Master):
  52. def __init__(self, serial, interchar_multiplier=1.5, interframe_multiplier=3.5, t0=None):
  53. self._serial = serial
  54. self.use_sw_timeout = False
  55. LOGGER.debug("RtuMaster %s is %s", self._serial.name, "opened" if self._serial.is_open else "closed")
  56. super(RtuMaster, self).__init__(self._serial.timeout)
  57. if t0:
  58. self._t0 = t0
  59. else:
  60. self._t0 = utils.calculate_rtu_inter_char(self._serial.baudrate)
  61. self._serial.inter_byte_timeout = interchar_multiplier * self._t0
  62. self.set_timeout(interframe_multiplier * self._t0)
  63. self.handle_local_echo = False
  64. def _do_open(self):
  65. if not self._serial.is_open:
  66. call_hooks("modbus_rtu.RtuMaster.before_open", (self, ))
  67. self._serial.open()
  68. def _do_close(self):
  69. if self._serial.is_open:
  70. self._serial.close()
  71. call_hooks("modbus_rtu.RtuMaster.after_close", (self, ))
  72. return True
  73. def set_timeout(self, timeout_in_sec, use_sw_timeout=False):
  74. Master.set_timeout(self, timeout_in_sec)
  75. self._serial.timeout = timeout_in_sec
  76. self.use_sw_timeout = use_sw_timeout
  77. def _send(self, request):
  78. retval = call_hooks("modbus_rtu.RtuMaster.before_send", (self, request))
  79. if retval is not None:
  80. request = retval
  81. self._serial.reset_input_buffer()
  82. self._serial.reset_output_buffer()
  83. self._serial.write(request)
  84. self._serial.flush()
  85. if self.handle_local_echo:
  86. self._serial.read(len(request))
  87. def _recv(self, expected_length=-1):
  88. response = utils.to_data("")
  89. start_time = time.time() if self.use_sw_timeout else 0
  90. readed_len = 0
  91. while True:
  92. if self._serial.timeout:
  93. read_bytes = self._serial.read(expected_length - readed_len if (expected_length - readed_len) > 0 else 1)
  94. else:
  95. read_bytes = self._serial.read(expected_length if expected_length > 0 else 1)
  96. if self.use_sw_timeout:
  97. read_duration = time.time() - start_time
  98. else:
  99. read_duration = 0
  100. if (not read_bytes) or (read_duration > self._serial.timeout):
  101. break
  102. response += read_bytes
  103. if expected_length >= 0 and len(response) >= expected_length:
  104. break
  105. readed_len += len(read_bytes)
  106. retval = call_hooks("modbus_rtu.RtuMaster.after_recv", (self, response))
  107. if retval is not None:
  108. return retval
  109. return response
  110. def _make_query(self):
  111. return RtuQuery()
  112. class RtuServer(Server):
  113. _timeout = 0
  114. def __init__(self, serial, databank=None, error_on_missing_slave=True, **kwargs):
  115. interframe_multiplier = kwargs.pop('interframe_multiplier', 3.5)
  116. interchar_multiplier = kwargs.pop('interchar_multiplier', 1.5)
  117. databank = databank if databank else Databank(error_on_missing_slave=error_on_missing_slave)
  118. super(RtuServer, self).__init__(databank)
  119. self._serial = serial
  120. LOGGER.debug("RtuServer %s is %s", self._serial.name, "opened" if self._serial.is_open else "closed")
  121. self._t0 = utils.calculate_rtu_inter_char(self._serial.baudrate)
  122. self._serial.inter_byte_timeout = interchar_multiplier * self._t0
  123. self.set_timeout(interframe_multiplier * self._t0)
  124. self._block_on_first_byte = False
  125. def close(self):
  126. if self._serial.is_open:
  127. call_hooks("modbus_rtu.RtuServer.before_close", (self, ))
  128. self._serial.close()
  129. call_hooks("modbus_rtu.RtuServer.after_close", (self, ))
  130. def set_timeout(self, timeout):
  131. self._timeout = timeout
  132. self._serial.timeout = timeout
  133. def get_timeout(self):
  134. return self._timeout
  135. def __del__(self):
  136. self.close()
  137. def _make_query(self):
  138. return RtuQuery()
  139. def start(self):
  140. self._block_on_first_byte = True
  141. super(RtuServer, self).start()
  142. def stop(self):
  143. self._block_on_first_byte = False
  144. if self._serial.is_open:
  145. self._serial.cancel_read()
  146. super(RtuServer, self).stop()
  147. def _do_init(self):
  148. if not self._serial.is_open:
  149. call_hooks("modbus_rtu.RtuServer.before_open", (self, ))
  150. self._serial.open()
  151. call_hooks("modbus_rtu.RtuServer.after_open", (self, ))
  152. def _do_exit(self):
  153. self.close()
  154. def _do_run(self):
  155. try:
  156. request = utils.to_data('')
  157. if self._block_on_first_byte:
  158. self._serial.timeout = None
  159. try:
  160. read_bytes = self._serial.read(1)
  161. request += read_bytes
  162. except Exception as e:
  163. self._serial.close()
  164. self._serial.open()
  165. self._serial.timeout = self._timeout
  166. while True:
  167. try:
  168. read_bytes = self._serial.read(128)
  169. if not read_bytes:
  170. break
  171. except Exception as e:
  172. self._serial.close()
  173. self._serial.open()
  174. break
  175. request += read_bytes
  176. if request:
  177. retval = call_hooks("modbus_rtu.RtuServer.after_read", (self, request))
  178. if retval is not None:
  179. request = retval
  180. response = self._handle(request)
  181. retval = call_hooks("modbus_rtu.RtuServer.before_write", (self, response))
  182. if retval is not None:
  183. response = retval
  184. if response:
  185. if self._serial.in_waiting > 0:
  186. LOGGER.warning("Not sending response because there is new request pending")
  187. else:
  188. self._serial.write(response)
  189. self._serial.flush()
  190. time.sleep(self.get_timeout())
  191. call_hooks("modbus_rtu.RtuServer.after_write", (self, response))
  192. except Exception as excpt:
  193. LOGGER.error("Error while handling request, Exception occurred: %s", excpt)
  194. call_hooks("modbus_rtu.RtuServer.on_error", (self, excpt))