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.

354 lines
14 KiB

  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 [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).
  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. ros2 launch stretch_core stretch_driver.launch.py
  9. ```
  10. There's no need to switch to the position mode in comparison with ROS1 because the default mode of the launcher is this position mode. Then run the [effort_sensing.py](https://github.com/hello-robot/stretch_tutorials/blob/humble/stretch_ros_tutorials/effort_sensing.py) node. In a new terminal, execute:
  11. ```{.bash .shell-prompt}
  12. cd ament_ws/src/stretch_tutorials/stretch_ros_tutorials/
  13. python3 effort_sensing.py
  14. ```
  15. This will send a `FollowJointTrajectory` command to move Stretch's arm or head while also printing the effort of the lift.
  16. ### The Code
  17. ```python
  18. ##!/usr/bin/env python3
  19. import rclpy
  20. import hello_helpers.hello_misc as hm
  21. import os
  22. import csv
  23. import time
  24. import pandas as pd
  25. import matplotlib
  26. matplotlib.use('tkagg')
  27. import matplotlib.pyplot as plt
  28. from matplotlib import animation
  29. from datetime import datetime
  30. from control_msgs.action import FollowJointTrajectory
  31. from trajectory_msgs.msg import JointTrajectoryPoint
  32. class JointActuatorEffortSensor(hm.HelloNode):
  33. def __init__(self, export_data=True, animate=True):
  34. hm.HelloNode.__init__(self)
  35. hm.HelloNode.main(self, 'issue_command', 'issue_command', wait_for_first_pointcloud=False)
  36. self.joints = ['joint_lift']
  37. self.joint_effort = []
  38. self.save_path = '/home/hello-robot/ament_ws/src/stretch_tutorials/stored_data'
  39. self.export_data = export_data
  40. self.result = False
  41. self.file_name = datetime.now().strftime("effort_sensing_tutorial_%Y%m%d%I")
  42. def issue_command(self):
  43. trajectory_goal = FollowJointTrajectory.Goal()
  44. trajectory_goal.trajectory.joint_names = self.joints
  45. point0 = JointTrajectoryPoint()
  46. point0.positions = [0.9]
  47. trajectory_goal.trajectory.points = [point0]
  48. trajectory_goal.trajectory.header.stamp = self.get_clock().now().to_msg()
  49. trajectory_goal.trajectory.header.frame_id = 'base_link'
  50. while self.joint_state is None:
  51. time.sleep(0.1)
  52. self._send_goal_future = self.trajectory_client.send_goal_async(trajectory_goal, self.feedback_callback)
  53. self.get_logger().info('Sent position goal = {0}'.format(trajectory_goal))
  54. self._send_goal_future.add_done_callback(self.goal_response_callback)
  55. def goal_response_callback(self, future):
  56. goal_handle = future.result()
  57. if not goal_handle.accepted:
  58. self.get_logger().info('Failed')
  59. return
  60. self.get_logger().info('Succeded')
  61. self._get_result_future = goal_handle.get_result_async()
  62. self._get_result_future.add_done_callback(self.get_result_callback)
  63. def get_result_callback(self, future):
  64. self.result = future.result().result
  65. self.get_logger().info('Sent position goal = {0}'.format(self.result))
  66. def feedback_callback(self, feedback_msg):
  67. if 'wrist_extension' in self.joints:
  68. self.joints.remove('wrist_extension')
  69. self.joints.append('joint_arm_l0')
  70. current_effort = []
  71. for joint in self.joints:
  72. index = self.joint_state.name.index(joint)
  73. current_effort.append(self.joint_state.effort[index])
  74. if not self.export_data:
  75. print("name: " + str(self.joints))
  76. print("effort: " + str(current_effort))
  77. else:
  78. self.joint_effort.append(current_effort)
  79. if self.export_data:
  80. file_name = self.file_name
  81. completeName = os.path.join(self.save_path, file_name)
  82. with open(completeName, "w") as f:
  83. writer = csv.writer(f)
  84. writer.writerow(self.joints)
  85. writer.writerows(self.joint_effort)
  86. def plot_data(self, animate = True):
  87. while not self.result:
  88. time.sleep(0.1)
  89. file_name = self.file_name
  90. self.completeName = os.path.join(self.save_path, file_name)
  91. self.data = pd.read_csv(self.completeName)
  92. self.y_anim = []
  93. self.animate = animate
  94. for joint in self.data.columns:
  95. # Create figure, labels, and title
  96. fig = plt.figure()
  97. plt.title(joint + ' Effort Sensing')
  98. plt.ylabel('Effort')
  99. plt.xlabel('Data Points')
  100. # Conditional statement for animation plotting
  101. if self.animate:
  102. self.effort = self.data[joint]
  103. frames = len(self.effort)-1
  104. anim = animation.FuncAnimation(fig=fig,func=self.plot_animate, repeat=False,blit=False,frames=frames, interval =75)
  105. plt.show()
  106. ## If you want to save a video, make sure to comment out plt.show(),
  107. ## right before this comment.
  108. # save_path = str(self.completeName + '.mp4')
  109. # anim.save(save_path, writer=animation.FFMpegWriter(fps=10))
  110. # Reset y_anim for the next joint effort animation
  111. del self.y_anim[:]
  112. # Conditional statement for regular plotting (No animation)
  113. else:
  114. self.data[joint].plot(kind='line')
  115. # save_path = str(self.completeName + '.png')
  116. # plt.savefig(save_path, bbox_inches='tight')
  117. plt.show()
  118. def plot_animate(self,i):
  119. # Append self.effort values for given joint
  120. self.y_anim.append(self.effort.values[i])
  121. plt.plot(self.y_anim, color='blue')
  122. def main(self):
  123. self.get_logger().info('issuing command')
  124. self.issue_command()
  125. def main():
  126. try:
  127. node = JointActuatorEffortSensor(export_data=True, animate=True)
  128. node.main()
  129. node.plot_data()
  130. node.new_thread.join()
  131. except KeyboardInterrupt:
  132. node.get_logger().info('interrupt received, so shutting down')
  133. node.destroy_node()
  134. rclpy.shutdown()
  135. if __name__ == '__main__':
  136. main()
  137. ```
  138. ### The Code Explained
  139. This code is similar to that of the [multipoint_command](https://github.com/hello-robot/stretch_tutorials/blob/humble/stretch_ros_tutorials/multipoint_command.py) and [joint_state_printer](https://github.com/hello-robot/stretch_tutorials/blob/humble/stretch_ros_tutorials/joint_state_printer.py) node. Therefore, this example will highlight sections that are different from those tutorials. Now let's break the code down.
  140. ```python
  141. #!/usr/bin/env python3
  142. ```
  143. Every Python ROS [Node](http://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html
  144. ) will have this declaration at the top. The first line makes sure your script is executed as a Python3 script.
  145. ```python
  146. import rclpy
  147. import hello_helpers.hello_misc as hm
  148. import os
  149. import csv
  150. import time
  151. import pandas as pd
  152. import matplotlib
  153. matplotlib.use('tkagg')
  154. import matplotlib.pyplot as plt
  155. from matplotlib import animation
  156. from datetime import datetime
  157. from control_msgs.action import FollowJointTrajectory
  158. from trajectory_msgs.msg import JointTrajectoryPoint
  159. ```
  160. You need to import rclpy if you are writing a ROS [Node](http://docs.ros.org/en/humble/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Nodes/Understanding-ROS2-Nodes.html). Import the `FollowJointTrajectory` from the `control_msgs.action` 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_ros2). In this instance, we are importing the `hello_misc` script.
  161. ```Python
  162. class JointActuatorEffortSensor(hm.HelloNode):
  163. def __init__(self, export_data=False):
  164. hm.HelloNode.__init__(self)
  165. hm.HelloNode.main(self, 'issue_command', 'issue_command', wait_for_first_pointcloud=False)
  166. ```
  167. The `JointActuatorEffortSensor` class inherits the `HelloNode` class from `hm` and is initialized also the HelloNode class already have the topic joint_states, thanks to this we don't need to create a subscriber.
  168. ```python
  169. self.joints = ['joint_lift']
  170. ```
  171. Create a list of the desired joints you want to print.
  172. ```Python
  173. self.joint_effort = []
  174. self.save_path = '/home/hello-robot/ament_ws/src/stretch_tutorials/stored_data'
  175. self.export_data = export_data
  176. self.result = False
  177. self.file_name = datetime.now().strftime("effort_sensing_tutorial_%Y%m%d%I")
  178. ```
  179. 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 `True`. If set to `False`, then the joint values will be printed in the terminal, otherwise, it will be stored in a .txt file and that's what we want to see the plot graph. Also we want to give our text file a name since the beginning and we use the `self.file_name` to access this later.
  180. ```python
  181. self._send_goal_future = self.trajectory_client.send_goal_async(trajectory_goal, self.feedback_callback)
  182. self._send_goal_future.add_done_callback(self.goal_response_callback)
  183. ```
  184. The [ActionClient.send_goal_async()](https://docs.ros2.org/latest/api/rclpy/api/actions.html#rclpy.action.client.ActionClient.send_goal_async) method returns a future to a goal handle. Include the goal and `feedback_callback` functions in the send goal function. Also we need to register a `goal_response_callback` for when the future is complete
  185. ```python
  186. def goal_response_callback(self,future):
  187. goal_handle = future.result()
  188. if not goal_handle.accepted:
  189. self.get_logger().info('Failed')
  190. return
  191. self.get_logger().info('Succeded')
  192. ```
  193. Looking at the `goal_response_callback` in more detail we can see if the future is complete with the messages that will appear.
  194. ```python
  195. self._get_result_future = goal_handle.get_result_async()
  196. self._get_result_future.add_done_callback(self.get_result_callback)
  197. ```
  198. We need the goal_handle to request the result with the method get_result_async. With this we will get a future that will complete when the result is ready so we need a callback for this result.
  199. ```python
  200. def get_result_callback(self, future):
  201. self.result = future.result().result
  202. self.get_logger().info('Sent position goal = {0}'.format(result))
  203. ```
  204. In the result callback we log the result of our poistion goal
  205. ```python
  206. def feedback_callback(self,feedback_msg):
  207. ```
  208. The feedback callback function takes in the `FollowJointTrajectoryActionFeedback` message as its argument.
  209. ```python
  210. if 'wrist_extension' in self.joints:
  211. self.joints.remove('wrist_extension')
  212. self.joints.append('joint_arm_l0')
  213. ```
  214. 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.
  215. ```python
  216. current_effort = []
  217. for joint in self.joints:
  218. index = self.joint_states.name.index(joint)
  219. current_effort.append(self.joint_states.effort[index])
  220. ```
  221. Create an empty list to store the current effort values. Then use a for loop to parse the joint names and effort values.
  222. ```python
  223. if not self.export_data:
  224. print("name: " + str(self.joints))
  225. print("effort: " + str(current_effort))
  226. else:
  227. self.joint_effort.append(current_effort)
  228. ```
  229. 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.
  230. ```python
  231. if self.export_data:
  232. file_name = self.file_name
  233. completeName = os.path.join(self.save_path, file_name)
  234. with open(completeName, "w") as f:
  235. writer = csv.writer(f)
  236. writer.writerow(self.joints)
  237. writer.writerows(self.joint_effort)
  238. ```
  239. A conditional statement is used to export the data to a .txt file. The file's name is set to the one we created at the beginning.
  240. ```python
  241. def plot_data(self, animate = True):
  242. while not self.result:
  243. time.sleep(0.1)
  244. file_name = self.file_name
  245. self.completeName = os.path.join(self.save_path, file_name)
  246. self.data = pd.read_csv(self.completeName)
  247. self.y_anim = []
  248. self.animate = animate
  249. ```
  250. This function will help us initialize some values to plot our data, we need to wait until we get the results to start plotting, because the file could still be storing values and we want to plot every point also we need to create an empty list for the animation.
  251. ```python
  252. for joint in self.data.columns:
  253. # Create figure, labels, and title
  254. fig = plt.figure()
  255. plt.title(joint + ' Effort Sensing')
  256. plt.ylabel('Effort')
  257. plt.xlabel('Data Points')
  258. ```
  259. Create a for loop to print each joint's effort writing the correct labels for x and y
  260. ```python
  261. if self.animate:
  262. self.effort = self.data[joint]
  263. frames = len(self.effort)-1
  264. anim = animation.FuncAnimation(fig=fig,func=self.plot_animate, repeat=False,blit=False,frames=frames, interval =75)
  265. plt.show()
  266. del self.y_anim[:]
  267. else:
  268. self.data[joint].plot(kind='line')
  269. # save_path = str(self.completeName + '.png')
  270. # plt.savefig(save_path, bbox_inches='tight')
  271. plt.show()
  272. ```
  273. This is a conditional statement for the animation plotting
  274. ```python
  275. def plot_animate(self,i):
  276. self.y_anim.append(self.effort.values[i])
  277. plt.plot(self.y_anim, color='blue')
  278. ```
  279. We will create another function that will plot every increment in the data frame
  280. <p align="center">
  281. <img src="https://raw.githubusercontent.com/hello-robot/stretch_tutorials/noetic/stored_data/2022-06-30_11:26:20-AM.png"/>
  282. </p>