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.

220 lines
8.7 KiB

2 years ago
  1. # Example 13
  2. In this example, we will be utilizing the [move_base ROS node](http://wiki.ros.org/move_base), a component of the [ROS navigation stack](http://wiki.ros.org/navigation?distro=melodic) to send base goals to the Stretch robot.
  3. ## Build a map
  4. First, begin by building a map of the space the robot will be navigating in. If you need a refresher on how to do this, then check out the [Navigation Stack tutorial](navigation_stack.md).
  5. ## Getting Started
  6. Next, with your created map, we can navigate the robot around the mapped space. Run:
  7. ```bash
  8. roslaunch stretch_navigation navigation.launch map_yaml:=${HELLO_FLEET_PATH}/maps/<map_name>.yaml
  9. ```
  10. Where `${HELLO_FLEET_PATH}` is the path of the `<map_name>.yaml` file.
  11. **IMPORTANT NOTE:** It's likely that the robot's location in the map does not match the robot's location in the real space. In the top bar of Rviz, use 2D Pose Estimate to lay an arrow down roughly where the robot is located in the real space. Below is a gif for reference.
  12. <p align="center">
  13. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/main/images/2D_pose_estimate.gif"/>
  14. </p>
  15. Now we are going to use a node to send a a move base goal half a meter in front of the map's origin. run the following command to execute the [navigation.py](https://github.com/hello-robot/stretch_tutorials/blob/main/src/navigation.py) node.
  16. ```bash
  17. # Terminal 2
  18. cd catkin_ws/src/stretch_tutorials/src/
  19. python navigation.py
  20. ```
  21. ### The Code
  22. ```python
  23. #!/usr/bin/env python
  24. import rospy
  25. import actionlib
  26. import sys
  27. from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
  28. from geometry_msgs.msg import Quaternion
  29. from tf import transformations
  30. class StretchNavigation:
  31. """
  32. A simple encapsulation of the navigation stack for a Stretch robot.
  33. """
  34. def __init__(self):
  35. """
  36. Create an instance of the simple navigation interface.
  37. :param self: The self reference.
  38. """
  39. self.client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
  40. self.client.wait_for_server()
  41. rospy.loginfo('{0}: Made contact with move_base server'.format(self.__class__.__name__))
  42. self.goal = MoveBaseGoal()
  43. self.goal.target_pose.header.frame_id = 'map'
  44. self.goal.target_pose.header.stamp = rospy.Time()
  45. self.goal.target_pose.pose.position.x = 0.0
  46. self.goal.target_pose.pose.position.y = 0.0
  47. self.goal.target_pose.pose.position.z = 0.0
  48. self.goal.target_pose.pose.orientation.x = 0.0
  49. self.goal.target_pose.pose.orientation.y = 0.0
  50. self.goal.target_pose.pose.orientation.z = 0.0
  51. self.goal.target_pose.pose.orientation.w = 1.0
  52. def get_quaternion(self,theta):
  53. """
  54. A function to build Quaternians from Euler angles. Since the Stretch only
  55. rotates around z, we can zero out the other angles.
  56. :param theta: The angle (radians) the robot makes with the x-axis.
  57. """
  58. return Quaternion(*transformations.quaternion_from_euler(0.0, 0.0, theta))
  59. def go_to(self, x, y, theta):
  60. """
  61. Drive the robot to a particular pose on the map. The Stretch only needs
  62. (x, y) coordinates and a heading.
  63. :param x: x coordinate in the map frame.
  64. :param y: y coordinate in the map frame.
  65. :param theta: heading (angle with the x-axis in the map frame)
  66. """
  67. rospy.loginfo('{0}: Heading for ({1}, {2}) at {3} radians'.format(self.__class__.__name__,
  68. x, y, theta))
  69. self.goal.target_pose.pose.position.x = x
  70. self.goal.target_pose.pose.position.y = y
  71. self.goal.target_pose.pose.orientation = self.get_quaternion(theta)
  72. self.client.send_goal(self.goal, done_cb=self.done_callback)
  73. self.client.wait_for_result()
  74. def done_callback(self, status, result):
  75. """
  76. The done_callback function will be called when the joint action is complete.
  77. :param self: The self reference.
  78. :param status: status attribute from MoveBaseActionResult message.
  79. :param result: result attribute from MoveBaseActionResult message.
  80. """
  81. if status == actionlib.GoalStatus.SUCCEEDED:
  82. rospy.loginfo('{0}: SUCCEEDED in reaching the goal.'.format(self.__class__.__name__))
  83. else:
  84. rospy.loginfo('{0}: FAILED in reaching the goal.'.format(self.__class__.__name__))
  85. if __name__ == '__main__':
  86. rospy.init_node('navigation', argv=sys.argv)
  87. nav = StretchNavigation()
  88. nav.go_to(0.5, 0.0, 0.0)
  89. ```
  90. ### The Code Explained
  91. Now let's break the code down.
  92. ```python
  93. #!/usr/bin/env python
  94. ```
  95. Every Python ROS [Node](http://wiki.ros.org/Nodes) will have this declaration at the top. The first line makes sure your script is executed as a Python script.
  96. ```python
  97. import rospy
  98. import actionlib
  99. import sys
  100. from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
  101. from geometry_msgs.msg import Quaternion
  102. from tf import transformations
  103. ```
  104. You need to import `rospy` if you are writing a ROS [Node](http://wiki.ros.org/Nodes).
  105. ```python
  106. self.client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
  107. self.client.wait_for_server()
  108. rospy.loginfo('{0}: Made contact with move_base server'.format(self.__class__.__name__))
  109. ```
  110. Set up a client for the navigation action. On the Stretch, this is called `move_base`, and has type `MoveBaseAction`. Once we make the client, we wait for the server to be ready.
  111. ```python
  112. self.goal = MoveBaseGoal()
  113. self.goal.target_pose.header.frame_id = 'map'
  114. self.goal.target_pose.header.stamp = rospy.Time()
  115. ```
  116. Make a goal for the action. Specify the coordinate frame that we want, in this instance the *map*. Then we set the time to be now.
  117. ```python
  118. self.goal.target_pose.pose.position.x = 0.0
  119. self.goal.target_pose.pose.position.y = 0.0
  120. self.goal.target_pose.pose.position.z = 0.0
  121. self.goal.target_pose.pose.orientation.x = 0.0
  122. self.goal.target_pose.pose.orientation.y = 0.0
  123. self.goal.target_pose.pose.orientation.z = 0.0
  124. self.goal.target_pose.pose.orientation.w = 1.0
  125. ```
  126. Initialize a position in the coordinate frame.
  127. ```python
  128. def get_quaternion(self,theta):
  129. """
  130. A function to build Quaternians from Euler angles. Since the Stretch only
  131. rotates around z, we can zero out the other angles.
  132. :param theta: The angle (radians) the robot makes with the x-axis.
  133. """
  134. return Quaternion(*transformations.quaternion_from_euler(0.0, 0.0, theta))
  135. ```
  136. A function that transforms Euler angles to quaternions and returns those values.
  137. ```python
  138. def go_to(self, x, y, theta, wait=False):
  139. """
  140. Drive the robot to a particular pose on the map. The Stretch only needs
  141. (x, y) coordinates and a heading.
  142. :param x: x coordinate in the map frame.
  143. :param y: y coordinate in the map frame.
  144. :param theta: heading (angle with the x-axis in the map frame)
  145. """
  146. rospy.loginfo('{0}: Heading for ({1}, {2}) at {3} radians'.format(self.__class__.__name__,
  147. x, y, theta))
  148. ```
  149. The `go_to()` function takes in the 3 arguments, the x and y coordinates in the *map* frame, and the heading.
  150. ```python
  151. self.goal.target_pose.pose.position.x = x
  152. self.goal.target_pose.pose.position.y = y
  153. self.goal.target_pose.pose.orientation = self.get_quaternion(theta)
  154. ```
  155. The `MoveBaseGoal()` data structure has three goal positions (in meters), along each of the axes. For Stretch, it will only pay attention to the x and y coordinates, since it can't move in the z direction.
  156. ```python
  157. self.client.send_goal(self.goal, done_cb=self.done_callback)
  158. self.client.wait_for_result()
  159. ```
  160. Send the goal and include the `done_callback()` function in one of the arguments in `send_goal()`.
  161. ```python
  162. def done_callback(self, status, result):
  163. """
  164. The done_callback function will be called when the joint action is complete.
  165. :param self: The self reference.
  166. :param status: status attribute from MoveBaseActionResult message.
  167. :param result: result attribute from MoveBaseActionResult message.
  168. """
  169. if status == actionlib.GoalStatus.SUCCEEDED:
  170. rospy.loginfo('{0}: SUCCEEDED in reaching the goal.'.format(self.__class__.__name__))
  171. else:
  172. rospy.loginfo('{0}: FAILED in reaching the goal.'.format(self.__class__.__name__))
  173. ```
  174. Conditional statement to print whether the goal status in the `MoveBaseActionResult` succeeded or failed.
  175. ```python
  176. rospy.init_node('navigation', argv=sys.argv)
  177. nav = StretchNavigation()
  178. ```
  179. The next line, `rospy.init_node(NAME, ...)`, is very important as it tells rospy the name of your node -- until rospy has this information, it cannot start communicating with the ROS Master. **NOTE:** the name must be a base name, i.e. it cannot contain any slashes "/".
  180. Declare the `StretchNavigation` object.
  181. ```python
  182. nav.go_to(0.5, 0.0, 0.0)
  183. ```
  184. Send a move base goal half a meter in front of the map's origin.