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.

254 lines
11 KiB

  1. # Introduction to HelloNode
  2. HelloNode is a convenience class for creating a ROS 2 node for Stretch. The most common way to use this class is to extend it. In your extending class, the main funcion would call `HelloNode`'s main function. This would look like:
  3. ```python
  4. import hello_helpers.hello_misc as hm
  5. class MyNode(hm.HelloNode):
  6. def __init__(self):
  7. hm.HelloNode.__init__(self)
  8. def main(self):
  9. hm.HelloNode.main(self, 'my_node', 'my_node', wait_for_first_pointcloud=False)
  10. # my_node's main logic goes here
  11. node = MyNode()
  12. node.main()
  13. ```
  14. There is also a one-liner class method for instantiating a `HelloNode` for easy prototyping. One example where this is handy in sending pose commands from iPython:
  15. ```python
  16. # roslaunch the stretch launch file beforehand
  17. import hello_helpers.hello_misc as hm
  18. temp = hm.HelloNode.quick_create('temp')
  19. temp.move_to_pose({'joint_lift': 0.4})
  20. ```
  21. #### Attributes
  22. ##### `dryrun`
  23. This attribute allows you to control whether the robot actually moves when calling `move_to_pose()`, `home_the_robot()`, `stow_the_robot()`, or other motion methods in this class. When `dryrun` is set to True, these motion methods return immediately. This attribute is helpful when you want to run just the perception/planning part of your node without actually moving the robot. For example, you could replace the following verbose snippet:
  24. ```python
  25. # launch the stretch driver launch file beforehand
  26. import hello_helpers.hello_misc as hm
  27. temp = hm.HelloNode.quick_create('temp')
  28. actually_move = False
  29. [...]
  30. if actually_move:
  31. temp.move_to_pose({'translate_mobile_base': 1.0})
  32. ```
  33. to be more consise:
  34. ```python
  35. # launch the stretch driver launch file beforehand
  36. import hello_helpers.hello_misc as hm
  37. temp = hm.HelloNode.quick_create('temp')
  38. [...]
  39. temp.dryrun = True
  40. temp.move_to_pose({'translate_mobile_base': 1.0})
  41. ```
  42. #### Methods
  43. ##### `move_to_pose(pose, blocking=False, custom_contact_thresholds=False, duration=2.0)`
  44. This method takes in a dictionary that describes a desired pose for the robot and communicates with [stretch_driver](../stretch_core/README.md#stretchdrivernodesstretchdriver) to execute it. The basic format of this dictionary is string/number key/value pairs, where the keys are joint names and the values are desired position goals. For example, `{'joint_lift': 0.5}` would put the lift at 0.5m in its joint range. A full list of command-able joints is published to the `/stretch/joint_states` topic. Used within a node extending `HelloNode`, calling this method would look like:
  45. ```python
  46. self.move_to_pose({'joint_lift': 0.5})
  47. ```
  48. Internally, this dictionary is converted into a [JointTrajectory](https://docs.ros2.org/latest/api/trajectory_msgs/msg/JointTrajectory.html) message that is sent to a [FollowJointTrajectory action](http://docs.ros.org/en/noetic/api/control_msgs/html/action/FollowJointTrajectory.html) server in stretch_driver. This method waits by default for the server to report that the goal has completed executing. However, you can return before the goal has completed by setting the `blocking` argument to False. This can be useful for preempting goals.
  49. When the robot is in `position` mode, if you set `custom_contact_thresholds` to True, this method expects a different format dictionary: string/tuple key/value pairs, where the keys are still joint names, but the values are `(position_goal, effort_threshold)`. The addition of a effort threshold enables you to detect when a joint has made contact with something in the environment, which is useful for manipulation or safe movements. For example, `{'joint_arm': (0.5, 20)}` commands the telescoping arm fully out (the arm is nearly fully extended at 0.5 meters) but with a low enough effort threshold (20% of the arm motor's max effort) that the motor will stop when the end of arm has made contact with something. Again, in a node, this would look like:
  50. ```python
  51. self.move_to_pose({'joint_arm': (0.5, 40)}, custom_contact_thresholds=True)
  52. ```
  53. When the robot is in `trajectory` mode, if you set argument `duration` as `ts`, this method will ensure that the target joint positions are achieved over `ts` seconds. For example, the below would put the lift at 0.5m from its current position in `5.0` seconds:
  54. ```python
  55. self.move_to_pose({'joint_lift': 0.5}, duration=5.0)
  56. ```
  57. ##### `home_the_robot()`
  58. This is a convenience method to interact with the driver's [`/home_the_robot` service](../stretch_core/README.md#home_the_robot-std_srvstrigger).
  59. ##### `stow_the_robot()`
  60. This is a convenience method to interact with the driver's [`/stow_the_robot` service](../stretch_core/README.md#stow_the_robot-std_srvstrigger).
  61. ##### `stop_the_robot()`
  62. This is a convenience method to interact with the driver's [`/stop_the_robot` service](../stretch_core/README.md#stop_the_robot-std_srvstrigger).
  63. ##### `get_tf(from_frame, to_frame)`
  64. Use this method to get the transform ([geometry_msgs/TransformStamped](https://docs.ros2.org/latest/api/geometry_msgs/msg/TransformStamped.html)) between two frames. This method is blocking. For example, this method can do forward kinematics from the base_link to the link between the gripper fingers, link_grasp_center, using:
  65. ```python
  66. # launch the stretch driver launch file beforehand
  67. import hello_helpers.hello_misc as hm
  68. temp = hm.HelloNode.quick_create('temp')
  69. t = temp.get_tf('base_link', 'link_grasp_center')
  70. print(t.transform.translation)
  71. ```
  72. ##### `get_robot_floor_pose_xya(floor_frame='odom')`
  73. Returns the current estimated x, y position and angle of the robot on the floor. This is typically called with respect to the odom frame or the map frame. x and y are in meters and the angle is in radians.
  74. !!! note
  75. To get the robot pose with respect to the odom frame we need to launch stretch_driver along with the broadcast_odom_tf parameter set to True. To do this execute the command:
  76. `ros2 launch stretch_core stretch_driver.launch.py broadcast_odom_tf:=True`
  77. ```python
  78. # launch the stretch driver launch file beforehand
  79. import hello_helpers.hello_misc as hm
  80. temp = hm.HelloNode.quick_create('temp')
  81. t = temp.get_robot_floor_pose_xya(floor_frame='odom')
  82. print(t)
  83. ```
  84. ##### `main(node_name, node_topic_namespace, wait_for_first_pointcloud=True)`
  85. When extending the `HelloNode` class, call this method at the very beginning of your `main()` method. This method handles setting up a few ROS components, including registering the node with the ROS server, creating a TF listener, creating a [FollowJointTrajectory](http://docs.ros.org/en/noetic/api/control_msgs/html/action/FollowJointTrajectory.html) client for the [`move_to_pose()`](#movetoposepose-returnbeforedonefalse-customcontactthresholdsfalse-customfullgoalfalse) method, subscribing to depth camera point cloud topic, and connecting to the quick-stop service.
  86. Since it takes up to 30 seconds for the head camera to start streaming data, the `wait_for_first_pointcloud` argument will get the node to wait until it has seen camera data, which is helpful if your node is processing camera data.
  87. ##### `quick_create(name, wait_for_first_pointcloud=False)`
  88. A class level method for quick testing. This allows you to avoid having to extend `HelloNode` to use it.
  89. ```python
  90. # launch the stretch driver launch file beforehand
  91. import hello_helpers.hello_misc as hm
  92. temp = hm.HelloNode.quick_create('temp')
  93. temp.move_to_pose({'joint_lift': 0.4})
  94. ```
  95. #### Subscribed Topics
  96. ##### /camera/depth/color/points ([sensor_msgs/PointCloud2](https://docs.ros2.org/latest/api/sensor_msgs/msg/PointCloud2.html))
  97. Provides a point cloud as currently seen by the Realsense depth camera in Stretch's head. Accessible from the `self.point_cloud` attribute.
  98. ```python
  99. # launch the stretch driver launch file beforehand
  100. import hello_helpers.hello_misc as hm
  101. temp = hm.HelloNode.quick_create('temp', wait_for_first_pointcloud=True)
  102. print(temp.point_cloud)
  103. ```
  104. ##### /stretch/joint_states ([sensor_msgs/JointState](https://docs.ros2.org/latest/api/sensor_msgs/msg/JointState.html))
  105. Provides the current state of robot joints that includes joint names, positions, velocities, efforts. Accessible from the `self.joint_state` attribute.
  106. ```python
  107. print(temp.joint_state)
  108. ```
  109. ##### /mode ([std_msgs/String](https://docs.ros2.org/latest/api/std_msgs/msg/String.html))
  110. Provides the mode the stretch driver is currently in. Possible values include `position`, `trajectory`, `navigation`, `homing`, `stowing`.
  111. ```python
  112. print(temp.mode)
  113. ```
  114. ##### /tool ([std_msgs/String](https://docs.ros2.org/latest/api/std_msgs/msg/String.html))
  115. Provides the end of arm tool attached to the robot.
  116. ```python
  117. print(temp.tool)
  118. ```
  119. #### Subscribed Services
  120. ##### /stop_the_robot ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html))
  121. Provides a service to quickly stop any motion currently executing on the robot.
  122. ```python
  123. # launch the stretch driver launch file beforehand
  124. from std_srvs.srv import Trigger
  125. import hello_helpers.hello_misc as hm
  126. temp = hm.HelloNode.quick_create('temp')
  127. temp.stop_the_robot_service.call_async(Trigger.Request())
  128. ```
  129. ##### /stow_the_robot ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html))
  130. Provides a service to stow the robot arm.
  131. ```python
  132. # launch the stretch driver launch file beforehand
  133. from std_srvs.srv import Trigger
  134. import hello_helpers.hello_misc as hm
  135. temp = hm.HelloNode.quick_create('temp')
  136. temp.stow_the_robot_service.call_async(Trigger.Request())
  137. ```
  138. ##### /home_the_robot ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html))
  139. Provides a service to home the robot joints.
  140. ```python
  141. # launch the stretch driver launch file beforehand
  142. from std_srvs.srv import Trigger
  143. import hello_helpers.hello_misc as hm
  144. temp = hm.HelloNode.quick_create('temp')
  145. temp.home_the_robot_service.call_async(Trigger.Request())
  146. ```
  147. ##### /switch_to_trajectory_mode ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html))
  148. Provides a service to quickly stop any motion currently executing on the robot.
  149. ```python
  150. # launch the stretch driver launch file beforehand
  151. from std_srvs.srv import Trigger
  152. import hello_helpers.hello_misc as hm
  153. temp = hm.HelloNode.quick_create('temp')
  154. temp.switch_to_trajectory_mode_service.call_async(Trigger.Request())
  155. ```
  156. ##### /switch_to_position_mode ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html))
  157. Provides a service to quickly stop any motion currently executing on the robot.
  158. ```python
  159. # launch the stretch driver launch file beforehand
  160. from std_srvs.srv import Trigger
  161. import hello_helpers.hello_misc as hm
  162. temp = hm.HelloNode.quick_create('temp')
  163. temp.switch_to_position_mode_service.call_async(Trigger.Request())
  164. ```
  165. ##### /switch_to_navigation_mode ([std_srvs/Trigger](https://docs.ros2.org/latest/api/std_srvs/srv/Trigger.html))
  166. Provides a service to quickly stop any motion currently executing on the robot.
  167. ```python
  168. # launch the stretch driver launch file beforehand
  169. from std_srvs.srv import Trigger
  170. import hello_helpers.hello_misc as hm
  171. temp = hm.HelloNode.quick_create('temp')
  172. temp.switch_to_navigation_mode_service.call_async(Trigger.Request())
  173. ```