serial_port.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import time
  2. import serial
  3. from typing import Any, Callable, Optional
  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. from nbus_types.nbus_defines import *
  13. def default_logger(*message: Any) -> None:
  14. """
  15. Default logger function.
  16. :param message: message to log
  17. :return: None
  18. """
  19. for i in range(len(message)):
  20. if isinstance(message[i], list):
  21. for m in message[i]:
  22. print(hex(m), end="|")
  23. continue
  24. print(message[i], end=" ")
  25. print()
  26. @beartype
  27. class NBusSerialPort(NBusPort):
  28. """
  29. Class representing nBus serial port.
  30. """
  31. def __init__(self, config: NBusSerialConfig):
  32. """
  33. Constructor.
  34. :param config: configuration
  35. """
  36. self._flush_delay = config.flush_delay
  37. self._port = serial.Serial(timeout=config.timeout)
  38. self._port.port = config.port_name
  39. self._port.parity = config.parity.value
  40. self._port.baudrate = config.baud.value
  41. self._logger_cb = default_logger
  42. self._enable_log = config.enable_log
  43. self._request_attempts = config.request_attempts
  44. """
  45. ================================================================================================================
  46. API Methods
  47. ================================================================================================================
  48. """
  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._flush_delay = config.flush_delay
  56. self._port.port = config.port_name
  57. self._port.parity = config.parity.value
  58. self._port.baudrate = config.baud.value
  59. self._enable_log = config.enable_log
  60. self._request_attempts = config.request_attempts
  61. def open(self) -> None:
  62. """
  63. Open port.
  64. """
  65. self._port.open()
  66. self._port.setRTS(False)
  67. self._port.flush()
  68. self._log("INFO", " 0", "\tOpen communication port")
  69. def close(self) -> None:
  70. """
  71. Close port.
  72. """
  73. self._log("INFO", " 0", "\tClose communication port")
  74. self._port.close()
  75. def flush(self) -> None:
  76. """
  77. Flush port with periodic check.
  78. """
  79. while self._port.in_waiting:
  80. self._log("INFO", " 0", "\tFlush communication port")
  81. self._port.reset_input_buffer()
  82. self._port.reset_output_buffer()
  83. time.sleep(self._flush_delay)
  84. def try_read(self) -> bytes:
  85. """
  86. Try reading from port.
  87. :return: bytes
  88. """
  89. if self._port.in_waiting > 0:
  90. data = self._port.read()
  91. self._log("DATA", f"{len(data):3}", "\tARS>", list(data))
  92. return data
  93. else:
  94. return bytes()
  95. def is_connected(self) -> bool:
  96. """
  97. Return connection status.
  98. :return: status (1 = connected, 0 = not connected)
  99. """
  100. return self._port.is_open
  101. def set_logger(self, callback: Callable[[Any], None]) -> None:
  102. """
  103. Set logger function.
  104. :param callback: logging callback
  105. """
  106. self._logger_cb = callback
  107. def request_bridge(self, command: NBusCommand, data: bytearray, long_answer: NBusDelay = 0.0):
  108. """
  109. Make bridge request.
  110. :param command: command id
  111. :param data: command data to send
  112. :param long_answer: delay in s for longer answer
  113. """
  114. return self._request_response(NBUS_BROADCAST_ADDR, NBUS_BRIDGE_ADDR, command.value, data, long_answer)
  115. def send_bridge(self, command: NBusCommand, data: bytearray):
  116. """
  117. Make bridge request without waiting for response.
  118. :param command: command id
  119. :param data: command data to send
  120. """
  121. request = self._create_packet(bytearray([0, NBUS_BROADCAST_ADDR, NBUS_BRIDGE_ADDR, command.value]), data)
  122. self._log("DATA", f"{request[0]:3}", "\tARQ>", list(request[1:]))
  123. self._port.write(request) # send message
  124. def request_broadcast(self, command: NBusCommand, data: bytearray) -> None:
  125. """
  126. Make broadcast request to nbus network.
  127. :param command: command id
  128. :param data: command data to send
  129. """
  130. request = self._create_packet(bytearray([0, NBUS_BROADCAST_ADDR, NBUS_BROADCAST_ADDR, command.value]), data)
  131. self._log("DATA", f"{request[0]:3}", "\tARQ>", list(request[1:]))
  132. self._port.write(request) # send message
  133. def request_module(self, module_addr: NBusModuleAddress, command: NBusCommand, data: bytearray,
  134. long_answer: NBusDelay = 0.0) -> bytearray:
  135. """
  136. Make module request to nbus network.
  137. :param module_addr: address of module
  138. :param command: command id
  139. :param data: command data to send
  140. :param long_answer: delay in s for longer answer
  141. :return: | payload length | payload |
  142. """
  143. return self._request_response(module_addr, 0, command.value, data, long_answer)
  144. def send_module(self, module_addr: NBusModuleAddress, command: NBusCommand, data: bytearray) -> None:
  145. """
  146. Make module request without waiting for response.
  147. :param module_addr: address of module
  148. :param command: command id
  149. :param data: command data to send
  150. """
  151. request = self._create_packet(bytearray([0, module_addr, 0, command.value]), data)
  152. self._log("DATA", f"{request[0]:3}", "\tARQ>", list(request[1:]))
  153. self._port.write(request) # send message
  154. def request_sensor(self, module_addr: NBusModuleAddress, sensor_address: NBusSensorAddress,
  155. command: NBusCommand, data: bytearray, long_answer: NBusDelay = 0.0) -> bytearray:
  156. """
  157. Make device request to nbus network.
  158. :param module_addr: address of module
  159. :param sensor_address: address of sensor
  160. :param command: command id
  161. :param data: command data to send
  162. :param long_answer: delay in s for longer answer
  163. :return: | payload length | payload |
  164. """
  165. return self._request_response(module_addr, sensor_address, command.value, data, long_answer)
  166. """
  167. ================================================================================================================
  168. Internal Methods
  169. ================================================================================================================
  170. """
  171. def _request_response(self, module: int, sensor: int, command, data: bytearray,
  172. long_answer: float = 0) -> bytearray:
  173. """
  174. Make request to nbus node and receive response.
  175. :param module: module address
  176. :param sensor: sensor address
  177. :param command: command id
  178. :param data: command data to send
  179. :param long_answer: timeout for long answer
  180. :return: response length | response
  181. """
  182. request = self._create_packet(bytearray([0, module, sensor, command]), data) # create request
  183. self._log("DATA", f"{request[0]:3}", "\tRQ>", list(request[1:])) # log request
  184. counter = 0 # err. trials
  185. while True: # try to communicate
  186. self._port.write(request)
  187. if long_answer > 0: # wait for long answer
  188. time.sleep(long_answer)
  189. try:
  190. return self._receive_payload()
  191. except NBusErrorNetwork as Ex: # if network error, try to reconnect
  192. counter += 1
  193. if counter > self._request_attempts:
  194. raise Ex # if out of trials, propagate exception
  195. def _create_packet(self, start: bytearray, data: bytearray) -> bytearray:
  196. """
  197. Create packet to send and prepare port.
  198. :param start: head of message
  199. :param data: body of message
  200. :return: request packet
  201. """
  202. if len(data) > 0:
  203. for c in data:
  204. start.append(c)
  205. if not self._port.is_open:
  206. self.open()
  207. self._port.flush()
  208. start[0] = len(start)
  209. crc_sum = crc8(start[1:])
  210. start.append(crc_sum)
  211. return start
  212. def _receive_payload(self) -> bytearray:
  213. """
  214. Read response from serial.
  215. :return: | payload len | payload
  216. """
  217. # read data length
  218. response_l = self._port.read(1)
  219. if (response_l is None) or (len(response_l) == 0): # check if message is empty
  220. raise NBusErrorNetwork(NBusErrorNetworkType.EMPTY_MESSAGE)
  221. response_l = ord(response_l) # convert response length to number
  222. # read response body
  223. response = self._port.read(response_l)
  224. if (response is None) or (len(response) != response_l): # check for message completeness
  225. raise NBusErrorNetwork(NBusErrorNetworkType.MESSAGE_NOT_COMPLETE)
  226. # check for crc
  227. if response[NBUS_CRC_ADDR] != crc8(response[:NBUS_CRC_ADDR]):
  228. raise NBusErrorNetwork(NBusErrorNetworkType.DAMAGED_MESSAGE)
  229. self._log("DATA", f"{response_l:3}", "\tRS>", list(response)) # log response
  230. # check for node error
  231. if response[NBUS_FC_ADDR] & NBUS_ERR_BIT:
  232. raise NBusErrorNode(NBusErrorNodeType(response[NBUS_DATA0_ADDR]))
  233. # return payload length + payload
  234. return bytearray([response_l - NBUS_RX_META_SIZE]) + bytearray(response[NBUS_DATA0_ADDR:NBUS_CRC_ADDR])
  235. def _log(self, *message: Any) -> None:
  236. """
  237. Log request/response.
  238. :param message: message to log
  239. """
  240. if self._enable_log:
  241. self._logger_cb(*message)