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.

311 lines
12 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
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 6
  2. In this example, we will review a Python script that prints and stores the effort values from a specified joint. If you are looking for a continuous print of the joint state efforts while Stretch is in action, then you can use the [rostopic command-line tool](http://wiki.ros.org/rostopic) shown in the [Internal State of Stretch Tutorial](internal_state_of_stretch.md).
  3. <p align="center">
  4. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/images/effort_sensing.gif"/>
  5. </p>
  6. Begin by running the following command in a terminal.
  7. ```{.bash .shell-prompt}
  8. roslaunch stretch_core stretch_driver.launch
  9. ```
  10. Switch the mode to `position` mode using a rosservice call. Then run the [effort_sensing.py](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/effort_sensing.py) node. In a new terminal, execute:
  11. ```{.bash .shell-prompt}
  12. rosservice call /switch_to_position_mode
  13. cd catkin_ws/src/stretch_tutorials/src/
  14. python3 effort_sensing.py
  15. ```
  16. This will send a `FollowJointTrajectory` command to move Stretch's arm or head while also printing the effort of the lift.
  17. ### The Code
  18. ```python
  19. #!/usr/bin/env python3
  20. import rospy
  21. import time
  22. import actionlib
  23. import os
  24. import csv
  25. from datetime import datetime
  26. from control_msgs.msg import FollowJointTrajectoryGoal
  27. from trajectory_msgs.msg import JointTrajectoryPoint
  28. from sensor_msgs.msg import JointState
  29. import hello_helpers.hello_misc as hm
  30. class JointActuatorEffortSensor(hm.HelloNode):
  31. """
  32. A class that sends multiple joint trajectory goals to a single joint.
  33. """
  34. def __init__(self, export_data=False):
  35. """
  36. Function that initializes the subscriber,and other features.
  37. :param self: The self reference.
  38. :param export_data: A boolean message type.
  39. """
  40. hm.HelloNode.__init__(self)
  41. self.sub = rospy.Subscriber('joint_states', JointState, self.callback)
  42. self.joints = ['joint_lift']
  43. self.joint_effort = []
  44. self.save_path = '/home/hello-robot/catkin_ws/src/stretch_tutorials/stored_data'
  45. self.export_data = export_data
  46. def callback(self, msg):
  47. """
  48. Callback function to update and store JointState messages.
  49. :param self: The self reference.
  50. :param msg: The JointState message.
  51. """
  52. self.joint_states = msg
  53. def issue_command(self):
  54. """
  55. Function that makes an action call and sends joint trajectory goals
  56. to a single joint.
  57. :param self: The self reference.
  58. """
  59. trajectory_goal = FollowJointTrajectoryGoal()
  60. trajectory_goal.trajectory.joint_names = self.joints
  61. point0 = JointTrajectoryPoint()
  62. point0.positions = [0.9]
  63. trajectory_goal.trajectory.points = [point0]
  64. trajectory_goal.trajectory.header.stamp = rospy.Time(0.0)
  65. trajectory_goal.trajectory.header.frame_id = 'base_link'
  66. self.trajectory_client.send_goal(trajectory_goal, feedback_cb=self.feedback_callback, done_cb=self.done_callback)
  67. rospy.loginfo('Sent position goal = {0}'.format(trajectory_goal))
  68. self.trajectory_client.wait_for_result()
  69. def feedback_callback(self,feedback):
  70. """
  71. The feedback_callback function deals with the incoming feedback messages
  72. from the trajectory_client. Although, in this function, we do not use the
  73. feedback information.
  74. :param self: The self reference.
  75. :param feedback: FollowJointTrajectoryActionFeedback message.
  76. """
  77. if 'wrist_extension' in self.joints:
  78. self.joints.remove('wrist_extension')
  79. self.joints.append('joint_arm_l0')
  80. current_effort = []
  81. for joint in self.joints:
  82. index = self.joint_states.name.index(joint)
  83. current_effort.append(self.joint_states.effort[index])
  84. if not self.export_data:
  85. print("name: " + str(self.joints))
  86. print("effort: " + str(current_effort))
  87. else:
  88. self.joint_effort.append(current_effort)
  89. def done_callback(self, status, result):
  90. """
  91. The done_callback function will be called when the joint action is complete.
  92. Within this function we export the data to a .txt file in the /stored_data directory.
  93. :param self: The self reference.
  94. :param status: status attribute from FollowJointTrajectoryActionResult message.
  95. :param result: result attribute from FollowJointTrajectoryActionResult message.
  96. """
  97. if status == actionlib.GoalStatus.SUCCEEDED:
  98. rospy.loginfo('Succeeded')
  99. else:
  100. rospy.loginfo('Failed')
  101. if self.export_data:
  102. file_name = datetime.now().strftime("%Y-%m-%d_%I:%M:%S-%p")
  103. completeName = os.path.join(self.save_path, file_name)
  104. with open(completeName, "w") as f:
  105. writer = csv.writer(f)
  106. writer.writerow(self.joints)
  107. writer.writerows(self.joint_effort)
  108. def main(self):
  109. """
  110. Function that initiates the issue_command function.
  111. :param self: The self reference.
  112. """
  113. hm.HelloNode.main(self, 'issue_command', 'issue_command', wait_for_first_pointcloud=False)
  114. rospy.loginfo('issuing command...')
  115. self.issue_command()
  116. time.sleep(2)
  117. if __name__ == '__main__':
  118. try:
  119. node = JointActuatorEffortSensor(export_data=True)
  120. node.main()
  121. except KeyboardInterrupt:
  122. rospy.loginfo('interrupt received, so shutting down')
  123. ```
  124. ### The Code Explained
  125. This code is similar to that of the [multipoint_command](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/multipoint_command.py) and [joint_state_printer](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/joint_state_printer.py) node. Therefore, this example will highlight sections that are different from those tutorials. Now let's break the code down.
  126. ```python
  127. #!/usr/bin/env python3
  128. ```
  129. 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.
  130. ```python
  131. import rospy
  132. import time
  133. import actionlib
  134. import os
  135. import csv
  136. from datetime import datetime
  137. from control_msgs.msg import FollowJointTrajectoryGoal
  138. from trajectory_msgs.msg import JointTrajectoryPoint
  139. from sensor_msgs.msg import JointState
  140. import hello_helpers.hello_misc as hm
  141. ```
  142. You need to import rospy if you are writing a ROS [Node](http://wiki.ros.org/Nodes). Import the `FollowJointTrajectoryGoal` from the `control_msgs.msg` package to control the Stretch robot. Import `JointTrajectoryPoint` from the `trajectory_msgs` package to define robot trajectories. The `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.
  143. ```Python
  144. class JointActuatorEffortSensor(hm.HelloNode):
  145. """
  146. A class that sends multiple joint trajectory goals to a single joint.
  147. """
  148. def __init__(self, export_data=False):
  149. """
  150. Function that initializes the subscriber,and other features.
  151. :param self: The self reference.
  152. :param export_data: A boolean message type.
  153. """
  154. hm.HelloNode.__init__(self)
  155. ```
  156. The `JointActuatorEffortSensor` class inherits the `HelloNode` class from `hm` and is initialized.
  157. ```python
  158. self.sub = rospy.Subscriber('joint_states', JointState, self.callback)
  159. self.joints = ['joint_lift']
  160. ```
  161. 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. Create a list of the desired joints you want to print.
  162. ```Python
  163. self.joint_effort = []
  164. self.save_path = '/home/hello-robot/catkin_ws/src/stretch_tutorials/stored_data'
  165. self.export_data = export_data
  166. ```
  167. Create an empty list to store the joint effort values. The `self.save_path` is the directory path where the .txt file of the effort values will be stored. You can change this path to a preferred directory. The `self.export_data` is a boolean and its default value is set to `False`. If set to `True`, then the joint values will be stored in a .txt file, otherwise, the values will be printed in the terminal where you ran the effort sensing node.
  168. ```python
  169. self.trajectory_client.send_goal(trajectory_goal, feedback_cb=self.feedback_callback, done_cb=self.done_callback)
  170. ```
  171. Include the feedback and `done_callback` functions in the send goal function.
  172. ```python
  173. def feedback_callback(self,feedback):
  174. """
  175. The feedback_callback function deals with the incoming feedback messages
  176. from the trajectory_client. Although, in this function, we do not use the
  177. feedback information.
  178. :param self: The self reference.
  179. :param feedback: FollowJointTrajectoryActionFeedback message.
  180. """
  181. ```
  182. The feedback callback function takes in the `FollowJointTrajectoryActionFeedback` message as its argument.
  183. ```python
  184. if 'wrist_extension' in self.joints:
  185. self.joints.remove('wrist_extension')
  186. self.joints.append('joint_arm_l0')
  187. ```
  188. Use a conditional statement to replace `wrist_extenstion` with `joint_arm_l0`. This is because `joint_arm_l0` has the effort values that the `wrist_extension` is experiencing.
  189. ```python
  190. current_effort = []
  191. for joint in self.joints:
  192. index = self.joint_states.name.index(joint)
  193. current_effort.append(self.joint_states.effort[index])
  194. ```
  195. Create an empty list to store the current effort values. Then use a for loop to parse the joint names and effort values.
  196. ```python
  197. if not self.export_data:
  198. print("name: " + str(self.joints))
  199. print("effort: " + str(current_effort))
  200. else:
  201. self.joint_effort.append(current_effort)
  202. ```
  203. Use a conditional statement to print effort values in the terminal or store values into a list that will be used for exporting the data in a .txt file.
  204. ```Python
  205. def done_callback(self, status, result):
  206. """
  207. The done_callback function will be called when the joint action is complete.
  208. Within this function we export the data to a .txt file in the /stored_data directory.
  209. :param self: The self reference.
  210. :param status: status attribute from FollowJointTrajectoryActionResult message.
  211. :param result: result attribute from FollowJointTrajectoryActionResult message.
  212. """
  213. ```
  214. The done callback function takes in the `FollowJointTrajectoryActionResult` messages as its arguments.
  215. ```python
  216. if status == actionlib.GoalStatus.SUCCEEDED:
  217. rospy.loginfo('Succeeded')
  218. else:
  219. rospy.loginfo('Failed')
  220. ```
  221. Conditional statement to print whether the goal status in the `FollowJointTrajectoryActionResult` succeeded or failed.
  222. ```python
  223. if self.export_data:
  224. file_name = datetime.now().strftime("%Y-%m-%d_%I:%M:%S-%p")
  225. completeName = os.path.join(self.save_path, file_name)
  226. with open(completeName, "w") as f:
  227. writer = csv.writer(f)
  228. writer.writerow(self.joints)
  229. writer.writerows(self.joint_effort)
  230. ```
  231. A conditional statement is used to export the data to a .txt file. The file's name is set to the date and time the node was executed. That way, no previous files are overwritten.
  232. ### Plotting/Animating Effort Data
  233. <p align="center">
  234. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/stored_data/2022-06-30_11:26:20-AM.png"/>
  235. </p>
  236. We added a simple python script, [stored_data_plotter.py](https://github.com/hello-robot/stretch_tutorials/blob/noetic/src/stored_data_plotter.py), to this package for plotting the stored data.
  237. !!! note
  238. You have to change the name of the file you wish to see in the python script. This is shown below.
  239. ```Python
  240. ####################### Copy the file name here! #######################
  241. file_name = '2022-06-30_11:26:20-AM'
  242. ```
  243. Once you have changed the file name, then run the following in a new command.
  244. ```{.bash .shell-prompt}
  245. cd catkin_ws/src/stretch_tutorials/src/
  246. python3 stored_data_plotter.py
  247. ```
  248. Because this is not a node, you don't need `roscore` to run this script. Please review the comments in the python script for additional guidance.