You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

dynamixel.py 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. from __future__ import annotations
  2. import math
  3. import os
  4. from dynamixel_sdk import * # Uses Dynamixel SDK library
  5. from dataclasses import dataclass
  6. import enum
  7. import time
  8. class ReadAttribute(enum.Enum):
  9. TEMPERATURE = 146
  10. VOLTAGE = 145
  11. VELOCITY = 128
  12. POSITION = 132
  13. CURRENT = 126
  14. PWM = 124
  15. HARDWARE_ERROR_STATUS = 70
  16. HOMING_OFFSET = 20
  17. BAUDRATE = 8
  18. class OperatingMode(enum.Enum):
  19. VELOCITY = 1
  20. POSITION = 3
  21. CURRENT_CONTROLLED_POSITION = 5
  22. PWM = 16
  23. UNKNOWN = -1
  24. class Dynamixel:
  25. ADDR_TORQUE_ENABLE = 64
  26. ADDR_GOAL_POSITION = 116
  27. ADDR_VELOCITY_LIMIT = 44
  28. ADDR_GOAL_PWM = 100
  29. OPERATING_MODE_ADDR = 11
  30. POSITION_I = 82
  31. POSITION_P = 84
  32. ADDR_ID = 7
  33. @dataclass
  34. class Config:
  35. def instantiate(self):
  36. return Dynamixel(self)
  37. baudrate: int = 57600
  38. protocol_version: float = 2.0
  39. device_name: str = "" # /dev/tty.usbserial-1120'
  40. dynamixel_id: int = 1
  41. def __init__(self, config: Config):
  42. self.config = config
  43. self.connect()
  44. def connect(self):
  45. if self.config.device_name == "":
  46. for port_name in os.listdir("/dev"):
  47. if "ttyUSB" in port_name or "ttyACM" in port_name:
  48. self.config.device_name = "/dev/" + port_name
  49. print(f"using device {self.config.device_name}")
  50. self.portHandler = PortHandler(self.config.device_name)
  51. # self.portHandler.LA
  52. self.packetHandler = PacketHandler(self.config.protocol_version)
  53. if not self.portHandler.openPort():
  54. raise Exception(f"Failed to open port {self.config.device_name}")
  55. if not self.portHandler.setBaudRate(self.config.baudrate):
  56. raise Exception(f"failed to set baudrate to {self.config.baudrate}")
  57. # self.operating_mode = OperatingMode.UNKNOWN
  58. # self.torque_enabled = False
  59. # self._disable_torque()
  60. self.operating_modes = [None for _ in range(32)]
  61. self.torque_enabled = [None for _ in range(32)]
  62. return True
  63. def disconnect(self):
  64. self.portHandler.closePort()
  65. def set_goal_position(self, motor_id, goal_position):
  66. # if self.operating_modes[motor_id] is not OperatingMode.POSITION:
  67. # self._disable_torque(motor_id)
  68. # self.set_operating_mode(motor_id, OperatingMode.POSITION)
  69. # if not self.torque_enabled[motor_id]:
  70. # self._enable_torque(motor_id)
  71. # self._enable_torque(motor_id)
  72. dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(
  73. self.portHandler, motor_id, self.ADDR_GOAL_POSITION, goal_position
  74. )
  75. # self._process_response(dxl_comm_result, dxl_error)
  76. # print(f'set position of motor {motor_id} to {goal_position}')
  77. def set_pwm_value(self, motor_id: int, pwm_value, tries=3):
  78. if self.operating_modes[motor_id] is not OperatingMode.PWM:
  79. self._disable_torque(motor_id)
  80. self.set_operating_mode(motor_id, OperatingMode.PWM)
  81. if not self.torque_enabled[motor_id]:
  82. self._enable_torque(motor_id)
  83. # print(f'enabling torque')
  84. dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
  85. self.portHandler, motor_id, self.ADDR_GOAL_PWM, pwm_value
  86. )
  87. # self._process_response(dxl_comm_result, dxl_error)
  88. # print(f'set pwm of motor {motor_id} to {pwm_value}')
  89. if dxl_comm_result != COMM_SUCCESS:
  90. if tries <= 1:
  91. raise ConnectionError(
  92. f"dxl_comm_result: {self.packetHandler.getTxRxResult(dxl_comm_result)}"
  93. )
  94. else:
  95. print(
  96. f"dynamixel pwm setting failure trying again with {tries - 1} tries"
  97. )
  98. self.set_pwm_value(motor_id, pwm_value, tries=tries - 1)
  99. elif dxl_error != 0:
  100. print(f"dxl error {dxl_error}")
  101. raise ConnectionError(
  102. f"dynamixel error: {self.packetHandler.getTxRxResult(dxl_error)}"
  103. )
  104. def read_temperature(self, motor_id: int):
  105. return self._read_value(motor_id, ReadAttribute.TEMPERATURE, 1)
  106. def read_velocity(self, motor_id: int):
  107. pos = self._read_value(motor_id, ReadAttribute.VELOCITY, 4)
  108. if pos > 2**31:
  109. pos -= 2**32
  110. # print(f'read position {pos} for motor {motor_id}')
  111. return pos
  112. def read_position(self, motor_id: int):
  113. pos = self._read_value(motor_id, ReadAttribute.POSITION, 4)
  114. if pos > 2**31:
  115. pos -= 2**32
  116. # print(f'read position {pos} for motor {motor_id}')
  117. return pos
  118. def read_position_degrees(self, motor_id: int) -> float:
  119. return (self.read_position(motor_id) / 4096) * 360
  120. def read_position_radians(self, motor_id: int) -> float:
  121. return (self.read_position(motor_id) / 4096) * 2 * math.pi
  122. def read_current(self, motor_id: int):
  123. current = self._read_value(motor_id, ReadAttribute.CURRENT, 2)
  124. if current > 2**15:
  125. current -= 2**16
  126. return current
  127. def read_present_pwm(self, motor_id: int):
  128. return self._read_value(motor_id, ReadAttribute.PWM, 2)
  129. def read_hardware_error_status(self, motor_id: int):
  130. return self._read_value(motor_id, ReadAttribute.HARDWARE_ERROR_STATUS, 1)
  131. def set_id(self, old_id, new_id, use_broadcast_id: bool = False):
  132. """
  133. sets the id of the dynamixel servo
  134. @param old_id: current id of the servo
  135. @param new_id: new id
  136. @param use_broadcast_id: set ids of all connected dynamixels if True.
  137. If False, change only servo with self.config.id
  138. @return:
  139. """
  140. if use_broadcast_id:
  141. current_id = 254
  142. else:
  143. current_id = old_id
  144. dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
  145. self.portHandler, current_id, self.ADDR_ID, new_id
  146. )
  147. self._process_response(dxl_comm_result, dxl_error, old_id)
  148. self.config.id = id
  149. def _enable_torque(self, motor_id):
  150. dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
  151. self.portHandler, motor_id, self.ADDR_TORQUE_ENABLE, 1
  152. )
  153. self._process_response(dxl_comm_result, dxl_error, motor_id)
  154. self.torque_enabled[motor_id] = True
  155. def _disable_torque(self, motor_id):
  156. dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
  157. self.portHandler, motor_id, self.ADDR_TORQUE_ENABLE, 0
  158. )
  159. self._process_response(dxl_comm_result, dxl_error, motor_id)
  160. self.torque_enabled[motor_id] = False
  161. def _process_response(self, dxl_comm_result: int, dxl_error: int, motor_id: int):
  162. if dxl_comm_result != COMM_SUCCESS:
  163. raise ConnectionError(
  164. f"dxl_comm_result for motor {motor_id}: {self.packetHandler.getTxRxResult(dxl_comm_result)}"
  165. )
  166. elif dxl_error != 0:
  167. print(f"dxl error {dxl_error}")
  168. raise ConnectionError(
  169. f"dynamixel error for motor {motor_id}: {self.packetHandler.getTxRxResult(dxl_error)}"
  170. )
  171. def set_operating_mode(self, motor_id: int, operating_mode: OperatingMode):
  172. dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
  173. self.portHandler, motor_id, self.OPERATING_MODE_ADDR, operating_mode.value
  174. )
  175. self._process_response(dxl_comm_result, dxl_error, motor_id)
  176. self.operating_modes[motor_id] = operating_mode
  177. def set_pwm_limit(self, motor_id: int, limit: int):
  178. dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
  179. self.portHandler, motor_id, 36, limit
  180. )
  181. self._process_response(dxl_comm_result, dxl_error, motor_id)
  182. def set_velocity_limit(self, motor_id: int, velocity_limit):
  183. dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(
  184. self.portHandler, motor_id, self.ADDR_VELOCITY_LIMIT, velocity_limit
  185. )
  186. self._process_response(dxl_comm_result, dxl_error, motor_id)
  187. def set_P(self, motor_id: int, P: int):
  188. dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
  189. self.portHandler, motor_id, self.POSITION_P, P
  190. )
  191. self._process_response(dxl_comm_result, dxl_error, motor_id)
  192. def set_I(self, motor_id: int, I: int):
  193. dxl_comm_result, dxl_error = self.packetHandler.write2ByteTxRx(
  194. self.portHandler, motor_id, self.POSITION_I, I
  195. )
  196. self._process_response(dxl_comm_result, dxl_error, motor_id)
  197. def read_home_offset(self, motor_id: int):
  198. self._disable_torque(motor_id)
  199. # dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(self.portHandler, motor_id,
  200. # ReadAttribute.HOMING_OFFSET.value, home_position)
  201. home_offset = self._read_value(motor_id, ReadAttribute.HOMING_OFFSET, 4)
  202. # self._process_response(dxl_comm_result, dxl_error)
  203. self._enable_torque(motor_id)
  204. return home_offset
  205. def set_home_offset(self, motor_id: int, home_position: int):
  206. self._disable_torque(motor_id)
  207. dxl_comm_result, dxl_error = self.packetHandler.write4ByteTxRx(
  208. self.portHandler, motor_id, ReadAttribute.HOMING_OFFSET.value, home_position
  209. )
  210. self._process_response(dxl_comm_result, dxl_error, motor_id)
  211. self._enable_torque(motor_id)
  212. def set_baudrate(self, motor_id: int, baudrate):
  213. # translate baudrate into dynamixel baudrate setting id
  214. if baudrate == 57600:
  215. baudrate_id = 1
  216. elif baudrate == 1_000_000:
  217. baudrate_id = 3
  218. elif baudrate == 2_000_000:
  219. baudrate_id = 4
  220. elif baudrate == 3_000_000:
  221. baudrate_id = 5
  222. elif baudrate == 4_000_000:
  223. baudrate_id = 6
  224. else:
  225. raise Exception("baudrate not implemented")
  226. self._disable_torque(motor_id)
  227. dxl_comm_result, dxl_error = self.packetHandler.write1ByteTxRx(
  228. self.portHandler, motor_id, ReadAttribute.BAUDRATE.value, baudrate_id
  229. )
  230. self._process_response(dxl_comm_result, dxl_error, motor_id)
  231. def _read_value(self, motor_id, attribute: ReadAttribute, num_bytes: int, tries=10):
  232. try:
  233. if num_bytes == 1:
  234. value, dxl_comm_result, dxl_error = self.packetHandler.read1ByteTxRx(
  235. self.portHandler, motor_id, attribute.value
  236. )
  237. elif num_bytes == 2:
  238. value, dxl_comm_result, dxl_error = self.packetHandler.read2ByteTxRx(
  239. self.portHandler, motor_id, attribute.value
  240. )
  241. elif num_bytes == 4:
  242. value, dxl_comm_result, dxl_error = self.packetHandler.read4ByteTxRx(
  243. self.portHandler, motor_id, attribute.value
  244. )
  245. except Exception:
  246. if tries == 0:
  247. raise Exception
  248. else:
  249. return self._read_value(motor_id, attribute, num_bytes, tries=tries - 1)
  250. if dxl_comm_result != COMM_SUCCESS:
  251. if tries <= 1:
  252. # print("%s" % self.packetHandler.getTxRxResult(dxl_comm_result))
  253. raise ConnectionError(
  254. f"dxl_comm_result {dxl_comm_result} for servo {motor_id} value {value}"
  255. )
  256. else:
  257. print(
  258. f"dynamixel read failure for servo {motor_id} trying again with {tries - 1} tries"
  259. )
  260. time.sleep(0.02)
  261. return self._read_value(motor_id, attribute, num_bytes, tries=tries - 1)
  262. elif (
  263. dxl_error != 0
  264. ): # # print("%s" % self.packetHandler.getRxPacketError(dxl_error))
  265. # raise ConnectionError(f'dxl_error {dxl_error} binary ' + "{0:b}".format(37))
  266. if tries == 0 and dxl_error != 128:
  267. raise Exception(
  268. f"Failed to read value from motor {motor_id} error is {dxl_error}"
  269. )
  270. else:
  271. return self._read_value(motor_id, attribute, num_bytes, tries=tries - 1)
  272. return value
  273. def set_home_position(self, motor_id: int):
  274. print(f"setting home position for motor {motor_id}")
  275. self.set_home_offset(motor_id, 0)
  276. current_position = self.read_position(motor_id)
  277. print(f"position before {current_position}")
  278. self.set_home_offset(motor_id, -current_position)
  279. # dynamixel.set_home_offset(motor_id, -4096)
  280. # dynamixel.set_home_offset(motor_id, -4294964109)
  281. current_position = self.read_position(motor_id)
  282. # print(f'signed position {current_position - 2** 32}')
  283. print(f"position after {current_position}")