sr_controller_tuner.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Copyright 2011 Shadow Robot Company Ltd.
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation version 2 of the License.
8 #
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 # more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 
18 import rospy
19 import re
20 
21 from xml.etree import ElementTree as ET
22 from controller_manager_msgs.srv import ListControllers
23 
24 from sr_robot_msgs.srv import ForceController, SetEffortControllerGains, SetMixedPositionVelocityPidGains, SetPidGains
25 from sr_gui_controller_tuner.pid_loader_and_saver import PidLoader, PidSaver
26 
27 
28 class CtrlSettings(object):
29 
30  """
31  Parses xml file and reads controller settings
32  Creates lists for headers, fingers, motors
33  """
34 
35  def __init__(self, xml_path, controller_type, joint_prefix):
36  self.headers = []
37 
38  # open and parses the xml config file
39  xml_file = open(xml_path)
40  xml_tree = ET.parse(xml_file)
41  xml_file.close()
42 
43  # read the settings from the xml file
44  ctrl_tree = None
45  for ctrl in xml_tree.findall("controller"):
46  ctrl_name = ctrl.attrib['name']
47  if ctrl_name == controller_type:
48  ctrl_tree = ctrl
49  break
50 
51  if ctrl_tree is None:
52  rospy.logerr(
53  "Couldn't find the settings for the controller " + controller_type)
54 
55  # read the headers settings
56  xml_headers = ctrl_tree.find("headers")
57  for header in xml_headers.findall("item"):
58  self.headers.append(header.attrib)
59 
60  self.nb_columns = len(self.headers)
61 
62  self.hand_item = ["Hand"]
63  self.hand_item.extend((self.nb_columns - 1) * [""])
64 
65  # read the fingers and the motors from the xml file
66  self.fingers = []
67  self.motors = []
68  all_fingers = xml_tree.find("fingers")
69  for finger in all_fingers.findall("finger"):
70  finger_row = [finger.attrib['name']]
71  finger_row.extend((self.nb_columns - 1) * [""])
72  self.fingers.append(finger_row)
73 
74  motors_for_finger = []
75  for motor in finger.findall("motor"):
76  motor_row = ["", joint_prefix + motor.attrib['name']]
77  motor_row.extend((self.nb_columns - 1) * [""])
78  motors_for_finger.append(motor_row)
79 
80  self.motors.append(motors_for_finger)
81 
82 
83 class SrControllerTunerApp(object):
84 
85  """
86  Handles loading, saving and setting of controller settings
87  """
88  CONTROLLER_MANAGER_DETECTION_TIMEOUT = 3.0
89 
90  def __init__(self, xml_path):
91  self.xml_path = xml_path
92  self.all_controller_types = ["Motor Force", "Position", "Velocity",
93  "Mixed Position/Velocity", "Effort", "Muscle Position"]
95 
96  self.edit_only_mode = False
97  self.control_mode = "FORCE"
98  # global prefix
99 
100  # prefix used in shadow controllers
101  self.controller_prefix = "sh_"
102 
103  # both prefix and selected_prefix are stored
104  # to handle case when gui is started in a namespace, then prefix
105  # must be set to empty but selected prefix is still necessary
106  # to select which joint_prefix to choose
107  self.selected_prefix = ""
108  self.prefix = ""
109  self.joint_prefix = ""
110  # store if one or two loops are running
111  self.single_loop = False
112 
113  self.namespace = rospy.get_namespace()
114 
115  def check_prefix(self):
116  """
117  Get the prefix (hand and joint)
118  Check if it matches selected prefix
119  """
120 
121  hand_ids = []
122  hand_joint_prefixes = []
123  # unless incompatible, use the selected prefix by default
124  self.prefix = self.selected_prefix
125  prefix = self.selected_prefix.rstrip("/")
126  # mapping is always in global ns
127  if rospy.has_param("/hand/mapping"):
128  hand_mapping = rospy.get_param("/hand/mapping")
129  for _, value in hand_mapping.items():
130  # if prefix matches the mapping, add this hand (empty prefix
131  # means both hands)
132  if value.startswith(prefix):
133  hand_ids.append(value)
134  if len(hand_ids) == 0:
135  # no matching hand id to selected prefix, check for namespace
136  if prefix in self.namespace:
137  rospy.loginfo("Using namespace, no prefix")
138  # in this case no prefix is needed
139  self.prefix = ""
140  else:
141  rospy.logwarn("Prefix not matching namespace")
142  return False
143 
144  if len(hand_ids) > 1:
145  # the plugin cannot handle more than one hand
146  rospy.logwarn(
147  "More than one hand found with prefix :" + prefix + " !\n Not loading controllers")
148  return False
149 
150  # joint_prefix always in global ns
151  if rospy.has_param("/hand/joint_prefix"):
152  hand_joint_prefix_mapping = rospy.get_param("/hand/joint_prefix")
153  for _, value in hand_joint_prefix_mapping.items():
154  # if prefix matches the mapping, add this joint prefix
155  if prefix in value:
156  hand_joint_prefixes.append(value)
157 
158  if len(hand_joint_prefixes) > 1:
159  # the plugin cannot handle more than one hand
160  rospy.logwarn(
161  "More than one hand found with prefix :" + prefix + " !\n Not loading controllers")
162  return False
163 
164  if len(hand_joint_prefixes) == 0:
165  rospy.logwarn("No hand found with prefix :" + prefix)
166  self.joint_prefix = ""
167  else:
168  self.joint_prefix = hand_joint_prefixes[0]
169 
170  rospy.loginfo("using joint_prefix " + self.joint_prefix)
171  return True
172 
173  def get_ctrls(self):
174  """
175  Retrieve currently running controllers
176  return ["Motor Force", "Position"]
177  """
178  running_ctrls = []
179 
180  # find controller manager in selected namespace
181 
182  ctrl_srv_name = self.prefix + 'controller_manager/list_controllers'
183  try:
184  rospy.wait_for_service(
185  ctrl_srv_name, self.CONTROLLER_MANAGER_DETECTION_TIMEOUT)
186 
187  except rospy.ROSException:
188  # try at root namespace (only in case bimanual setup in a single
189  # loop and only if no GUI ns)
190  if self.namespace == "/":
191  ctrl_srv_name = 'controller_manager/list_controllers'
192  try:
193  rospy.wait_for_service(
194  ctrl_srv_name, self.CONTROLLER_MANAGER_DETECTION_TIMEOUT)
195  self.single_loop = True
196  rospy.loginfo("Detected single loop")
197  except rospy.ROSException, e:
198  rospy.loginfo(
199  "Controller manager not running: %s" % str(e))
200  rospy.loginfo("Running controller tuner in edit-only mode")
201  return self.set_edit_only(running_ctrls)
202  else:
203  return self.set_edit_only(running_ctrls)
204 
205  # found a controller manager
206  controllers = rospy.ServiceProxy(ctrl_srv_name, ListControllers)
207  resp = None
208  try:
209  resp = controllers()
210  except rospy.ServiceException, e:
211  rospy.logerr("Service did not process request: %s" % str(e))
212 
213  running_ctrls.append("Motor Force")
214  if resp is not None:
215  for controller in resp.controller:
216  if controller.state == "running":
217  # find at the specific pattern of the controller
218  splitted = re.split(
219  '[tfmrlw][fhr]j[0-5]_', controller.name)
220  # only consider shadow (prefix sh_) controllers (drop js
221  # ctrl and others)
222  if self.controller_prefix in splitted[0]:
223  ctrl_type_tmp = ""
224  # only consider joint controllers (containing _xxjy_)
225  if len(splitted) >= 2:
226  ctrl_type_tmp = splitted[1]
227  # look at first word of the controller type
228  ctrl_type_tmp_splitted = ctrl_type_tmp.split("_")
229  for defined_ctrl_type in self.all_controller_types:
230  if ctrl_type_tmp_splitted[0].lower() in defined_ctrl_type.lower():
231  running_ctrls.append(defined_ctrl_type)
232  self.edit_only_mode = False
233  return running_ctrls
234 
235  rospy.loginfo("No controllers currently running")
236  rospy.loginfo("Running controller tuner in edit-only mode")
237  del running_ctrls[:]
238  return self.set_edit_only(running_ctrls)
239 
240  def set_edit_only(self, running_ctrls):
241  """
242  Sets all the controllers to defined type for edit-only mode
243  """
244  self.edit_only_mode = True
245  # In edit_only_mode all the controllers are available for editing
246  for defined_ctrl_type in self.all_controller_types:
247  running_ctrls.append(defined_ctrl_type)
248  return running_ctrls
249 
251  """
252  Effectively change control mode on the realtime loop
253  """
254  self.control_mode = rospy.get_param(
255  'sr_hand_robot/' + self.prefix + 'default_control_mode', 'FORCE')
256 
257  def get_controller_settings(self, controller_type):
258  """
259  Parses a file containing the controller settings
260  and their min and max values, and returns them.
261  """
262  ctrl_settings = CtrlSettings(
263  self.xml_path, controller_type, self.joint_prefix)
264 
265  return ctrl_settings
266 
267  def load_parameters(self, controller_type, joint_name):
268  """
269  Load the parameters from the yaml file.
270  """
271  param_name = ""
272  prefix = self.prefix if self.single_loop is not True else ""
273  if controller_type == "Motor Force":
274  # currently the motor_board topics use non-prefixed joint names
275  # no matter if single or dual loops there is always a prefix for
276  # motors
277  param_name = self.prefix + \
278  joint_name.strip(
279  self.joint_prefix.rstrip("/") + "_").lower() + "/pid"
280  elif controller_type == "Position":
281  param_name = prefix + self.controller_prefix + \
282  joint_name.lower() + "_position_controller/pid"
283  elif controller_type == "Muscle Position":
284  param_name = prefix + self.controller_prefix + \
285  joint_name.lower() + "_muscle_position_controller/pid"
286  elif controller_type == "Velocity":
287  param_name = prefix + self.controller_prefix + \
288  joint_name.lower() + "_velocity_controller/pid"
289  elif controller_type == "Mixed Position/Velocity":
290  param_name = [prefix + self.controller_prefix + joint_name.lower() +
291  "_mixed_position_velocity_controller/position_pid",
292  prefix + self.controller_prefix + joint_name.lower() +
293  "_mixed_position_velocity_controller/velocity_pid"]
294  elif controller_type == "Effort":
295  param_name = prefix + self.controller_prefix + \
296  joint_name.lower() + "_effort_controller"
297 
298  return self.pid_loader.get_settings(param_name)
299 
300  def set_controller(self, joint_name, controller_type, controller_settings):
301  """
302  Sets the controller settings calling the proper service with the correct syntax for controller type.
303  """
304  pid_service = None
305  service_name = ""
306  prefix = self.prefix if self.single_loop is not True else ""
307 
308  if controller_type == "Motor Force":
309  # /sr_hand_robot/change_force_PID_FFJ0
310  # currently use non-prefixed joint names but adds prefix in the middle
311  # no matter if single or dual loops there is always a prefix for
312  # motors
313  service_name = "sr_hand_robot/" + self.prefix + \
314  "change_force_PID_" + joint_name[-4:].upper()
315  pid_service = rospy.ServiceProxy(service_name, ForceController)
316 
317  elif controller_type == "Position":
318  # /sh_ffj3_position_controller/set_gains
319  service_name = prefix + self.controller_prefix + \
320  joint_name.lower() + "_position_controller/set_gains"
321  pid_service = rospy.ServiceProxy(service_name, SetPidGains)
322 
323  elif controller_type == "Muscle Position":
324  # /sh_ffj3_position_controller/set_gains
325  service_name = prefix + self.controller_prefix + \
326  joint_name.lower() + "_muscle_position_controller/set_gains"
327  pid_service = rospy.ServiceProxy(service_name, SetPidGains)
328 
329  elif controller_type == "Velocity":
330  # /sh_ffj3_velocity_controller/set_gains
331  service_name = prefix + self.controller_prefix + \
332  joint_name.lower() + "_velocity_controller/set_gains"
333  pid_service = rospy.ServiceProxy(service_name, SetPidGains)
334 
335  elif controller_type == "Mixed Position/Velocity":
336  # /sh_ffj3_mixed_position_velocity_controller/set_gains
337  service_name = prefix + self.controller_prefix + \
338  joint_name.lower() + \
339  "_mixed_position_velocity_controller/set_gains"
340  pid_service = rospy.ServiceProxy(
341  service_name, SetMixedPositionVelocityPidGains)
342 
343  elif controller_type == "Effort":
344  # /sh_ffj3_effort_controller/set_gains
345  service_name = prefix + self.controller_prefix + \
346  joint_name.lower() + "_effort_controller/set_gains"
347  pid_service = rospy.ServiceProxy(
348  service_name, SetEffortControllerGains)
349 
350  else:
351  rospy.logerr(
352  "", controller_type, " is not a recognized controller type.")
353 
354  contrlr_settings_converted = {}
355  for param in controller_settings.items():
356  contrlr_settings_converted[param[0]] = float(param[1])
357 
358  if controller_type == "Motor Force":
359  try:
360  for setting in ["torque_limit", "torque_limiter_gain"]:
361  if setting not in contrlr_settings_converted:
362  contrlr_settings_converted[setting] = 0
363  pid_service(int(contrlr_settings_converted["max_pwm"]),
364  int(contrlr_settings_converted["sgleftref"]),
365  int(contrlr_settings_converted["sgrightref"]),
366  int(contrlr_settings_converted["f"]),
367  int(contrlr_settings_converted["p"]), int(
368  contrlr_settings_converted["i"]),
369  int(contrlr_settings_converted["d"]), int(
370  contrlr_settings_converted["imax"]),
371  int(contrlr_settings_converted["deadband"]),
372  int(contrlr_settings_converted["sign"]),
373  int(contrlr_settings_converted["torque_limit"]),
374  int(contrlr_settings_converted["torque_limiter_gain"]))
375  except rospy.ServiceException:
376  return False
377 
378  elif controller_type == "Position":
379  try:
380  pid_service(
381  float(contrlr_settings_converted["p"]), float(
382  contrlr_settings_converted["i"]),
383  float(contrlr_settings_converted["d"]), float(
384  contrlr_settings_converted["i_clamp"]),
385  float(contrlr_settings_converted["max_force"]), float(
386  contrlr_settings_converted[
387  "position_deadband"]),
388  int(contrlr_settings_converted["friction_deadband"]))
389  except rospy.ServiceException:
390  return False
391 
392  elif controller_type == "Muscle Position":
393  try:
394  pid_service(
395  float(contrlr_settings_converted["p"]), float(
396  contrlr_settings_converted["i"]),
397  float(contrlr_settings_converted["d"]), float(
398  contrlr_settings_converted["i_clamp"]),
399  float(contrlr_settings_converted["max_force"]), float(
400  contrlr_settings_converted[
401  "position_deadband"]),
402  int(contrlr_settings_converted["friction_deadband"]))
403  except rospy.ServiceException:
404  return False
405 
406  elif controller_type == "Velocity":
407  try:
408  pid_service(
409  float(contrlr_settings_converted["p"]), float(
410  contrlr_settings_converted["i"]),
411  float(contrlr_settings_converted["d"]), float(
412  contrlr_settings_converted["i_clamp"]),
413  float(contrlr_settings_converted["max_force"]), float(
414  contrlr_settings_converted[
415  "velocity_deadband"]),
416  int(contrlr_settings_converted["friction_deadband"]))
417  except rospy.ServiceException:
418  return False
419 
420  elif controller_type == "Mixed Position/Velocity":
421  try:
422  pid_service(
423  float(contrlr_settings_converted["pos/p"]), float(
424  contrlr_settings_converted["pos/i"]),
425  float(contrlr_settings_converted["pos/d"]), float(
426  contrlr_settings_converted["pos/i_clamp"]),
427  float(contrlr_settings_converted["pos/min_velocity"]), float(
428  contrlr_settings_converted[
429  "pos/max_velocity"]),
430  float(contrlr_settings_converted[
431  "pos/position_deadband"]),
432  float(contrlr_settings_converted["vel/p"]), float(
433  contrlr_settings_converted["vel/i"]),
434  float(contrlr_settings_converted["vel/d"]), float(
435  contrlr_settings_converted["vel/i_clamp"]),
436  float(contrlr_settings_converted["vel/max_force"]),
437  int(contrlr_settings_converted["vel/friction_deadband"]))
438  except rospy.ServiceException:
439  return False
440 
441  elif controller_type == "Effort":
442  try:
443  pid_service(int(contrlr_settings_converted["max_force"]), int(
444  contrlr_settings_converted["friction_deadband"]))
445  except rospy.ServiceException:
446  return False
447  else:
448  rospy.logerr(
449  "", controller_type, " is not a recognized controller type.")
450  return False
451  return True
452 
453  def save_controller(self, joint_name, controller_type, controller_settings, filename):
454  """
455  Saves the controller settings calling the proper service with the correct syntax for controller type
456  """
457  param_name = []
458  prefix = self.prefix if self.single_loop is not True else ""
459  if controller_type == "Motor Force":
460  param_name = ["" + joint_name[-4:].lower(), "pid"]
461  elif controller_type == "Position":
462  param_name = [prefix + self.controller_prefix +
463  joint_name.lower() + "_position_controller", "pid"]
464  elif controller_type == "Muscle Position":
465  param_name = [prefix + self.controller_prefix +
466  joint_name.lower() + "_muscle_position_controller", "pid"]
467  elif controller_type == "Velocity":
468  param_name = [prefix + self.controller_prefix +
469  joint_name.lower() + "_velocity_controller", "pid"]
470  elif controller_type == "Mixed Position/Velocity":
471  param_name = [prefix + self.controller_prefix +
472  joint_name.lower() + "_mixed_position_velocity_controller", "pid"]
473  elif controller_type == "Effort":
474  param_name = [prefix + self.controller_prefix +
475  joint_name.lower() + "_effort_controller"]
476  pid_saver = PidSaver(filename)
477  pid_saver.save_settings(param_name, controller_settings)
def save_controller(self, joint_name, controller_type, controller_settings, filename)
def __init__(self, xml_path, controller_type, joint_prefix)
def set_controller(self, joint_name, controller_type, controller_settings)


sr_gui_controller_tuner
Author(s): Ugo Cupcic
autogenerated on Wed Oct 14 2020 03:22:49