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.

233 lines
8.7 KiB

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
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
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
2 years ago
  1. # Example 13
  2. In this example, we will be utilizing the [move_base package](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 .shell-prompt}
  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. !!! note
  12. It's likely that the robot's location on the map does not match the robot's location in real space. In the top bar of Rviz, use `2D Pose Estimate` to lay an arrow down roughly where the robot is located in real space. Below is a gif for reference.
  13. <p align="center">
  14. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/2D_pose_estimate.gif"/>
  15. </p>
  16. Now we are going to use a node to send a move_base goal half a meter in front of the map's origin. run the following command in a new terminal to execute the [navigation.py](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/navigation.py) node.
  17. ```{.bash .shell-prompt}
  18. cd catkin_ws/src/stretch_tutorials/src/
  19. python3 navigation.py
  20. ```
  21. ### The Code
  22. ```python
  23. #!/usr/bin/env python3
  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 python3
  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 Python3 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` frame. 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.
  180. !!! note
  181. The name must be a base name, i.e. it cannot contain any slashes "/".
  182. Declare the `StretchNavigation` object.
  183. ```python
  184. nav.go_to(0.5, 0.0, 0.0)
  185. ```
  186. Send a move base goal half a meter in front of the map's origin.