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.

207 lines
8.3 KiB

  1. ## Example 5
  2. In this example, we will review a Python script that prints out the positions of a selected group of Stretch joints. This script is helpful if you need the joint positions after you teleoperated Stretch with the Xbox controller or physically moved the robot to the desired configuration after hitting the run stop button.
  3. If you are looking for a continuous print of the joint states while Stretch is in action, then you can use the [ros2 topic command-line tool](https://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics.html) shown in the [Internal State of Stretch Tutorial](https://github.com/hello-robot/stretch_tutorials/blob/master/ros2/internal_state_of_stretch.md).
  4. Begin by starting up the stretch driver launch file.
  5. ```{.bash .shell-prompt}
  6. ros2 launch stretch_core stretch_driver.launch.py
  7. ```
  8. You can then hit the run-stop button (you should hear a beep and the LED light in the button blink) and move the robot's joints to a desired configuration. Once you are satisfied with the configuration, hold the run-stop button until you hear a beep. Then run the following command to execute the [joint_state_printer.py](https://github.com/hello-robot/stretch_tutorials/blob/humble/stretch_ros_tutorials/joint_state_printer.py) which will print the joint positions of the lift, arm, and wrist. In a new terminal, execute:
  9. ```{.bash .shell-prompt}
  10. cd ament_ws/src/stretch_tutorials/stretch_ros_tutorials/
  11. python3 joint_state_printer.py
  12. ```
  13. Your terminal will output the `position` information of the previously mentioned joints shown below.
  14. ```{.bash .no-copy}
  15. name: ['joint_lift', 'wrist_extension', 'joint_wrist_yaw']
  16. position: [0.6043133175850597, 0.19873586673129257, 0.017257283863713464]
  17. ```
  18. !!! note
  19. Stretch's arm has four prismatic joints and the sum of their positions gives the *wrist_extension* distance. The *wrist_extension* is needed when sending [joint trajectory commands](https://github.com/hello-robot/stretch_tutorials/blob/master/ros2/follow_joint_trajectory.md) to the robot. Further, you can not actuate an individual arm joint. Here is an image of the arm joints for reference:
  20. <p align="center">
  21. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/joints.png"/>
  22. </p>
  23. ### The Code
  24. ```python
  25. #!/usr/bin/env python3
  26. import rclpy
  27. from rclpy.node import Node
  28. import sys
  29. import time
  30. # We're going to subscribe to a JointState message type, so we need to import
  31. # the definition for it
  32. from sensor_msgs.msg import JointState
  33. class JointStatePublisher(Node):
  34. """
  35. A class that prints the positions of desired joints in Stretch.
  36. """
  37. def __init__(self):
  38. """
  39. Function that initializes the subscriber.
  40. :param self: The self reference
  41. """
  42. super().__init__('stretch_joint_state')
  43. # Set up a subscriber. We're going to subscribe to the topic "joint_states"
  44. self.sub = self.create_subscription(JointState, 'joint_states', self.callback, 1)
  45. def callback(self, msg):
  46. """
  47. Callback function to deal with the incoming JointState messages.
  48. :param self: The self reference.
  49. :param msg: The JointState message.
  50. """
  51. # Store the joint messages for later use
  52. self.get_logger().info('Receiving JointState messages')
  53. self.joint_states = msg
  54. def print_states(self, joints):
  55. """
  56. print_states function to deal with the incoming JointState messages.
  57. :param self: The self reference.
  58. :param joints: A list of string values of joint names.
  59. """
  60. # Create an empty list that will store the positions of the requested joints
  61. joint_positions = []
  62. # Use of forloop to parse the names of the requested joints list.
  63. # The index() function returns the index at the first occurrence of
  64. # the name of the requested joint in the self.joint_states.name list
  65. for joint in joints:
  66. if joint == "wrist_extension":
  67. index = self.joint_states.name.index('joint_arm_l0')
  68. joint_positions.append(4*self.joint_states.position[index])
  69. continue
  70. index = self.joint_states.name.index(joint)
  71. joint_positions.append(self.joint_states.position[index])
  72. # Print the joint position values to the terminal
  73. print("name: " + str(joints))
  74. print("position: " + str(joint_positions))
  75. # Sends a signal to rclpy to shutdown the ROS interfaces
  76. rclpy.shutdown()
  77. # Exit the Python interpreter
  78. sys.exit(0)
  79. def main(args=None):
  80. # Initialize the node
  81. rclpy.init(args=args)
  82. joint_publisher = JointStatePublisher()
  83. time.sleep(1)
  84. rclpy.spin_once(joint_publisher)
  85. # Create a list of the joints and name them joints. These will be an argument
  86. # for the print_states() function
  87. joints = ["joint_lift", "wrist_extension", "joint_wrist_yaw"]
  88. joint_publisher.print_states(joints)
  89. # Give control to ROS. This will allow the callback to be called whenever new
  90. # messages come in. If we don't put this line in, then the node will not work,
  91. # and ROS will not process any messages
  92. rclpy.spin(joint_publisher)
  93. if __name__ == '__main__':
  94. main()
  95. ```
  96. ### The Code Explained
  97. Now let's break the code down.
  98. ```python
  99. #!/usr/bin/env python3
  100. ```
  101. Every Python ROS [Node](http://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html) will have this declaration at the top. The first line makes sure your script is executed as a Python3 script.
  102. ```python
  103. import rclpy
  104. import sys
  105. import time
  106. from rclpy.node import Node
  107. from sensor_msgs.msg import JointState
  108. ```
  109. You need to import rclpy if you are writing a ROS 2 Node. Import `sensor_msgs.msg` so that we can subscribe to `JointState` messages.
  110. ```python
  111. self.sub = self.create_subscription(JointState, 'joint_states', self.callback, 1)
  112. ```
  113. Set up a subscriber. We're going to subscribe to the topic `joint_states`, looking for `JointState` messages. When a message comes in, ROS is going to pass it to the function "callback" automatically
  114. ```python
  115. def callback(self, msg):
  116. self.joint_states = msg
  117. ```
  118. This is the callback function where the `JointState` messages are stored as `self.joint_states`. Further information about this message type can be found here: [JointState Message](http://docs.ros.org/en/lunar/api/sensor_msgs/html/msg/JointState.html)
  119. ```python
  120. def print_states(self, joints):
  121. joint_positions = []
  122. ```
  123. This is the `print_states()` function which takes in a list of joints of interest as its argument. the is also an empty list set as `joint_positions` and this is where the positions of the requested joints will be appended.
  124. ```python
  125. for joint in joints:
  126. if joint == "wrist_extension":
  127. index = self.joint_states.name.index('joint_arm_l0')
  128. joint_positions.append(4*self.joint_states.position[index])
  129. continue
  130. index = self.joint_states.name.index(joint)
  131. joint_positions.append(self.joint_states.position[index])
  132. ```
  133. In this section of the code, a for loop is used to parse the names of the requested joints from the `self.joint_states` list. The `index()` function returns the index of the name of the requested joint and appends the respective position to the `joint_positions` list.
  134. ```python
  135. rclpy.shutdown()
  136. sys.exit(0)
  137. ```
  138. The first line of code initiates a clean shutdown of ROS. The second line of code exits the Python interpreter.
  139. ```python
  140. rclpy.init(args=args)
  141. joint_publisher = JointStatePublisher()
  142. time.sleep(1)
  143. ```
  144. The next line, rclpy.init_node initializes the node. In this case, your node will take on the name 'stretch_joint_state'. **NOTE:** the name must be a base name, i.e. it cannot contain any slashes "/".
  145. Declare object, *joint_publisher*, from the `JointStatePublisher` class.
  146. The use of the `time.sleep()` function is to allow the *joint_publisher* class to initialize all of its features before requesting to publish joint positions of desired joints (running the `print_states()` method).
  147. ```python
  148. joints = ["joint_lift", "wrist_extension", "joint_wrist_yaw"]
  149. #joints = ["joint_head_pan","joint_head_tilt", joint_gripper_finger_left", "joint_gripper_finger_right"]
  150. joint_publisher.print_states(joints)
  151. ```
  152. Create a list of the desired joints that you want positions to be printed. Then use that list as an argument for the `print_states()` method.
  153. ```python
  154. rclpy.spin(joint_publisher)
  155. ```
  156. Give control to ROS. This will allow the callback to be called whenever new messages come in. If we don't put this line in, then the node will not work, and ROS will not process any messages.