00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 import os, subprocess, math, time
00020
00021 import roslib
00022 roslib.load_manifest('sr_gui_controller_tuner')
00023 import rospy, rosparam
00024
00025
00026 from qt_gui.plugin import Plugin
00027 from python_qt_binding import loadUi
00028
00029 from QtCore import QEvent, QObject, Qt, QTimer, Slot, SIGNAL, QThread
00030 from QtGui import QWidget, QTreeWidgetItem, QCheckBox, QSpinBox, QDoubleSpinBox, QFileDialog, QMessageBox, QPushButton, QFrame, QHBoxLayout
00031 from functools import partial
00032 from tempfile import NamedTemporaryFile
00033
00034 from sensor_msgs.msg import JointState
00035
00036 from sr_gui_controller_tuner.sr_controller_tuner import SrControllerTunerApp
00037
00038 class PlotThread(QThread):
00039 def __init__(self, parent = None, joint_name = "FFJ0", controller_type = "Motor Force"):
00040 QThread.__init__(self, parent)
00041 self.joint_name_ = joint_name
00042 self.is_joint_0_ = False
00043 self.joint_index_in_joint_state_ = None
00044 try:
00045 self.joint_index_in_joint_state_ = ["FFJ0", "MFJ0", "RFJ0", "LFJ0"].index(self.joint_name_)
00046 self.is_joint_0_ = True
00047 except ValueError:
00048 self.is_joint_0_ = False
00049
00050 self.controller_type_ = controller_type
00051
00052
00053 self.plot_title_ = self.joint_name_ + " " + self.controller_type_
00054 self.plot_title_ = self.plot_title_.replace(" ", "_").replace("/","_")
00055
00056
00057 self.subprocess_ = []
00058
00059 def run(self):
00060 rxplot_str = "rxplot -b 30 -p 30 --title=" + self.plot_title_ + " "
00061
00062 if self.controller_type_ == "Motor Force":
00063
00064 if not self.is_joint_0_:
00065
00066 self.subscriber_ = rospy.Subscriber("/joint_states", JointState, self.js_callback_)
00067
00068
00069
00070 while self.joint_index_in_joint_state_ == None:
00071 time.sleep(0.01)
00072
00073 rxplot_str += "/joint_states/effort["+ str(self.joint_index_in_joint_state_) +"]"
00074 else:
00075 rxplot_str += "/joint_0s/joint_states/effort["+ str(self.joint_index_in_joint_state_) +"]"
00076
00077 elif self.controller_type_ == "Position":
00078 rxplot_str += "sh_"+self.joint_name_.lower()+"_position_controller/state/set_point,sh_"+self.joint_name_.lower()+"_position_controller/state/process_value /sh_" + self.joint_name_.lower()+"_position_controller/state/command"
00079 elif self.controller_type_ == "Velocity":
00080 rxplot_str += "sh_"+self.joint_name_.lower()+"_velocity_controller/state/set_point,sh_"+self.joint_name_.lower()+"_velocity_controller/state/process_value"
00081 elif self.controller_type_ == "Mixed Position/Velocity":
00082 rxplot_str += "sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/set_point,sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/process_value sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/process_value_dot,sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/commanded_velocity sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/command,sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/measured_effort,sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller/state/friction_compensation"
00083 elif self.controller_type_ == "Effort":
00084 rxplot_str += "sh_"+self.joint_name_.lower()+"_effort_controller/state/set_point,sh_"+self.joint_name_.lower()+"_effort_controller/state/process_value"
00085
00086 self.subprocess_.append( subprocess.Popen(rxplot_str.split()) )
00087
00088 def js_callback_(self, msg):
00089
00090 self.joint_index_in_joint_state_ = msg.name.index( self.joint_name_.upper() )
00091 self.subscriber_.unregister()
00092 self.subscriber_ = None
00093
00094 def __del__(self):
00095 for subprocess in self.subprocess_:
00096
00097 subprocess.kill()
00098
00099 if self.subscriber_ != None:
00100 self.subscriber_.unregister()
00101 self.subscriber_ = None
00102
00103 self.wait()
00104
00105 class MoveThread(QThread):
00106 def __init__(self, parent = None, joint_name = "FFJ0", controller_type = "Motor Force"):
00107 QThread.__init__(self, parent)
00108 self.joint_name_ = joint_name
00109 self.controller_type_ = controller_type
00110 self.btn = parent
00111 self.subprocess_ = []
00112
00113 def run(self):
00114 self.launch_()
00115
00116 def __del__(self):
00117 for subprocess in self.subprocess_:
00118 subprocess.terminate()
00119 self.wait()
00120
00121 def create_launch_file_(self):
00122
00123 controller_name_ = ""
00124 if self.controller_type_ == "Position":
00125 controller_name_ = "sh_"+self.joint_name_.lower()+"_position_controller"
00126 elif self.controller_type_ == "Mixed Position/Velocity":
00127 controller_name_ = "sh_"+self.joint_name_.lower()+"_mixed_position_velocity_controller"
00128
00129 min_max = self.get_min_max_()
00130
00131 string = "<launch> <node pkg=\"sr_movements\" name=\"sr_movements\" type=\"sr_movements\">"
00132 string += "<remap from=\"~targets\" to=\""+ controller_name_ +"/command\"/>"
00133 string += "<remap from=\"~inputs\" to=\""+ controller_name_ +"/state\"/>"
00134 string += "<param name=\"image_path\" value=\"$(find sr_movements)/movements/test.png\"/>"
00135 string += "<param name=\"min\" value=\""+ str(min_max[0]) +"\"/>"
00136 string += "<param name=\"max\" value=\""+ str(min_max[1]) + "\"/>"
00137 string += "<param name=\"publish_rate\" value=\"100\"/>"
00138 string += "<param name=\"repetition\" value=\"1000\"/>"
00139 string += "<param name=\"nb_step\" value=\"10000\"/>"
00140 string += "<param name=\"msg_type\" value=\"sr\"/>"
00141 string += "</node> </launch>"
00142
00143 tmp_launch_file = NamedTemporaryFile(delete=False)
00144
00145 tmp_launch_file.writelines(string)
00146 tmp_launch_file.close()
00147
00148 return tmp_launch_file.name
00149
00150 def get_min_max_(self):
00151 if self.joint_name_ in ["FFJ0", "MFJ0", "RFJ0", "LFJ0"]:
00152 return [0.0, math.radians(180.0)]
00153 elif self.joint_name_ in ["FFJ3", "MFJ3", "RFJ3", "LFJ3", "THJ1"]:
00154 return [0.0, math.radians(90.0)]
00155 elif self.joint_name_ in ["LFJ5"]:
00156 return [0.0, math.radians(45.0)]
00157 elif self.joint_name_ in ["FFJ4", "MFJ4", "RFJ4", "LFJ4"]:
00158 return [math.radians(-25.0), math.radians(25.0)]
00159 elif self.joint_name_ in ["THJ2"]:
00160 return [math.radians(-30.0), math.radians(30.0)]
00161 elif self.joint_name_ in ["THJ3"]:
00162 return [math.radians(-15.0), math.radians(15.0)]
00163 elif self.joint_name_ in ["THJ4"]:
00164 return [math.radians(0.0), math.radians(70.0)]
00165 elif self.joint_name_ in ["THJ5"]:
00166 return [math.radians(-60.0), math.radians(60.0)]
00167 elif self.joint_name_ in ["WRJ1"]:
00168 return [math.radians(-35.0), math.radians(45.0)]
00169 elif self.joint_name_ in ["WRJ2"]:
00170 return [math.radians(-30.0), math.radians(10.0)]
00171
00172 def launch_(self):
00173 filename = self.create_launch_file_()
00174
00175 launch_string = "roslaunch "+filename
00176
00177 self.subprocess_.append( subprocess.Popen(launch_string.split()) )
00178
00179 class SrGuiControllerTuner(Plugin):
00180
00181 def __init__(self, context):
00182 super(SrGuiControllerTuner, self).__init__(context)
00183 self.setObjectName('SrGuiControllerTuner')
00184
00185 self.controller_type = None
00186
00187 self._publisher = None
00188 self._widget = QWidget()
00189
00190 self.file_to_save = None
00191
00192 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../uis/SrGuiControllerTuner.ui')
00193 loadUi(ui_file, self._widget)
00194 self._widget.setObjectName('SrControllerTunerUi')
00195 context.add_widget(self._widget)
00196
00197
00198 self.move_threads = []
00199
00200
00201 self.controllers_in_dropdown = []
00202
00203
00204
00205 self.ctrl_widgets = {}
00206
00207
00208 self.sr_controller_tuner_app_ = SrControllerTunerApp( os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../data/controller_settings.xml') )
00209
00210
00211 self.on_btn_refresh_ctrl_clicked_()
00212
00213 self._widget.btn_refresh_ctrl.pressed.connect(self.on_btn_refresh_ctrl_clicked_)
00214 self._widget.dropdown_ctrl.currentIndexChanged.connect(self.on_changed_controller_type_)
00215
00216 self._widget.btn_save_selected.pressed.connect(self.on_btn_save_selected_clicked_)
00217 self._widget.btn_save_all.pressed.connect(self.on_btn_save_all_clicked_)
00218 self._widget.btn_select_file_path.pressed.connect(self.on_btn_select_file_path_clicked_)
00219
00220 self._widget.btn_set_selected.pressed.connect(self.on_btn_set_selected_clicked_)
00221 self._widget.btn_set_all.pressed.connect(self.on_btn_set_all_clicked_)
00222
00223 self._widget.btn_load.pressed.connect(self.on_btn_load_clicked_)
00224 self._widget.btn_stop_mvts.pressed.connect(self.on_btn_stop_mvts_clicked_)
00225
00226 def on_btn_plot_pressed_(self, joint_name, btn):
00227 plot_thread = PlotThread(btn, joint_name, self.controller_type)
00228 plot_thread.start()
00229
00230 def on_btn_move_pressed_(self, joint_name, btn):
00231 move_thread = MoveThread(btn, joint_name, self.controller_type)
00232 move_thread.start()
00233 self.move_threads.append(move_thread)
00234
00235 def on_changed_controller_type_(self, index = None):
00236 if index == None:
00237 return
00238 self.reset_file_path()
00239 self.refresh_controller_tree_( self.controllers_in_dropdown[index] )
00240
00241 def reset_file_path(self):
00242 self._widget.txt_file_path.setText("")
00243
00244 self._widget.btn_load.setEnabled(False)
00245
00246
00247
00248 self._widget.btn_save_all.setEnabled(False)
00249 self._widget.btn_save_selected.setEnabled(False)
00250
00251 def on_btn_select_file_path_clicked_(self):
00252 path_to_config = "~"
00253 try:
00254 path_to_config = roslib.packages.get_pkg_dir("sr_ethercat_hand_config")
00255 except:
00256 rospy.logwarn("couldnt find the sr_ethercat_hand_config package, do you have the sr_config stack installed?")
00257
00258 subpath = "/controls/host"
00259 if self.sr_controller_tuner_app_.edit_only_mode:
00260 filter_files = "*.yaml"
00261 else:
00262 if self.controller_type == "Motor Force":
00263 filter_files = "*controllers.yaml"
00264 else:
00265 if self.sr_controller_tuner_app_.control_mode == "PWM":
00266 filter_files = "*s_PWM.yaml"
00267 else:
00268 filter_files = "*s.yaml"
00269
00270 if self.controller_type == "Motor Force":
00271 filter_files = "Config (*motor"+filter_files+")"
00272 subpath = "/controls/motors"
00273 elif self.controller_type == "Position":
00274 filter_files = "Config (*position_controller"+filter_files+")"
00275 elif self.controller_type == "Velocity":
00276 filter_files = "Config (*velocity_controller"+filter_files+")"
00277 elif self.controller_type == "Mixed Position/Velocity":
00278 filter_files = "Config (*position_velocity"+filter_files+")"
00279 elif self.controller_type == "Effort":
00280 filter_files = "Config (*effort_controller"+filter_files+")"
00281
00282 path_to_config += subpath
00283
00284 filename, _ = QFileDialog.getOpenFileName(self._widget.tree_ctrl_settings, self._widget.tr('Save Controller Settings'),
00285 self._widget.tr(path_to_config),
00286 self._widget.tr(filter_files))
00287
00288 if filename == "":
00289 return
00290
00291 self.file_to_save = filename
00292
00293 self._widget.txt_file_path.setText(filename)
00294
00295 self._widget.btn_load.setEnabled(True)
00296
00297
00298
00299 self._widget.btn_save_all.setEnabled(True)
00300 self._widget.btn_save_selected.setEnabled(True)
00301
00302 def on_btn_load_clicked_(self):
00303
00304 paramlist = rosparam.load_file( self.file_to_save )
00305 for params,ns in paramlist:
00306 rosparam.upload_params(ns, params)
00307
00308 self.refresh_controller_tree_( self.controllers_in_dropdown[self._widget.dropdown_ctrl.currentIndex()] )
00309
00310 def on_btn_save_selected_clicked_(self):
00311 selected_items = self._widget.tree_ctrl_settings.selectedItems()
00312
00313 if len( selected_items ) == 0:
00314 QMessageBox.warning(self._widget.tree_ctrl_settings, "Warning", "No motors selected.")
00315
00316 for it in selected_items:
00317 if str(it.text(1)) != "":
00318 self.save_controller( str(it.text(1)) )
00319
00320 def on_btn_save_all_clicked_(self):
00321 for motor in self.ctrl_widgets.keys():
00322 self.save_controller( motor )
00323
00324 def on_btn_set_selected_clicked_(self):
00325 selected_items = self._widget.tree_ctrl_settings.selectedItems()
00326
00327 if len( selected_items ) == 0:
00328 QMessageBox.warning(self._widget.tree_ctrl_settings, "Warning", "No motors selected.")
00329
00330 for it in selected_items:
00331 if str(it.text(1)) != "":
00332 self.set_controller( str(it.text(1)) )
00333
00334 def on_btn_set_all_clicked_(self):
00335 for motor in self.ctrl_widgets.keys():
00336 self.set_controller( motor )
00337
00338 def on_btn_refresh_ctrl_clicked_(self):
00339 ctrls = self.sr_controller_tuner_app_.get_ctrls()
00340 self.sr_controller_tuner_app_.refresh_control_mode()
00341 self.controllers_in_dropdown = []
00342 self._widget.dropdown_ctrl.clear()
00343 for ctrl in ctrls:
00344 self._widget.dropdown_ctrl.addItem(ctrl)
00345 self.controllers_in_dropdown.append(ctrl)
00346
00347 self.refresh_controller_tree_()
00348
00349 def read_settings(self, joint_name):
00350 dict_of_widgets = self.ctrl_widgets[joint_name]
00351
00352 settings = {}
00353 for item in dict_of_widgets.items():
00354 if item[0] == "sign":
00355 if item[1].checkState() == Qt.Checked:
00356 settings["sign"] = 1
00357 else:
00358 settings["sign"] = 0
00359 else:
00360 try:
00361 settings[item[0]] = item[1].value()
00362 except AttributeError:
00363 pass
00364
00365 return settings
00366
00367 def set_controller(self, joint_name):
00368 """
00369 Sets the current values for the given controller
00370 using the ros service.
00371 """
00372 settings = self.read_settings( joint_name )
00373
00374
00375 success = self.sr_controller_tuner_app_.set_controller(joint_name, self.controller_type, settings)
00376 if success == False:
00377 if self.controller_type == "Motor Force":
00378 QMessageBox.warning(self._widget.tree_ctrl_settings, "Warning", "Failed to set the PID values for joint "+ joint_name +". This won't work for Gazebo controllers as there are no force controllers yet.")
00379 else:
00380 QMessageBox.warning(self._widget.tree_ctrl_settings, "Warning", "Failed to set the PID values for joint "+ joint_name +".")
00381
00382
00383 def save_controller(self, joint_name):
00384 """
00385 Sets the current values for the given controller
00386 using the ros service.
00387 """
00388 settings = self.read_settings( joint_name )
00389
00390
00391 self.sr_controller_tuner_app_.save_controller(joint_name, self.controller_type, settings, self.file_to_save)
00392
00393
00394 def refresh_controller_tree_(self, controller_type = "Motor Force"):
00395 """
00396 Get the controller settings and their ranges and display them in
00397 the tree.
00398 """
00399
00400 if self.sr_controller_tuner_app_.edit_only_mode:
00401 self._widget.btn_set_selected.setEnabled(False)
00402 self._widget.btn_set_all.setEnabled(False)
00403 self._widget.btn_stop_mvts.setEnabled(False)
00404 else:
00405 self._widget.btn_set_selected.setEnabled(True)
00406 self._widget.btn_set_all.setEnabled(True)
00407 self._widget.btn_stop_mvts.setEnabled(True)
00408
00409 self.controller_type = controller_type
00410 ctrl_settings = self.sr_controller_tuner_app_.get_controller_settings( controller_type )
00411
00412 self._widget.tree_ctrl_settings.clear()
00413 self._widget.tree_ctrl_settings.setColumnCount(ctrl_settings.nb_columns)
00414
00415 tmp_headers = []
00416 for header in ctrl_settings.headers:
00417 tmp_headers.append( header["name"] )
00418 self._widget.tree_ctrl_settings.setHeaderLabels( tmp_headers )
00419
00420 hand_item = QTreeWidgetItem(ctrl_settings.hand_item)
00421 self._widget.tree_ctrl_settings.addTopLevelItem(hand_item)
00422 for index_finger,finger_settings in enumerate(ctrl_settings.fingers):
00423 finger_item = QTreeWidgetItem( hand_item, finger_settings )
00424 self._widget.tree_ctrl_settings.addTopLevelItem(finger_item)
00425 for motor_settings in ctrl_settings.motors[index_finger]:
00426 motor_name = motor_settings[1]
00427
00428 motor_item = QTreeWidgetItem( finger_item, motor_settings )
00429 self._widget.tree_ctrl_settings.addTopLevelItem(motor_item)
00430
00431 parameter_values = self.sr_controller_tuner_app_.load_parameters( controller_type, motor_name )
00432 if parameter_values != -1:
00433
00434 self.ctrl_widgets[ motor_name ] = {}
00435
00436
00437 if not self.sr_controller_tuner_app_.edit_only_mode:
00438
00439 frame_buttons = QFrame()
00440 layout_buttons = QHBoxLayout()
00441 btn_plot = QPushButton("Plot")
00442 self.ctrl_widgets[ motor_name ]["btn_plot"] = btn_plot
00443 self.ctrl_widgets[ motor_name ]["btn_plot"].clicked.connect(partial(self.on_btn_plot_pressed_, motor_name, self.ctrl_widgets[ motor_name ]["btn_plot"]))
00444 layout_buttons.addWidget(btn_plot)
00445
00446 if self.controller_type in ["Position", "Mixed Position/Velocity"]:
00447
00448 btn_move = QPushButton("Move")
00449 self.ctrl_widgets[ motor_name ]["btn_move"] = btn_move
00450 self.ctrl_widgets[ motor_name ]["btn_move"].clicked.connect(partial(self.on_btn_move_pressed_,motor_name, self.ctrl_widgets[ motor_name ]["btn_move"]))
00451 layout_buttons.addWidget(btn_move)
00452 frame_buttons.setLayout(layout_buttons)
00453
00454 self._widget.tree_ctrl_settings.setItemWidget( motor_item, 0, frame_buttons )
00455
00456 for index_item,item in enumerate(ctrl_settings.headers):
00457 if item["type"] == "Bool":
00458 check_box = QCheckBox()
00459
00460 if parameter_values["sign"] == 1.0:
00461 check_box.setChecked(True)
00462
00463 check_box.setToolTip("Check if you want a negative sign\n(if the motor is being driven\n the wrong way around).")
00464 self._widget.tree_ctrl_settings.setItemWidget( motor_item, index_item, check_box )
00465
00466 self.ctrl_widgets[ motor_name ]["sign"] = check_box
00467
00468
00469 if item["type"] == "Int":
00470 spin_box = QSpinBox()
00471 spin_box.setRange(int( item["min"] ), int( item["max"] ))
00472
00473 param_name = item["name"].lower()
00474 spin_box.setValue( int(parameter_values[ param_name ] ) )
00475 self.ctrl_widgets[ motor_name ][param_name] = spin_box
00476
00477 self._widget.tree_ctrl_settings.setItemWidget( motor_item, index_item, spin_box )
00478
00479 if item["type"] == "Float":
00480 spin_box = QDoubleSpinBox()
00481 spin_box.setRange( -65535.0, 65535.0 )
00482 spin_box.setDecimals(3)
00483
00484 param_name = item["name"].lower()
00485 spin_box.setValue(float(parameter_values[param_name]))
00486 self.ctrl_widgets[ motor_name ][param_name] = spin_box
00487
00488 self._widget.tree_ctrl_settings.setItemWidget( motor_item, index_item, spin_box )
00489
00490 motor_item.setExpanded(True)
00491 else:
00492 motor_item.setText(1, "parameters not found - controller tuning disabled")
00493 finger_item.setExpanded(True)
00494 hand_item.setExpanded(True)
00495
00496 for col in range(0, self._widget.tree_ctrl_settings.columnCount()):
00497 self._widget.tree_ctrl_settings.resizeColumnToContents(col)
00498
00499
00500 def on_btn_stop_mvts_clicked_(self):
00501 for move_thread in self.move_threads:
00502 move_thread.__del__()
00503 self.move_threads = []
00504
00505
00506
00507
00508
00509 def _unregisterPublisher(self):
00510 if self._publisher is not None:
00511 self._publisher.unregister()
00512 self._publisher = None
00513
00514 def shutdown_plugin(self):
00515 self._unregisterPublisher()
00516
00517 def save_settings(self, global_settings, perspective_settings):
00518 pass
00519
00520 def restore_settings(self, global_settings, perspective_settings):
00521 pass
00522