serial_port.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import time
  2. import serial
  3. from typing import Any, Callable
  4. from beartype import beartype
  5. from nbus_types.nbus_command_type import *
  6. from nbus_types.nbus_exceptions.nbus_network_exception import *
  7. from nbus_types.nbus_exceptions.nbus_node_exception import *
  8. from nbus_hal.nbus_serial.serial_config import NBusSerialConfig
  9. from nbus_hal.nbus_generic_port import NBusPort, NBusDelay
  10. from nbus_types.nbus_address_type import NBusModuleAddress, NBusSensorAddress
  11. from nbus_hal.crc8 import crc8
  12. def default_logger(*message: Any) -> None:
  13. """
  14. Default logger function.
  15. :param message: message to log
  16. :return: None
  17. """
  18. for i in range(len(message)):
  19. if isinstance(message[i], list):
  20. for m in message[i]:
  21. print(hex(m), end="|")
  22. continue
  23. print(message[i], end=" ")
  24. print()
  25. @beartype
  26. class NBusSerialPort(NBusPort):
  27. """
  28. Class representing nBus serial port.
  29. """
  30. def __init__(self, config: NBusSerialConfig):
  31. """
  32. Constructor.
  33. :param config: configuration
  34. """
  35. self._port = serial.Serial(timeout=config.timeout)
  36. self._port.port = config.port_name
  37. self._port.parity = config.parity.value
  38. self._port.baudrate = config.baud.value
  39. self._logger_cb = default_logger
  40. self._enable_log = config.enable_log
  41. self._request_attempts = config.request_attempts
  42. """
  43. ================================================================================================================
  44. API Methods
  45. ================================================================================================================
  46. """
  47. def get_port(self):
  48. return self._port
  49. def change_configuration(self, config: NBusSerialConfig):
  50. """
  51. Change port configuration.
  52. :param config: configuration
  53. """
  54. self._port.timeout = config.timeout
  55. self._port.port = config.port_name
  56. self._port.parity = config.parity.value
  57. self._port.baudrate = config.baud.value
  58. self._enable_log = config.enable_log
  59. self._request_attempts = config.request_attempts
  60. def open(self) -> None:
  61. """
  62. Open port.
  63. """
  64. self._port.open()
  65. self._port.flush()
  66. self._log('i', 0, 'Open communication port')
  67. def close(self) -> None:
  68. """
  69. Close port.
  70. """
  71. self._log('i', 0, 'Close communication port')
  72. self._port.close()
  73. def is_connected(self) -> bool:
  74. """
  75. Return connection status.
  76. :return: status (1 = connected, 0 = not connected)
  77. """
  78. return self._port.is_open
  79. def set_logger(self, callback: Callable[[Any], None]) -> None:
  80. """
  81. Set logger function.
  82. :param callback: logging callback
  83. """
  84. self._logger_cb = callback
  85. def request_bridge(self, command: NBusCommand, data: bytearray, long_answer: NBusDelay = 0.0):
  86. """
  87. Make broadcast request to the nbus bridge module.
  88. :param command: command id
  89. :param data: command data to send
  90. :param long_answer: delay in s for longer answer
  91. """
  92. return self._request_response(0, 0xFF, command.value, data, long_answer)
  93. def send_bridge(self, command: NBusCommand, data: bytearray):
  94. """
  95. Make broadcast request to the nbus bridge module.
  96. :param command: command id
  97. :param data: command data to send
  98. :param long_answer: delay in s for longer answer
  99. """
  100. request = self._create_packet(bytearray([0, 0, 0xFF, command.value]), data)
  101. self._log("\tBRQ>", list(request))
  102. self._port.write(request) # send message
  103. def request_broadcast(self, command: NBusCommand, data: bytearray) -> None:
  104. """
  105. Make broadcast request to nbus network.
  106. :param command: command id
  107. :param data: command data to send
  108. """
  109. request = self._create_packet(bytearray([0, 0, 0xFF, command.value]), data)
  110. self._log("\tBRQ>", list(request))
  111. self._port.write(request) # send message
  112. def request_module(self, module_addr: NBusModuleAddress, command: NBusCommand, data: bytearray,
  113. long_answer: NBusDelay = 0.0) -> bytearray:
  114. """
  115. Make module request to nbus network.
  116. :param module_addr: address of module
  117. :param command: command id
  118. :param data: command data to send
  119. :param long_answer: delay in s for longer answer
  120. :return: | payload length | payload |
  121. """
  122. return self._request_response(module_addr, 0, command.value, data, long_answer)
  123. def request_sensor(self, module_addr: NBusModuleAddress, sensor_address: NBusSensorAddress,
  124. command: NBusCommand, data: bytearray, long_answer: NBusDelay = 0.0) -> bytearray:
  125. """
  126. Make device request to nbus network.
  127. :param module_addr: address of module
  128. :param sensor_address: address of sensor
  129. :param command: command id
  130. :param data: command data to send
  131. :param long_answer: delay in s for longer answer
  132. :return: | payload length | payload |
  133. """
  134. return self._request_response(module_addr, sensor_address, command.value, data, long_answer)
  135. """
  136. ================================================================================================================
  137. Internal Methods
  138. ================================================================================================================
  139. """
  140. def _request_response(self, module: int, sensor: int, command, data: bytearray,
  141. long_answer: float = 0) -> bytearray:
  142. """
  143. Make request to nbus node and receive response.
  144. :param module: module address
  145. :param sensor: sensor address
  146. :param command: command id
  147. :param data: command data to send
  148. :param long_answer: timeout for long answer
  149. :return: response length | response
  150. """
  151. request = self._create_packet(bytearray([0, module, sensor, command]), data) # create request
  152. self._log('d', sensor, "\tRQ>", list(request)) # log request
  153. counter = 0 # err. trials
  154. while True: # try to communicate
  155. self._port.write(request)
  156. if long_answer > 0: # wait for long answer
  157. time.sleep(long_answer)
  158. try:
  159. return self._receive_payload()
  160. except NBusErrorNetwork as Ex: # if network error, try to reconnect
  161. counter += 1
  162. if counter > self._request_attempts:
  163. raise Ex # if out of trials, propagate exception
  164. def _create_packet(self, start: bytearray, data: bytearray) -> bytearray:
  165. """
  166. Create packet to send and prepare port.
  167. :param start: head of message
  168. :param data: body of message
  169. :return: request packet
  170. """
  171. if len(data) > 0:
  172. for c in data:
  173. start.append(c)
  174. if not self._port.is_open:
  175. self.open()
  176. self._port.flush()
  177. start[0] = len(start)
  178. crc_sum = crc8(start[1:])
  179. start.append(crc_sum)
  180. return start
  181. def _receive_payload(self) -> bytearray:
  182. """
  183. Read response from serial.
  184. :return: | payload len | payload
  185. """
  186. # read data length
  187. response_l = self._port.read(1)
  188. if (response_l is None) or (len(response_l) == 0): # check if message is empty
  189. raise NBusErrorNetwork(NBusErrorNetworkType.EMPTY_MESSAGE)
  190. response_l = ord(response_l) # convert response length to number
  191. # read response body
  192. response = self._port.read(response_l)
  193. if (response is None) or (len(response) != response_l): # check for message completeness
  194. raise NBusErrorNetwork(NBusErrorNetworkType.MESSAGE_NOT_COMPLETE)
  195. # check for crc
  196. if response[-1] != crc8(response[:-1]):
  197. print(response_l, response, crc8(response[:-1]), response[-1])
  198. raise NBusErrorNetwork(NBusErrorNetworkType.DAMAGED_MESSAGE)
  199. self._log('d', 0, "\tRS>", [response_l] + list(response)) # log response
  200. # check for node error
  201. if response[2] & NBUS_ERR_BIT:
  202. raise NBusErrorNode(NBusErrorNodeType(response[3]))
  203. return bytearray([response_l - 4]) + bytearray(response[3:-1]) # return payload length + payload
  204. def _log(self, *message: Any) -> None:
  205. """
  206. Log request/response.
  207. :param message: message to log
  208. """
  209. if self._enable_log:
  210. self._logger_cb(*message)