返回教程正文

配套源码

zdt_x57s_driver.py

can-gateway/zdt_x57s_driver.py
zdt-x57s-can-test:封装单电机 Brick 并组合验证四台电机can-gateway/zdt_x57s_driver.py
Python155 行
  1. """
  2. @description : 基于SocketCAN封装单台ZDT X57S FW_Emm电机控制API
  3. @param : 无
  4. @return : 无
  5. """
  6. import time
  7. from zdt_x57s_protocol import (
  8. ZdtProtocolError,
  9. arbitration_id,
  10. build_enable_command,
  11. build_speed_command,
  12. build_speed_query,
  13. build_stop_command,
  14. parse_ack,
  15. parse_speed_reply,
  16. validate_motor_id,
  17. )
  18. class ZdtReplyTimeout(TimeoutError):
  19. """
  20. @description : 表示等待ZDT电机CAN应答超时
  21. @param message : 超时说明
  22. @return : ZdtReplyTimeout实例
  23. """
  24. def __init__(self, message):
  25. """
  26. @description : 初始化ZDT应答超时异常
  27. @param message : 超时说明
  28. @return : 无
  29. """
  30. super().__init__(message)
  31. class ZdtX57S:
  32. """
  33. @description : 控制一台使用FW_Emm和固定CAN协议的ZDT X57S电机
  34. @param transport : 已打开的SocketCanTransport实例
  35. @param motor_id : 电机地址
  36. @param reply_timeout_s: 应答超时时间,单位秒
  37. @return : ZdtX57S实例
  38. """
  39. def __init__(self, transport, motor_id, reply_timeout_s=0.5):
  40. """
  41. @description : 初始化电机驱动参数
  42. @param transport : 已打开的SocketCanTransport实例
  43. @param motor_id : 电机地址
  44. @param reply_timeout_s: 应答超时时间,单位秒
  45. @return : 无
  46. """
  47. if reply_timeout_s <= 0:
  48. raise ValueError("reply_timeout_s must be greater than zero")
  49. self._transport = transport
  50. self._motor_id = validate_motor_id(motor_id)
  51. self._frame_id = arbitration_id(self._motor_id)
  52. self._reply_timeout_s = float(reply_timeout_s)
  53. def enable(self, enabled=True, synchronized=False):
  54. """
  55. @description : 使能或失能当前电机
  56. @param enabled : true使能; false失能
  57. @param synchronized : true等待同步启动; false立即执行
  58. @return : 成功返回True
  59. """
  60. return self._send_and_wait_ack(
  61. build_enable_command(enabled, synchronized)
  62. )
  63. def set_speed(self, rpm, acceleration_level=0, synchronized=False):
  64. """
  65. @description : 使用FW_Emm速度模式设置目标转速
  66. @param rpm : 带符号目标转速,单位整数RPM
  67. @param acceleration_level: 加减速档位,范围0至255
  68. @param synchronized : true等待同步启动; false立即执行
  69. @return : 成功返回True
  70. """
  71. return self._send_and_wait_ack(
  72. build_speed_command(rpm, acceleration_level, synchronized)
  73. )
  74. def read_speed(self):
  75. """
  76. @description : 查询编码器反馈实时转速
  77. @param : 无
  78. @return : 带符号实时转速,单位整数RPM
  79. """
  80. command = build_speed_query()
  81. self._transport.clear_receive_queue()
  82. self._transport.send(self._frame_id, command, is_extended=True)
  83. deadline = time.monotonic() + self._reply_timeout_s
  84. while True:
  85. frame = self._receive_matching_frame(deadline)
  86. if frame is None:
  87. raise ZdtReplyTimeout(
  88. f"motor {self._motor_id} speed reply timed out"
  89. )
  90. if not frame.data:
  91. continue
  92. if frame.data[0] == 0x35:
  93. return parse_speed_reply(frame.data)
  94. if len(frame.data) >= 2 and frame.data[:2] == b"\x00\xEE":
  95. raise ZdtProtocolError("motor returned a command error")
  96. def stop(self, synchronized=False):
  97. """
  98. @description : 立即停止当前电机
  99. @param synchronized : true等待同步启动; false立即执行
  100. @return : 成功返回True
  101. """
  102. return self._send_and_wait_ack(build_stop_command(synchronized))
  103. def _send_and_wait_ack(self, command):
  104. """
  105. @description : 发送控制命令并等待相同功能码的确认应答
  106. @param command : 完整ZDT命令数据
  107. @return : 应答校验成功返回True
  108. """
  109. payload = bytes(command)
  110. self._transport.clear_receive_queue()
  111. self._transport.send(self._frame_id, payload, is_extended=True)
  112. deadline = time.monotonic() + self._reply_timeout_s
  113. while True:
  114. frame = self._receive_matching_frame(deadline)
  115. if frame is None:
  116. raise ZdtReplyTimeout(
  117. f"motor {self._motor_id} command 0x{payload[0]:02X} "
  118. "reply timed out"
  119. )
  120. if not frame.data:
  121. continue
  122. if len(frame.data) >= 2 and frame.data[:2] == b"\x00\xEE":
  123. raise ZdtProtocolError("motor returned a command error")
  124. if frame.data[0] == payload[0]:
  125. return parse_ack(frame.data, payload[0])
  126. def _receive_matching_frame(self, deadline):
  127. """
  128. @description : 在截止时间前读取当前电机的29位扩展帧
  129. @param deadline : time.monotonic生成的绝对截止时间
  130. @return : 匹配时返回CanFrame; 超时返回None
  131. """
  132. while True:
  133. remaining_s = deadline - time.monotonic()
  134. if remaining_s <= 0:
  135. return None
  136. frame = self._transport.receive(remaining_s)
  137. if frame is None:
  138. return None
  139. if frame.is_extended and frame.arbitration_id == self._frame_id:
  140. return frame