kinesthetic_teaching_console.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # Software License Agreement (BSD License)
3 #
4 # Copyright (c) 2017 Svenzva Robotics
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
10 #
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials provided
16 # with the distribution.
17 # * Neither the name of University of Arizona nor the names of its
18 # contributors may be used to endorse or promote products derived
19 # from this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 
34 
35 from cursesmenu import *
36 from cursesmenu.items import *
37 
38 import rospy
39 import rospkg
40 import actionlib
41 import yaml
42 import yamlordereddictloader
43 import re
44 import random
45 import string
46 
47 from std_msgs.msg import Bool, Int32
48 from svenzva_msgs.msg import *
49 from svenzva_msgs.srv import *
50 from trajectory_msgs.msg import JointTrajectoryPoint
51 from control_msgs.msg import JointTrajectoryAction, JointTrajectoryGoal, FollowJointTrajectoryAction, FollowJointTrajectoryGoal
52 from sensor_msgs.msg import JointState
53 
54 from os import listdir
55 from os.path import isfile, join
56 
57 
58 """
59 This class creates a kinesthetic interface for Revel series robotic arms.
60 Run as a file, this class is used and wrapped around an n-curses style terminal gui.
61 
62 The KinestheticTeaching class could be imported and used as a base for a drag-and-click GUI,
63 or voice activated interactive system.
64 """
65 
67 
68  def __init__(self):
69 
70  record = True
71  self.joint_states = JointState()
72  self.gripper_client = actionlib.SimpleActionClient('/revel/gripper_action', GripperAction)
73  joint_states_sub = rospy.Subscriber('/joint_states', JointState, self.js_cb, queue_size=1)
74  self.fkine = actionlib.SimpleActionClient('/svenzva_joint_action', SvenzvaJointAction)
75  #rospy.loginfo("Waiting for fkine trajectory server...")
76  self.fkine.wait_for_server()
77  #rospy.loginfo("Found Trajectory action server")
78 
79  #rospy.loginfo("Waiting for gripper action server")
80  self.gripper_client.wait_for_server()
81  #rospy.loginfo("Found Revel gripper action server")
82 
83  self.gripper_goal = GripperGoal()
84 
85  rospack = rospkg.RosPack()
86  self.path = rospack.get_path('svenzva_utils')
87 
88  self.interaction_name = None
89  self.playback_file = None
90  self.menu = None
91 
92  #Controls when a given pose will return, when played back on the arm
93  # where delta is the maximum deviation of all joints from the target pose array
94  self.delta = 0.1
95 
96 
97  def js_cb(self, data):
98  self.joint_states = data
99 
100  def open_gripper(self):
101  self.gripper_goal = GripperGoal()
102  self.gripper_goal.target_action = self.gripper_goal.OPEN
103  self.gripper_client.send_goal(self.gripper_goal)
104  rospy.sleep(1.0)
105  return
106 
107  def close_gripper(self):
108  self.gripper_goal.target_action = self.gripper_goal.CLOSE
109  self.gripper_goal.target_current = 200
110  self.gripper_client.send_goal(self.gripper_goal)
111  rospy.sleep(1.0)
112  return
113 
114  """
115  *
116  * RECORDING
117  *
118  * To be called each time the user wishes to save a pose
119  """
121  if self.interaction_name is None:
122  raw_input("You must set the interaction before saving poses. Press Enter to continue.")
123  return
124 
125  try:
126  f = open(self.path+"/config/" + self.interaction_name + ".yaml", "a+")
127 
128  name = raw_input("Name this pose: ")
129  ar = []
130  ar.append(self.joint_states.position[0])
131  ar.append(self.joint_states.position[1])
132  ar.append(self.joint_states.position[2])
133  ar.append(self.joint_states.position[3])
134  ar.append(self.joint_states.position[4])
135  ar.append(self.joint_states.position[5])
136  f.write(name + ": " + str(ar) + "\n")
137  f.close()
138  except:
139  raw_input("Unable to open file. Path: " + self.path+"/config/"+self.interaction_name+".yaml")
140  return
141 
142  def record_gripper_interaction(self, open_gripper):
143  if self.interaction_name is None:
144  raw_input("You must set the interaction before saving poses. Press Enter to continue.")
145  return
146 
147  #append_write = "a+"
148  #if not os.path.exists(self.path+"/config/"+self.interaction_name+".yaml"):
149  # append_write = "w+"
150  try:
151  ran = random.randint(1,10000)
152  f = open(self.path+"/config/" + self.interaction_name + ".yaml", "a+")
153  #f = open(self.path+"/config/" + self.interaction_name + ".yaml", append_write)
154 
155  num_lines = 0 #sum(1 for line in f)
156  ar = []
157  if open_gripper:
158  ar.append("open_gripper")
159  self.open_gripper()
160  else:
161  ar.append("close_gripper")
162  self.close_gripper()
163  f.write("gripper" + str(ran) + ": " +str(ar) + "\n")
164  f.close()
165  except:
166  raw_input("Unable to open file. Path: " + self.path+"/config/"+self.interaction_name+".yaml")
167 
168  return
169 
170 
171 
173  #check if the input filename contains only valid characters
174  self.interaction_name = raw_input("Set interaction (file)name: ")
175  while not re.match(r'[\w-]*$', self.interaction_name):
176  self.interaction_name = raw_input("Name contained invalid characters.\n Set interaction (file)name: ")
177  return
178 
179  """
180  *
181  * PLAYBACK
182  *
183  """
184 
185  """
186  Sends a j6 or gripper command given a yaml file.
187  Stand-alone method for playing a state.
188  """
189  def js_playback(self, filename, state_name):
190  try:
191  f = open(self.path+"/config/" + filename + ".yaml")
192  qmap = yaml.load(f,Loader=yamlordereddictloader.Loader)
193  f.close()
194  except:
195  #rospy.logerr("Could not find specified state file. Does it exist?")
196  return
197 
198  req = SvenzvaJointGoal()
199  if qmap[state_name][0] == "open_gripper":
200  self.open_gripper()
201  return False
202  elif qmap[state_name][0] == "close_gripper":
203  self.close_gripper()
204  return True
205  else:
206  if len(qmap[state_name]) < 6:
207  #rospy.logerr("Could not find specified state. Configuration file ill-formed or missing. Aborting.")
208  return False
209  req.positions = qmap[state_name]
210 
211  #rospy.loginfo("Sending state command...")
212  self.fkine.send_goal_and_wait(req)
213  return True
214  """
215  Plays back a specific state name. Helper fuction
216  """
217  def js_playback(self, qmap, state_name):
218 
219  req = SvenzvaJointGoal()
220 
221  #action_name is at 0th index. additional indicies reserved for gripper action specifications- e.g. torque
222  if qmap[state_name][0] == "open_gripper":
223  self.open_gripper()
224  return False
225  elif qmap[state_name][0] == "close_gripper":
226  self.close_gripper()
227  return False
228  else:
229  if len(qmap[state_name]) < 6:
230  #rospy.logerr("Could not find specified state. Configuration file ill-formed or missing. Aborting.")
231  return False
232  req.positions = qmap[state_name]
233 
234  #rospy.loginfo("Sending state command...")
235  self.fkine.send_goal_and_wait(req)
236  return True
237 
238  """
239  Plays back an entire interaction- all poses specified in an interaction file
240  """
242  if self.interaction_name is None:
243  raw_input("You must set the interaction before saving poses. Press Enter to continue.")
244  return
245 
246  try:
247  f = open(self.path+"/config/" + self.interaction_name + ".yaml")
248  qmap = yaml.load(f, Loader=yamlordereddictloader.Loader)
249  f.close()
250  except:
251  #rospy.logerr("Could not find specified state file. Does it exist?")
252  raw_input("Could not find specified state file.")
253  return
254 
255  raw_input("Press Enter to play back: " + self.interaction_name + ".yaml")
256  for state in qmap:
257  if self.js_playback(qmap, state):
258  self.wait_for_stall(qmap[state])
259  rospy.sleep(0.5)
260  return
261 
262  # This method spins until a stall condition is detected.
263  # A stall condition happens when the joint states published from one time step to another have a
264  # total change less than DELTA, or
265  # when the error value (|target - actual|) of the joint value is below DELTA
266  def wait_for_stall(self, q_ar):
267  time = rospy.get_rostime()
268  max_time = rospy.Duration(10.0)
269 
270  while rospy.get_rostime() < time + max_time:
271  stalled = True
272  for i in range(0,6):
273  if abs(self.joint_states.position[i] - q_ar[i]) > self.delta:
274  stalled &= False
275  if stalled:
276  #rospy.loginfo("Arm reached target position.")
277  return
278  rospy.sleep(0.1)
279  #rospy.loginfo("Arm stalled: did not reach target position after 10 seconds.")
280  return
281 
282 
283  def get_filelist(self):
284  mypath = self.path +"/config/"
285  return [f for f in listdir(mypath) if isfile(join(mypath, f))]
286 
288  files = self.get_filelist()
289  str_build = ""
290  for item in files:
291  str_build += str(item) + "\n"
292  raw_input(str_build)
293  return
294 
295 
297  # Create the menu
298  self.menu = CursesMenu("Main", "Teach or playback a guided interaction")
299 
300  gripper_menu = CursesMenu("Gripper Interaction", "Teaching a new guided interaction")
301  record_menu = CursesMenu("Record Interaction", "Teaching a new guided interaction")
302  playback_menu = CursesMenu("Playback Interaction", "Playing back an existing interaction")
303 
304  record_submenu_item = SubmenuItem("Record a new interaction", record_menu, self.menu)
305  gripper_submenu_item = SubmenuItem("Set gripper action", gripper_menu, record_menu)
306  playback_submenu_item = SubmenuItem("Playback an existing interaction", playback_menu, self.menu)
307 
308  # A FunctionItem runs a Python function when selected
309  save_pose_item = FunctionItem("Save robot pose", self.record_state_interaction)
310  save_gripper_open_item = FunctionItem("Open gripper", self.record_gripper_interaction, [True])
311  save_gripper_close_item = FunctionItem("Close gripper", self.record_gripper_interaction, [False])
312  playback_item = FunctionItem("Playback interaction", self.playback_interaction)
313  set_int_name_item = FunctionItem("Set interaction name", self.set_new_interaction_name)
314  list_existing_interactions_item = FunctionItem("List existing interaction names", self.list_existing_interactions)
315 
316  gripper_menu.append_item(save_gripper_open_item)
317  gripper_menu.append_item(save_gripper_close_item)
318 
319  record_menu.append_item(save_pose_item)
320  record_menu.append_item(gripper_submenu_item)
321 
322  self.menu.append_item(set_int_name_item)
323  self.menu.append_item(list_existing_interactions_item)
324  self.menu.append_item(record_submenu_item)
325  self.menu.append_item(playback_item)
326 
327  # Finally, we call show to show the menu and allow the user to interact
328  self.menu.show()
329 
330 if __name__ == '__main__':
331  #setup_console_menu()
332  rospy.init_node('svenzva_kinesthic_teaching_console', anonymous=False)
333  try:
335  kt.start_console_menu()
336  except rospy.ROSInterruptException:
337  pass
338 
339 
340 


svenzva_utils
Author(s): Max Svetlik
autogenerated on Wed Oct 28 2020 03:31:33