返回教程正文

配套源码

socketcan_transport.py

can-gateway/socketcan_transport.py
zdt_x57s_can Brick:把一台 ZDT X57S 封装成可复用对象can-gateway/socketcan_transport.py
Python176 行
  1. """
  2. @description : 使用Python标准库封装Linux SocketCAN经典CAN扩展帧收发
  3. @param : 无
  4. @return : 无
  5. """
  6. from dataclasses import dataclass
  7. import socket
  8. import struct
  9. CAN_EFF_FLAG = 0x80000000
  10. CAN_EFF_MASK = 0x1FFFFFFF
  11. CAN_FRAME_FORMAT = "=IB3x8s"
  12. CAN_FRAME_SIZE = struct.calcsize(CAN_FRAME_FORMAT)
  13. @dataclass(frozen=True)
  14. class CanFrame:
  15. """
  16. @description : 保存一帧经典CAN报文的标识符、帧类型和有效数据
  17. @param arbitration_id: 11位或29位CAN标识符
  18. @param is_extended : true为29位扩展帧; false为11位标准帧
  19. @param data : 有效数据,长度0至8字节
  20. @return : CanFrame实例
  21. """
  22. arbitration_id: int
  23. is_extended: bool
  24. data: bytes
  25. class SocketCanTransport:
  26. """
  27. @description : 管理指定Linux SocketCAN网络接口的经典CAN通信
  28. @param interface : SocketCAN接口名称,例如can0
  29. @return : SocketCanTransport实例
  30. """
  31. def __init__(self, interface):
  32. """
  33. @description : 保存SocketCAN接口名称并初始化未连接状态
  34. @param interface : SocketCAN接口名称,例如can0
  35. @return : 无
  36. """
  37. if not interface:
  38. raise ValueError("interface must not be empty")
  39. self._interface = interface
  40. self._socket = None
  41. def open(self):
  42. """
  43. @description : 创建并绑定Linux原始SocketCAN套接字
  44. @param : 无
  45. @return : 当前SocketCanTransport实例
  46. """
  47. if self._socket is not None:
  48. return self
  49. can_socket = socket.socket(
  50. socket.PF_CAN, socket.SOCK_RAW, socket.CAN_RAW
  51. )
  52. try:
  53. can_socket.bind((self._interface,))
  54. except Exception:
  55. can_socket.close()
  56. raise
  57. self._socket = can_socket
  58. return self
  59. def close(self):
  60. """
  61. @description : 关闭SocketCAN套接字并释放资源
  62. @param : 无
  63. @return : 无
  64. """
  65. if self._socket is not None:
  66. self._socket.close()
  67. self._socket = None
  68. def clear_receive_queue(self):
  69. """
  70. @description : 清空套接字中尚未处理的历史CAN报文
  71. @param : 无
  72. @return : 被清除的报文数量
  73. """
  74. can_socket = self._require_open()
  75. previous_timeout = can_socket.gettimeout()
  76. cleared_count = 0
  77. try:
  78. can_socket.setblocking(False)
  79. while True:
  80. can_socket.recv(CAN_FRAME_SIZE)
  81. cleared_count += 1
  82. except BlockingIOError:
  83. pass
  84. finally:
  85. can_socket.settimeout(previous_timeout)
  86. return cleared_count
  87. def send(self, arbitration_id, data, is_extended=True):
  88. """
  89. @description : 发送一帧经典CAN报文
  90. @param arbitration_id: CAN标识符,扩展帧范围0至0x1FFFFFFF
  91. @param data : 待发送数据,长度0至8字节
  92. @param is_extended : true发送29位扩展帧; false发送11位标准帧
  93. @return : 实际写入套接字的字节数
  94. """
  95. payload = bytes(data)
  96. if len(payload) > 8:
  97. raise ValueError("classic CAN payload must not exceed 8 bytes")
  98. if arbitration_id < 0 or arbitration_id > CAN_EFF_MASK:
  99. raise ValueError("arbitration_id is out of range")
  100. can_id = arbitration_id | (CAN_EFF_FLAG if is_extended else 0)
  101. packed_frame = struct.pack(
  102. CAN_FRAME_FORMAT,
  103. can_id,
  104. len(payload),
  105. payload.ljust(8, b"\x00"),
  106. )
  107. return self._require_open().send(packed_frame)
  108. def receive(self, timeout_s):
  109. """
  110. @description : 在限定时间内接收一帧经典CAN报文
  111. @param timeout_s : 最大等待时间,单位秒
  112. @return : 收到时返回CanFrame; 超时返回None
  113. """
  114. if timeout_s < 0:
  115. raise ValueError("timeout_s must not be negative")
  116. can_socket = self._require_open()
  117. previous_timeout = can_socket.gettimeout()
  118. try:
  119. can_socket.settimeout(timeout_s)
  120. raw_frame = can_socket.recv(CAN_FRAME_SIZE)
  121. except socket.timeout:
  122. return None
  123. finally:
  124. can_socket.settimeout(previous_timeout)
  125. can_id, data_length, payload = struct.unpack(
  126. CAN_FRAME_FORMAT, raw_frame
  127. )
  128. return CanFrame(
  129. arbitration_id=can_id & CAN_EFF_MASK,
  130. is_extended=bool(can_id & CAN_EFF_FLAG),
  131. data=payload[:data_length],
  132. )
  133. def _require_open(self):
  134. """
  135. @description : 获取已打开的SocketCAN套接字
  136. @param : 无
  137. @return : 已绑定的socket对象
  138. """
  139. if self._socket is None:
  140. raise RuntimeError("SocketCAN transport is not open")
  141. return self._socket
  142. def __enter__(self):
  143. """
  144. @description : 进入上下文管理器并打开SocketCAN套接字
  145. @param : 无
  146. @return : 当前SocketCanTransport实例
  147. """
  148. return self.open()
  149. def __exit__(self, exception_type, exception, traceback):
  150. """
  151. @description : 退出上下文管理器并关闭SocketCAN套接字
  152. @param exception_type: 上下文内异常类型或None
  153. @param exception : 上下文内异常对象或None
  154. @param traceback : 上下文内异常堆栈或None
  155. @return : false表示不抑制上下文内异常
  156. """
  157. self.close()
  158. return False