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

318 lines
12 KiB

2 years ago
2 years ago
1 year ago
2 years ago
1 year ago
2 years ago
2 years ago
1 year ago
2 years ago
1 year ago
2 years ago
2 years ago
1 year ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
1 year ago
2 years ago
1 year ago
2 years ago
2 years ago
1 year ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. # Tutorial: Robot Motion
  2. As we've seen in previous tutorials, commanding robot motion is simple and straightforward. For example, the incremental motion of the arm can be commanded by:
  3. ```python linenums="1"
  4. import stretch_body.robot
  5. import time
  6. robot=stretch_body.robot.Robot()
  7. robot.startup()
  8. robot.arm.move_by(0.1)
  9. robot.push_command()
  10. time.sleep(2.0)
  11. robot.stop()
  12. ```
  13. The absolute motion can be commanded by:
  14. ```python linenums="1"
  15. import stretch_body.robot
  16. import time
  17. robot=stretch_body.robot.Robot()
  18. robot.startup()
  19. robot.arm.move_to(0.1)
  20. robot.push_command()
  21. time.sleep(2.0)
  22. robot.stop()
  23. ```
  24. ## Waiting on Motion
  25. In the above examples, we executed a `time.sleep()` after `robot.push_command()`. This allows the joint time to complete its motion. As an alternative, we can use the `wait_until_at_setpoint()` method that polls the joint position versus the target position. We can also interrupt a motion by sending a new motion command at any time. For example, try the following script:
  26. ```python linenums="1"
  27. import stretch_body.robot
  28. import time
  29. robot=stretch_body.robot.Robot()
  30. robot.startup()
  31. #Move to full retraction
  32. robot.arm.move_to(0.0)
  33. robot.push_command()
  34. robot.arm.wait_until_at_setpoint()
  35. #Move to full extension
  36. robot.arm.move_to(0.5)
  37. robot.push_command()
  38. #Interrupt motion midway and retract again
  39. time.sleep(2.0)
  40. robot.arm.move_to(0.0)
  41. robot.arm.wait_until_at_setpoint()
  42. robot.stop()
  43. ```
  44. You will see the arm fully retract, begin to extend, and then fully retract again.
  45. ## Motion Profiles
  46. All joints support [trapezoidal motion profile](https://www.motioncontroltips.com/what-is-a-motion-profile/) generation. Other types of controllers are available (splined trajectory, PID, velocity, etc) but they are not covered here. The trapezoidal motion controllers require three values:
  47. * x: target position of joint
  48. * v: maximum velocity of motion
  49. * a: acceleration of motion
  50. We provide 'defaults' for the velocity and acceleration settings, as well as 'fast', and 'slow' settings. These values have been tuned to be appropriate for the safe movement of the robot. These values can be queried using the `stretch_params.py` tool:
  51. ```{.bash .shell-prompt}
  52. stretch_params.py | grep arm | grep motion | grep default
  53. ```
  54. ```{.bash .no-copy}
  55. stretch_body.robot_params.nominal_params param.arm.motion.fast.accel_m 0.14
  56. stretch_body.robot_params.nominal_params param.arm.motion.fast.vel_m 0.14
  57. ```
  58. We see that the arm motion in 'default' mode will move with a velocity of 0.14 m/s and an acceleration of 0.14 m/s^2.
  59. The `move_by` and `move_to` commands support optional motion profile parameters. For example, for a fast motion:
  60. ```python
  61. v = robot.arm.params['motion']['fast']['vel_m']
  62. a = robot.arm.params['motion']['fast']['accel_m']
  63. robot.arm.move_by(x_m=0.1, v_m=v, a_m=a)
  64. robot.push_command()
  65. ```
  66. The motion will use the 'default' motion profile settings if no values are specified.
  67. ## Motion Limits
  68. All joints obey motion limits which are specified in the robot parameters.
  69. ```{.bash .shell-prompt}
  70. stretch_params.py | grep arm | grep range_m
  71. ```
  72. ```{.bash .no-copy}
  73. stretch_user_params.yaml param.arm.range_m [0.0, 0.515]
  74. ```
  75. These are the mechanical limits of the joints and have been set at the factory to prevent damage to the hardware. It is not recommended to set them to be greater than the factory-specified values. However, they can be further limited if desired by setting soft motion limits:
  76. ```python
  77. import stretch_body.robot
  78. robot=stretch_body.robot.Robot()
  79. robot.startup()
  80. robot.arm.set_soft_motion_limit_min(0.2)
  81. robot.arm.set_soft_motion_limit_max(0.4)
  82. #Will move to position 0.2
  83. robot.arm.move_to(0.0)
  84. robot.push_command()
  85. robot.arm.wait_until_at_setpoint()
  86. #Will move to position 0.4
  87. robot.arm.move_to(0.5)
  88. robot.push_command()
  89. robot.arm.wait_until_at_setpoint()
  90. robot.stop()
  91. ```
  92. ## Controlling Dynamixel Motion
  93. The above examples have focused on the motion of the arm. Like the lift and the base, the arm utilizes Hello Robot's custom stepper motor controller. Control of the Dynamixels of the head and the end-of-arm is very similar to that of the arm, though not identical.
  94. As we see here, the `robot.push_command()` call is not required as the motion begins instantaneously and is not queued. In addition, the Dynamixel servos are managed as a chain of devices, so we must pass in the joint name along with the command.
  95. ```python
  96. import stretch_body.robot
  97. import time
  98. from stretch_body.hello_utils import deg_to_rad
  99. robot=stretch_body.robot.Robot()
  100. robot.startup()
  101. robot.head.move_to('head_pan',0)
  102. robot.head.move_to('head_tilt',0)
  103. time.sleep(3.0)
  104. robot.head.move_to('head_pan',deg_to_rad(90.0))
  105. robot.head.move_to('head_tilt',deg_to_rad(45.0))
  106. time.sleep(3.0)
  107. robot.stop()
  108. ```
  109. Similar to the stepper joints, the Dynamixel joints accept motion profile and motion limit commands. For example, here we restrict the head pan range of motion while executing both a fast and slow move:
  110. ```python
  111. import stretch_body.robot
  112. import time
  113. robot=stretch_body.robot.Robot()
  114. robot.startup()
  115. #Limit range of motion of head_pan
  116. robot.head.get_motor('head_pan').set_soft_motion_limit_min(deg_to_rad(-45.0))
  117. robot.head.get_motor('head_pan').set_soft_motion_limit_max(deg_to_rad(45.0))
  118. #Do a fast motion
  119. v = robot.params['head_pan']['motion']['fast']['vel']
  120. a = robot.params['head_pan']['motion']['fast']['accel']
  121. robot.head.move_to('head_pan',deg_to_rad(-90.0),v_r=v, a_r=a)
  122. time.sleep(3.0)
  123. #Do a slow motion
  124. v = robot.params['head_pan']['motion']['slow']['vel']
  125. a = robot.params['head_pan']['motion']['slow']['accel']
  126. robot.head.move_to('head_pan',deg_to_rad(90.0),v_r=v, a_r=a)
  127. time.sleep(3.0)
  128. robot.stop()
  129. ```
  130. ## Base Velocity Control
  131. The Base also supports a velocity control mode which can be useful with navigation planners. The Base controllers will automatically switch between velocity and position control. For example:
  132. ```python
  133. robot.base.translate_by(x_m=0.5)
  134. robot.push_command()
  135. time.sleep(4.0) #wait
  136. robot.base.set_rotational_velocity(v_r=0.1) #switch to velocity controller
  137. robot.push_command()
  138. time.sleep(4.0) #wait
  139. robot.base.set_rotational_velocity(v_r=0.0) #stop motion
  140. robot.push_command()
  141. ```
  142. !!! warning
  143. As shown, care should be taken to set commanded velocities to zero on exit to avoid runaway.
  144. ## Advanced Topics
  145. ### Stepper Control Modes
  146. Most users will control robot motion using the `move_to` and `move_by` commands as described above. However, there are numerous other low-level controller modes available. While this is a topic for advanced users, it is worth noting that each joint has a default safety mode and a default position control mode. These are:
  147. | Joint | Default Safety Mode | Default Position Control Mode |
  148. | --------------- | --------------------------- | ----------------------------- |
  149. | left_wheel | Freewheel | Trapezoidal position control |
  150. | right_wheel | Freewheel | Trapezoidal position control |
  151. | lift | Gravity compensated 'float' | Trapezoidal position control |
  152. | arm | Freewheel | Trapezoidal position control |
  153. | head_pan | Torque disabled | Trapezoidal position control |
  154. | head_tilt | Torque disabled | Trapezoidal position control |
  155. | wrist_yaw | Torque disabled | Trapezoidal position control |
  156. | stretch_gripper | Torque disabled | Trapezoidal position control |
  157. Each joint remains in its `Safety Mode` when no program is running. When the `<device>.startup()` function is called, the joint controller transitions from `Safety Mode` to its `Default Position Control Mode`. It is then placed back in `Safety Mode` when `<device>.stop()` is called.
  158. ### Motion Runstop
  159. Runstop activation will cause the Base, Arm, and Lift to switch to `Safety Mode` and subsequent motion commands will be ignored. The motion commands will resume smoothly when the Runstop is deactivated. This is usually done via the Runstop button. However, it can also be done via the Pimu interface. For example:
  160. ```python
  161. import stretch_body.robot
  162. robot=stretch_body.robot.Robot()
  163. robot.startup()
  164. #Move to full retraction
  165. robot.arm.move_to(0.0)
  166. robot.push_command()
  167. robot.arm.wait_until_at_setpoint()
  168. #Move to full extension
  169. robot.arm.move_to(0.5)
  170. robot.push_command()
  171. #Runstop motion midway through the motion
  172. input('Hit enter to runstop motion')
  173. robot.pimu.runstop_event_trigger()
  174. robot.push_command()
  175. input('Hit enter to restart motion')
  176. robot.pimu.runstop_event_reset()
  177. robot.push_command()
  178. robot.arm.move_to(0.5)
  179. robot.push_command()
  180. robot.arm.wait_until_at_setpoint()
  181. robot.stop()
  182. ```
  183. ### Guarded Motion
  184. The Arm, Lift, and Base support a guarded motion function. It will automatically transition the actuator from Control mode to Safety mode when the exerted motor torque exceeds a threshold.
  185. This functionality is most useful for the Lift and the Arm. It allows these joints to safely stop upon contact. It can be used to:
  186. * Safely stop when contacting an actuator hard stop
  187. * Safely stop when making unexpected contact with the environment or a person
  188. * Make a guarded motion where the robot reaches a surface and then stops
  189. For more information on guarded motion, see the [Contact Models Tutorial](./tutorial_contact_models.md)
  190. ### Synchronized Motion
  191. The Arm, Lift, and Base actuators have a hardware synchronization mechanism. This allows for stepper controller commands to be time synchronized across joints. This behavior can be disabled via the user YAML. By default the settings are:
  192. ```{.bash .shell-prompt}
  193. stretch_params.py | grep enable_sync_mode
  194. ```
  195. ```{.bash .no-copy}
  196. stretch_body.robot_params.nominal_params param.hello-motor-arm.gains.enable_sync_mode 1
  197. stretch_body.robot_params.nominal_params param.hello-motor-left-wheel.gains.enable_sync_mode 1
  198. stretch_body.robot_params.nominal_params param.hello-motor-lift.gains.enable_sync_mode 1
  199. stretch_body.robot_params.nominal_params param.hello-motor-right-wheel.gains.enable_sync_mode 1
  200. ```
  201. ### Motion Status
  202. It can be useful to poll the status of a joint during a motion to modify the robot's behavior, etc. The useful status values include:
  203. ```python
  204. robot.arm.status['pos'] #Joint position
  205. robot.arm.status['vel'] #Joint velocity
  206. robot.arm.motor.status['effort_pct'] #Joint effort (-100 to 100) (derived from motor current)
  207. robot.arm.motor.status['near_pos_setpoint'] #Is sensed position near commanded position
  208. robot.arm.motor.status['near_vel_setpoint'] #Is sensed velocity near commanded velocity
  209. robot.arm.motor.status['is_moving'] #Is the joint in motion
  210. robot.arm.motor.status['in_guarded_event'] #Has a guarded event occured
  211. robot.arm.motor.status['in_safety_event'] #Has a safety event occured
  212. ```
  213. ### Update Rates
  214. The following update rates apply to Stretch:
  215. | Item | Rate | Notes |
  216. | ----------------------------------------------- | ---- | ------------------------------------------------------------ |
  217. | Status data for Arm, Lift, Base, Wacc, and Pimu | 25Hz | Polled automatically by Robot thread |
  218. | Status data for End of Arm and Head servos | 15Hz | Polled automatically by Robot thread |
  219. | Command data for Arm, Lift, Base, Wacc, Pimu | N/A | Commands are queued and executed upon calling robot.push_command( ) |
  220. | Command data for End of Arm and Head servos | N/A | Commands execute immediately |
  221. !!! note
  222. Motion commands are non-blocking and it is the responsibility of the user code to poll the Robot Status to determine when and if a motion target has been achieved.
  223. !!! info
  224. The Stretch Body interface is not designed to support high bandwidth control applications. The natural dynamics of the robot actuators do not support high bandwidth control, and the USB-based interface limits high-rate communication.
  225. !!! tip
  226. In practice, a Python-based control loop that calls push_command() at 1Hz to 10Hz is sufficiently matched to the robot's natural dynamics.
  227. ------
  228. <div align="center"> All materials are Copyright 2022 by Hello Robot Inc. Hello Robot and Stretch are registered trademarks.</div>