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.

410 lines
17 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
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 12
  2. For this example, we will send follow joint trajectory commands for the head camera to search and locate an ArUco tag. In this instance, a Stretch robot will try to locate the docking station's ArUco tag.
  3. ## Modifying Stretch Marker Dictionary YAML File
  4. When defining the ArUco markers on Stretch, hello robot utilizes a YAML file, [stretch_marker_dict.yaml](https://github.com/hello-robot/stretch_ros/blob/master/stretch_core/config/stretch_marker_dict.yaml), that holds the information about the markers. A further breakdown of the YAML file can be found in our [Aruco Marker Detection](aruco_marker_detection.md) tutorial.
  5. Below is what needs to be included in the [stretch_marker_dict.yaml](https://github.com/hello-robot/stretch_ros/blob/master/stretch_core/config/stretch_marker_dict.yaml) file so the [detect_aruco_markers](https://github.com/hello-robot/stretch_ros/blob/master/stretch_core/nodes/detect_aruco_markers) node can find the docking station's ArUco tag.
  6. ```yaml
  7. '245':
  8. 'length_mm': 88.0
  9. 'use_rgb_only': False
  10. 'name': 'docking_station'
  11. 'link': None
  12. ```
  13. ## Getting Started
  14. Begin by running the stretch driver launch file.
  15. ```{.bash .shell-prompt}
  16. roslaunch stretch_core stretch_driver.launch
  17. ```
  18. To activate the RealSense camera and publish topics to be visualized, run the following launch file in a new terminal.
  19. ```{.bash .shell-prompt}
  20. roslaunch stretch_core d435i_high_resolution.launch
  21. ```
  22. Next, run the stretch ArUco launch file which will bring up the [detect_aruco_markers](https://github.com/hello-robot/stretch_ros/blob/master/stretch_core/nodes/detect_aruco_markers) node. In a new terminal, execute:
  23. ```{.bash .shell-prompt}
  24. roslaunch stretch_core stretch_aruco.launch
  25. ```
  26. Within this tutorial package, there is an [RViz config file](https://github.com/hello-robot/stretch_tutorials/blob/noetic/rviz/aruco_detector_example.rviz) with the topics for the transform frames in the Display tree. You can visualize these topics and the robot model by running the command below in a new terminal.
  27. ```{.bash .shell-prompt}
  28. rosrun rviz rviz -d /home/hello-robot/catkin_ws/src/stretch_tutorials/rviz/aruco_detector_example.rviz
  29. ```
  30. Then run the [aruco_tag_locator.py](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/aruco_tag_locator.py) node. In a new terminal, execute:
  31. ```{.bash .shell-prompt}
  32. cd catkin_ws/src/stretch_tutorials/src/
  33. python3 aruco_tag_locator.py
  34. ```
  35. <p align="center">
  36. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/aruco_locator.gif"/>
  37. </p>
  38. ### The Code
  39. ```python
  40. #! /usr/bin/env python3
  41. import rospy
  42. import time
  43. import tf2_ros
  44. import numpy as np
  45. from math import pi
  46. import hello_helpers.hello_misc as hm
  47. from sensor_msgs.msg import JointState
  48. from control_msgs.msg import FollowJointTrajectoryGoal
  49. from trajectory_msgs.msg import JointTrajectoryPoint
  50. from geometry_msgs.msg import TransformStamped
  51. class LocateArUcoTag(hm.HelloNode):
  52. """
  53. A class that actuates the RealSense camera to find the docking station's
  54. ArUco tag and returns a Transform between the `base_link` and the requested tag.
  55. """
  56. def __init__(self):
  57. """
  58. A function that initializes the subscriber and other needed variables.
  59. :param self: The self reference.
  60. """
  61. hm.HelloNode.__init__(self)
  62. self.joint_states_sub = rospy.Subscriber('/stretch/joint_states', JointState, self.joint_states_callback)
  63. self.transform_pub = rospy.Publisher('ArUco_transform', TransformStamped, queue_size=10)
  64. self.joint_state = None
  65. self.min_pan_position = -4.10
  66. self.max_pan_position = 1.50
  67. self.pan_num_steps = 10
  68. self.pan_step_size = abs(self.min_pan_position - self.max_pan_position)/self.pan_num_steps
  69. self.min_tilt_position = -0.75
  70. self.tilt_num_steps = 3
  71. self.tilt_step_size = pi/16
  72. self.rot_vel = 0.5 # radians per sec
  73. def joint_states_callback(self, msg):
  74. """
  75. A callback function that stores Stretch's joint states.
  76. :param self: The self reference.
  77. :param msg: The JointState message type.
  78. """
  79. self.joint_state = msg
  80. def send_command(self, command):
  81. '''
  82. Handles single joint control commands by constructing a FollowJointTrajectoryGoal
  83. message and sending it to the trajectory_client created in hello_misc.
  84. :param self: The self reference.
  85. :param command: A dictionary message type.
  86. '''
  87. if (self.joint_state is not None) and (command is not None):
  88. joint_name = command['joint']
  89. trajectory_goal = FollowJointTrajectoryGoal()
  90. trajectory_goal.trajectory.joint_names = [joint_name]
  91. point = JointTrajectoryPoint()
  92. if 'delta' in command:
  93. joint_index = self.joint_state.name.index(joint_name)
  94. joint_value = self.joint_state.position[joint_index]
  95. delta = command['delta']
  96. new_value = joint_value + delta
  97. point.positions = [new_value]
  98. elif 'position' in command:
  99. point.positions = [command['position']]
  100. point.velocities = [self.rot_vel]
  101. trajectory_goal.trajectory.points = [point]
  102. trajectory_goal.trajectory.header.stamp = rospy.Time(0.0)
  103. trajectory_goal.trajectory.header.frame_id = 'base_link'
  104. self.trajectory_client.send_goal(trajectory_goal)
  105. self.trajectory_client.wait_for_result()
  106. def find_tag(self, tag_name='docking_station'):
  107. """
  108. A function that actuates the camera to search for a defined ArUco tag
  109. marker. Then the function returns the pose
  110. :param self: The self reference.
  111. :param tag_name: A string value of the ArUco marker name.
  112. :returns transform: The docking station's TransformStamped message.
  113. """
  114. pan_command = {'joint': 'joint_head_pan', 'position': self.min_pan_position}
  115. self.send_command(pan_command)
  116. tilt_command = {'joint': 'joint_head_tilt', 'position': self.min_tilt_position}
  117. self.send_command(tilt_command)
  118. for i in range(self.tilt_num_steps):
  119. for j in range(self.pan_num_steps):
  120. pan_command = {'joint': 'joint_head_pan', 'delta': self.pan_step_size}
  121. self.send_command(pan_command)
  122. rospy.sleep(0.2)
  123. try:
  124. transform = self.tf_buffer.lookup_transform('base_link',
  125. tag_name,
  126. rospy.Time())
  127. rospy.loginfo("Found Requested Tag: \n%s", transform)
  128. self.transform_pub.publish(transform)
  129. return transform
  130. except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException):
  131. continue
  132. pan_command = {'joint': 'joint_head_pan', 'position': self.min_pan_position}
  133. self.send_command(pan_command)
  134. tilt_command = {'joint': 'joint_head_tilt', 'delta': self.tilt_step_size}
  135. self.send_command(tilt_command)
  136. rospy.sleep(.25)
  137. rospy.loginfo("The requested tag '%s' was not found", tag_name)
  138. def main(self):
  139. """
  140. Function that initiates the issue_command function.
  141. :param self: The self reference.
  142. """
  143. hm.HelloNode.main(self, 'aruco_tag_locator', 'aruco_tag_locator', wait_for_first_pointcloud=False)
  144. self.static_broadcaster = tf2_ros.StaticTransformBroadcaster()
  145. self.tf_buffer = tf2_ros.Buffer()
  146. self.listener = tf2_ros.TransformListener(self.tf_buffer)
  147. rospy.sleep(1.0)
  148. rospy.loginfo('Searching for docking ArUco tag.')
  149. pose = self.find_tag("docking_station")
  150. if __name__ == '__main__':
  151. try:
  152. node = LocateArUcoTag()
  153. node.main()
  154. except KeyboardInterrupt:
  155. rospy.loginfo('interrupt received, so shutting down')
  156. ```
  157. ### The Code Explained
  158. Now let's break the code down.
  159. ```python
  160. #!/usr/bin/env python3
  161. ```
  162. 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.
  163. ```python
  164. import rospy
  165. import time
  166. import tf2_ros
  167. import numpy as np
  168. from math import pi
  169. import hello_helpers.hello_misc as hm
  170. from sensor_msgs.msg import JointState
  171. from control_msgs.msg import FollowJointTrajectoryGoal
  172. from trajectory_msgs.msg import JointTrajectoryPoint
  173. from geometry_msgs.msg import TransformStamped
  174. ```
  175. You need to import `rospy` if you are writing a ROS [Node](http://wiki.ros.org/Nodes). Import other python modules needed for this node. Import the `FollowJointTrajectoryGoal` from the [control_msgs.msg](http://wiki.ros.org/control_msgs) package to control the Stretch robot. Import `JointTrajectoryPoint` from the [trajectory_msgs](http://wiki.ros.org/trajectory_msgs) package to define robot trajectories. The [hello_helpers](https://github.com/hello-robot/stretch_ros/tree/master/hello_helpers) package consists of a module that provides various Python scripts used across [stretch_ros](https://github.com/hello-robot/stretch_ros). In this instance, we are importing the `hello_misc` script.
  176. ```python
  177. def __init__(self):
  178. """
  179. A function that initializes the subscriber and other needed variables.
  180. :param self: The self reference.
  181. """
  182. hm.HelloNode.__init__(self)
  183. self.joint_states_sub = rospy.Subscriber('/stretch/joint_states', JointState, self.joint_states_callback)
  184. self.transform_pub = rospy.Publisher('ArUco_transform', TransformStamped, queue_size=10)
  185. self.joint_state = None
  186. ```
  187. The `LocateArUcoTag` class inherits the `HelloNode` class from `hm` and is instantiated.
  188. Set up a subscriber with `rospy.Subscriber('/stretch/joint_states', JointState, self.joint_states_callback)`. We're going to subscribe to the topic `stretch/joint_states`, looking for `JointState` messages. When a message comes in, ROS is going to pass it to the function `joint_states_callback()` automatically.
  189. `rospy.Publisher('ArUco_transform', TransformStamped, queue_size=10)` declares that your node is publishing to the `ArUco_transform` topic using the message type `TransformStamped`. The `queue_size` argument limits the amount of queued messages if any subscriber is not receiving them fast enough.
  190. ```python
  191. self.min_pan_position = -4.10
  192. self.max_pan_position = 1.50
  193. self.pan_num_steps = 10
  194. self.pan_step_size = abs(self.min_pan_position - self.max_pan_position)/self.pan_num_steps
  195. ```
  196. Provide the minimum and maximum joint positions for the head pan. These values are needed for sweeping the head to search for the ArUco tag. We also define the number of steps for the sweep, then create the step size for the head pan joint.
  197. ```python
  198. self.min_tilt_position = -0.75
  199. self.tilt_num_steps = 3
  200. self.tilt_step_size = pi/16
  201. ```
  202. Set the minimum position of the tilt joint, the number of steps, and the size of each step.
  203. ```python
  204. self.rot_vel = 0.5 # radians per sec
  205. ```
  206. Define the head actuation rotational velocity.
  207. ```python
  208. def joint_states_callback(self, msg):
  209. """
  210. A callback function that stores Stretch's joint states.
  211. :param self: The self reference.
  212. :param msg: The JointState message type.
  213. """
  214. self.joint_state = msg
  215. ```
  216. The `joint_states_callback()` function stores Stretch's joint states.
  217. ```python
  218. def send_command(self, command):
  219. '''
  220. Handles single joint control commands by constructing a FollowJointTrajectoryGoal
  221. message and sending it to the trajectory_client created in hello_misc.
  222. :param self: The self reference.
  223. :param command: A dictionary message type.
  224. '''
  225. if (self.joint_state is not None) and (command is not None):
  226. joint_name = command['joint']
  227. trajectory_goal = FollowJointTrajectoryGoal()
  228. trajectory_goal.trajectory.joint_names = [joint_name]
  229. point = JointTrajectoryPoint()
  230. ```
  231. Assign `trajectory_goal` as a `FollowJointTrajectoryGoal` message type. Then extract the string value from the `joint` key. Also, assign `point` as a `JointTrajectoryPoint` message type.
  232. ```python
  233. if 'delta' in command:
  234. joint_index = self.joint_state.name.index(joint_name)
  235. joint_value = self.joint_state.position[joint_index]
  236. delta = command['delta']
  237. new_value = joint_value + delta
  238. point.positions = [new_value]
  239. ```
  240. Check to see if `delta` is a key in the command dictionary. Then get the current position of the joint and add the delta as a new position value.
  241. ```python
  242. elif 'position' in command:
  243. point.positions = [command['position']]
  244. ```
  245. Check to see if `position` is a key in the command dictionary. Then extract the position value.
  246. ```python
  247. point.velocities = [self.rot_vel]
  248. trajectory_goal.trajectory.points = [point]
  249. trajectory_goal.trajectory.header.stamp = rospy.Time(0.0)
  250. trajectory_goal.trajectory.header.frame_id = 'base_link'
  251. self.trajectory_client.send_goal(trajectory_goal)
  252. self.trajectory_client.wait_for_result()
  253. ```
  254. Then `trajectory_goal.trajectory.points` is defined by the positions set in `point`. Specify the coordinate frame that we want (*base_link*) and set the time to be now. Make the action call and send the goal. The last line of code waits for the result before it exits the python script.
  255. ```python
  256. def find_tag(self, tag_name='docking_station'):
  257. """
  258. A function that actuates the camera to search for a defined ArUco tag
  259. marker. Then the function returns the pose
  260. :param self: The self reference.
  261. :param tag_name: A string value of the ArUco marker name.
  262. :returns transform: The docking station's TransformStamped message.
  263. """
  264. pan_command = {'joint': 'joint_head_pan', 'position': self.min_pan_position}
  265. self.send_command(pan_command)
  266. tilt_command = {'joint': 'joint_head_tilt', 'position': self.min_tilt_position}
  267. self.send_command(tilt_command)
  268. ```
  269. Create a dictionary to get the head in its initial position for its search and send the commands with the `send_command()` function.
  270. ```python
  271. for i in range(self.tilt_num_steps):
  272. for j in range(self.pan_num_steps):
  273. pan_command = {'joint': 'joint_head_pan', 'delta': self.pan_step_size}
  274. self.send_command(pan_command)
  275. rospy.sleep(0.5)
  276. ```
  277. Utilize a nested for loop to sweep the pan and tilt in increments. Then update the `joint_head_pan` position by the `pan_step_size`. Use `rospy.sleep()` function to give time to the system to do a Transform lookup before the next step.
  278. ```python
  279. try:
  280. transform = self.tf_buffer.lookup_transform('base_link',
  281. tag_name,
  282. rospy.Time())
  283. rospy.loginfo("Found Requested Tag: \n%s", transform)
  284. self.transform_pub.publish(transform)
  285. return transform
  286. except (tf2_ros.LookupException, tf2_ros.ConnectivityException, tf2_ros.ExtrapolationException):
  287. continue
  288. ```
  289. Use a try-except block to look up the transform between the *base_link* and the requested ArUco tag. Then publish and return the `TransformStamped` message.
  290. ```python
  291. pan_command = {'joint': 'joint_head_pan', 'position': self.min_pan_position}
  292. self.send_command(pan_command)
  293. tilt_command = {'joint': 'joint_head_tilt', 'delta': self.tilt_step_size}
  294. self.send_command(tilt_command)
  295. rospy.sleep(.25)
  296. ```
  297. Begin sweep with new tilt angle.
  298. ```python
  299. def main(self):
  300. """
  301. Function that initiates the issue_command function.
  302. :param self: The self reference.
  303. """
  304. hm.HelloNode.main(self, 'aruco_tag_locator', 'aruco_tag_locator', wait_for_first_pointcloud=False)
  305. ```
  306. Create a function, `main()`, to do the setup for the `hm.HelloNode` class and initialize the `aruco_tag_locator` node.
  307. ```python
  308. self.static_broadcaster = tf2_ros.StaticTransformBroadcaster()
  309. self.tf_buffer = tf2_ros.Buffer()
  310. self.listener = tf2_ros.TransformListener(self.tf_buffer)
  311. rospy.sleep(1.0)
  312. ```
  313. Create a StaticTranformBoradcaster Node. Also, start a tf buffer that will store the tf information for a few seconds. Then set up a tf listener, which will subscribe to all of the relevant tf topics, and keep track of the information. Include `rospy.sleep(1.0)` to give the listener some time to accumulate transforms.
  314. ```python
  315. rospy.loginfo('Searching for docking ArUco tag.')
  316. pose = self.find_tag("docking_station")
  317. ```
  318. Notice Stretch is searching for the ArUco tag with a `rospy.loginfo()` function. Then search for the ArUco marker for the docking station.
  319. ```python
  320. if __name__ == '__main__':
  321. try:
  322. node = LocateArUcoTag()
  323. node.main()
  324. except KeyboardInterrupt:
  325. rospy.loginfo('interrupt received, so shutting down')
  326. ```
  327. Declare `LocateArUcoTag` object. Then run the `main()` method.