00001 import python_qt_binding.QtCore as QtCore
00002 import python_qt_binding.QtGui as QtGui
00003 from python_qt_binding.QtCore import Qt, QTimer, Signal, Slot
00004 from python_qt_binding.QtGui import QHeaderView, QIcon, QMenu, QTreeWidgetItem, QWidget
00005
00006 from qt_gui.plugin import Plugin
00007
00008 import rospy
00009 import rosservice
00010 import rosparam
00011 from rqt_py_common.plugin_container_widget import PluginContainerWidget
00012 from controller_manager_msgs.srv import *
00013
00014 class ControllerManagerGUI(Plugin):
00015
00016 def __init__(self, context):
00017 super(ControllerManagerGUI, self).__init__(context)
00018 self.setObjectName('controller_manager_gui')
00019
00020 self.widget = ControllerManagerWidget(self)
00021
00022 self.main_widget = PluginContainerWidget(self.widget, True, False)
00023
00024 self.widget.start()
00025 if context.serial_number() > 1:
00026 self._widget.setWindowTitle(self.widget.windowTitle() + (' (%d)' % context.serial_number()))
00027 context.add_widget(self.widget)
00028
00029 def shutdown_plugin(self):
00030 self.widget.shutdown_plugin()
00031
00032 class ControllerManagerWidget(QWidget):
00033
00034 _column_names = ['name', 'state', 'type', 'hw_iface', 'resources']
00035 _column_names_pretty = ['Controller Name', 'State', 'Type', 'HW Interface', 'Claimed Resources']
00036
00037 sig_sysmsg = Signal(str)
00038 def __init__(self, plugin):
00039 super(ControllerManagerWidget, self).__init__()
00040
00041 self._plugin = plugin
00042 self.setWindowTitle('Controller Manager')
00043
00044
00045 vlayout_outer = QtGui.QVBoxLayout(self)
00046 vlayout_outer.setObjectName('vert_layout_outer')
00047 hlayout_top = QtGui.QHBoxLayout(self)
00048 hlayout_top.setObjectName('hori_layout_top')
00049 vlayout_outer.addLayout(hlayout_top)
00050
00051
00052
00053 cm_ns_label = QtGui.QLabel(self)
00054 cm_ns_label.setObjectName('cm_ns_label')
00055 cm_ns_label.setText('CM Namespace:')
00056 fixed_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed)
00057 cm_ns_label.setSizePolicy(fixed_policy)
00058 hlayout_top.addWidget(cm_ns_label)
00059 cm_namespace_combo = QtGui.QComboBox(self)
00060 cm_namespace_combo.setObjectName('cm_namespace_combo')
00061 hlayout_top.addWidget(cm_namespace_combo)
00062 self.cm_namespace_combo = cm_namespace_combo
00063
00064
00065 load_ctrl_label = QtGui.QLabel(self)
00066 load_ctrl_label.setObjectName('load_ctrl_label')
00067 load_ctrl_label.setText('Load Controller:')
00068 fixed_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed)
00069 load_ctrl_label.setSizePolicy(fixed_policy)
00070 hlayout_top.addWidget(load_ctrl_label)
00071 load_ctrl_combo = QtGui.QComboBox(self)
00072 load_ctrl_combo.setObjectName('load_ctrl_combo')
00073 load_ctrl_size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
00074 load_ctrl_combo.setSizePolicy(load_ctrl_size_policy)
00075 hlayout_top.addWidget(load_ctrl_combo)
00076 self.load_ctrl_combo = load_ctrl_combo
00077
00078
00079 load_ctrl_button = QtGui.QPushButton(self)
00080 load_ctrl_button.setObjectName('load_ctrl_button')
00081 button_size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
00082 button_size_policy.setHorizontalStretch(0)
00083 button_size_policy.setVerticalStretch(0)
00084 button_size_policy.setHeightForWidth(load_ctrl_button.sizePolicy().hasHeightForWidth())
00085 load_ctrl_button.setSizePolicy(button_size_policy)
00086 load_ctrl_button.setBaseSize(QtCore.QSize(30, 30))
00087 load_ctrl_button.setIcon(QIcon.fromTheme('list-add'))
00088 load_ctrl_button.setIconSize(QtCore.QSize(20,20))
00089 load_ctrl_button.clicked.connect(self.load_cur_ctrl)
00090 hlayout_top.addWidget(load_ctrl_button)
00091
00092
00093 start_ctrl_button = QtGui.QPushButton(self)
00094 start_ctrl_button.setObjectName('start_ctrl_button')
00095 button_size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
00096 button_size_policy.setHorizontalStretch(0)
00097 button_size_policy.setVerticalStretch(0)
00098 button_size_policy.setHeightForWidth(start_ctrl_button.sizePolicy().hasHeightForWidth())
00099 start_ctrl_button.setSizePolicy(button_size_policy)
00100 start_ctrl_button.setBaseSize(QtCore.QSize(30, 30))
00101 start_ctrl_button.setIcon(QIcon.fromTheme('media-playback-start'))
00102 start_ctrl_button.setIconSize(QtCore.QSize(20,20))
00103 start_ctrl_button.clicked.connect(self.start_cur_ctrl)
00104 hlayout_top.addWidget(start_ctrl_button)
00105
00106
00107 ctrl_list_tree_widget = QtGui.QTreeWidget(self)
00108 ctrl_list_tree_widget.setObjectName('ctrl_list_tree_widget')
00109 self.ctrl_list_tree_widget = ctrl_list_tree_widget
00110 ctrl_list_tree_widget.setColumnCount(len(self._column_names))
00111 ctrl_list_tree_widget.setHeaderLabels(self._column_names_pretty)
00112 ctrl_list_tree_widget.sortByColumn(0, Qt.AscendingOrder)
00113 ctrl_list_tree_widget.setContextMenuPolicy(Qt.CustomContextMenu)
00114 ctrl_list_tree_widget.customContextMenuRequested.connect(
00115 self.on_ctrl_list_tree_widget_customContextMenuRequested)
00116 vlayout_outer.addWidget(ctrl_list_tree_widget)
00117
00118 header = self.ctrl_list_tree_widget.header()
00119 header.setResizeMode(QHeaderView.ResizeToContents)
00120 header.customContextMenuRequested.connect(
00121 self.handle_header_view_customContextMenuRequested)
00122 header.setContextMenuPolicy(Qt.CustomContextMenu)
00123
00124 self._ctrlers = {}
00125 self._column_index = {}
00126 for column_name in self._column_names:
00127 self._column_index[column_name] = len(self._column_index)
00128
00129
00130 self.list_types = {}
00131 self.list_ctrlers = {}
00132 self.load_ctrler = {}
00133 self.unload_ctrler = {}
00134 self.switch_ctrlers = {}
00135 self.ctrlman_ns_cur = ''
00136 self.loadable_params = {}
00137
00138
00139 self._timer_refresh_ctrlers = QTimer(self)
00140 self._timer_refresh_ctrlers.timeout.connect(self._refresh_ctrlers_cb)
00141
00142 def controller_manager_connect(self, ctrlman_ns):
00143 self.list_types[ctrlman_ns] = rospy.ServiceProxy(
00144 ctrlman_ns + '/controller_manager/list_controller_types', ListControllerTypes)
00145 self.list_ctrlers[ctrlman_ns] = rospy.ServiceProxy(
00146 ctrlman_ns + '/controller_manager/list_controllers', ListControllers)
00147 self.load_ctrler[ctrlman_ns] = rospy.ServiceProxy(
00148 ctrlman_ns + '/controller_manager/load_controller', LoadController)
00149 self.unload_ctrler[ctrlman_ns] = rospy.ServiceProxy(
00150 ctrlman_ns + '/controller_manager/unload_controller', UnloadController)
00151 self.switch_ctrlers[ctrlman_ns] = rospy.ServiceProxy(
00152 ctrlman_ns + '/controller_manager/switch_controller', SwitchController)
00153 self.cm_namespace_combo.addItem(ctrlman_ns)
00154
00155 def controller_manager_disconnect(self, ctrlman_ns):
00156 self.list_types[ctrlman_ns].close()
00157 del self.list_types[ctrlman_ns]
00158 self.list_ctrlers[ctrlman_ns].close()
00159 del self.list_ctrlers[ctrlman_ns]
00160 self.load_ctrler[ctrlman_ns].close()
00161 del self.load_ctrler[ctrlman_ns]
00162 self.unload_ctrler[ctrlman_ns].close()
00163 del self.unload_ctrler[ctrlman_ns]
00164 self.switch_ctrlers[ctrlman_ns].close()
00165 del self.switch_ctrlers[ctrlman_ns]
00166 combo_ind = self.cm_namespace_combo.findText(ctrlman_ns)
00167 self.cm_namespace_combo.removeItem(combo_ind)
00168
00169 def _refresh_ctrlers_cb(self):
00170 try:
00171
00172 srv_list = rosservice.get_service_list()
00173 ctrlman_ns_list_cur = []
00174 for srv_name in srv_list:
00175 if 'controller_manager/list_controllers' in srv_name:
00176 srv_type = rosservice.get_service_type(srv_name)
00177 if srv_type == 'controller_manager_msgs/ListControllers':
00178 ctrlman_ns = srv_name.split('/controller_manager/list_controllers')[0]
00179 if ctrlman_ns == '':
00180 ctrlman_ns = '/'
00181
00182 if ctrlman_ns not in self.list_ctrlers:
00183
00184 self.controller_manager_connect(ctrlman_ns)
00185 ctrlman_ns_list_cur.append(ctrlman_ns)
00186
00187
00188 for ctrlman_ns_old in self.list_ctrlers.keys():
00189 if ctrlman_ns_old not in ctrlman_ns_list_cur:
00190 self.controller_manager_disconnect(ctrlman_ns_old)
00191
00192
00193 self.refresh_loadable_ctrlers()
00194 self.refresh_ctrlers()
00195 except Exception as e:
00196 self.sig_sysmsg.emit(e.message)
00197
00198 def remove_ctrler_from_list(self, ctrler_name):
00199 item = self._ctrlers[ctrler_name]['item']
00200 index = self.ctrl_list_tree_widget.indexOfTopLevelItem(item)
00201 self.ctrl_list_tree_widget.takeTopLevelItem(index)
00202 del self._ctrlers[ctrler_name]
00203
00204 def remove_loadable_from_list(self, load_text):
00205 load_ctrl_ind = self.load_ctrl_combo.findText(load_text)
00206 self.load_ctrl_combo.removeItem(load_ctrl_ind)
00207 del self.loadable_params[load_text]
00208
00209 def refresh_loadable_ctrlers(self):
00210 if self.cm_namespace_combo.count() == 0:
00211
00212
00213 for old_loadable_text in self.loadable_params.keys():
00214 self.remove_loadable_from_list(old_loadable_text)
00215 return
00216
00217 ctrlman_ns = self.cm_namespace_combo.currentText()
00218
00219 if self.ctrlman_ns_cur != ctrlman_ns:
00220
00221
00222 for old_loadable_text in self.loadable_params.keys():
00223 self.remove_loadable_from_list(old_loadable_text)
00224
00225 rospy.wait_for_service(ctrlman_ns + '/controller_manager/list_controller_types', 0.2)
00226 try:
00227 resp = self.list_types[ctrlman_ns].call(ListControllerTypesRequest())
00228 except rospy.ServiceException as e:
00229
00230 return
00231 ctrler_types = resp.types
00232 loadable_params_cur = []
00233 all_params = rosparam.list_params('/')
00234
00235 for pname in all_params:
00236
00237 if ctrlman_ns == '/':
00238 pname_sub = pname
00239 else:
00240 pname_sub = pname[len(ctrlman_ns):]
00241 psplit = pname_sub.split('/')
00242 if len(psplit) > 2 and psplit[2] == 'type':
00243 loadable_type = rosparam.get_param(pname)
00244 if loadable_type in ctrler_types:
00245 load_text = pname[:-5] + ' - ' + loadable_type
00246 loadable_params_cur.append(load_text)
00247 if load_text not in self.loadable_params:
00248 self.loadable_params[load_text] = psplit[1]
00249 self.load_ctrl_combo.addItem(load_text)
00250
00251
00252 for load_text_old in self.loadable_params.keys():
00253 if load_text_old not in loadable_params_cur:
00254 self.remove_loadable_from_list(load_text_old)
00255
00256 @Slot()
00257 def refresh_ctrlers(self):
00258 if self.cm_namespace_combo.count() == 0:
00259
00260
00261 for old_ctrler_name in self._ctrlers.keys():
00262 self.remove_ctrler_from_list(old_ctrler_name)
00263 return
00264
00265 ctrlman_ns = self.cm_namespace_combo.currentText()
00266
00267 if self.ctrlman_ns_cur != ctrlman_ns:
00268
00269
00270
00271 for old_ctrler_name in self._ctrlers.keys():
00272 self.remove_ctrler_from_list(old_ctrler_name)
00273 self.ctrlman_ns_cur = ctrlman_ns
00274
00275 rospy.wait_for_service(ctrlman_ns + '/controller_manager/list_controllers', 0.2)
00276 try:
00277 resp = self.list_ctrlers[ctrlman_ns].call(ListControllersRequest())
00278 except rospy.ServiceException as e:
00279
00280 return
00281
00282 controller_list = resp.controller
00283 new_ctrlers = {}
00284 for c in controller_list:
00285 if c.name not in self._ctrlers:
00286
00287 item = QTreeWidgetItem(self.ctrl_list_tree_widget)
00288 item.setData(0, Qt.UserRole, c.name)
00289 ctrler = {'item' : item,
00290 'state' : c.state,
00291 'type' : c.type,
00292 'hw_iface' : c.hardware_interface,
00293 'resources' : "[" + ", ".join(c.resources) + "]"}
00294 ctrler['item'].setText(self._column_index['name'], c.name)
00295 update_type = True
00296 update_state = True
00297 else:
00298
00299 ctrler = self._ctrlers[c.name]
00300 update_type = False
00301 update_state = False
00302 if ctrler['type'] != c.type or ctrler['hw_iface'] != c.hardware_interface:
00303
00304 ctrler['state'] = c.state
00305 ctrler['type'] = c.type
00306 ctrler['hw_iface'] = c.hardware_interface
00307 ctrler['resources'] = "[" + ", ".join(c.resources) + "]"
00308 update_type = True
00309 if ctrler['state'] != c.state:
00310
00311 ctrler['state'] = c.state
00312 update_state = True
00313
00314
00315 if update_type:
00316 ctrler['item'].setText(self._column_index['type'], ctrler['type'])
00317 ctrler['item'].setText(self._column_index['hw_iface'], ctrler['hw_iface'])
00318 ctrler['item'].setText(self._column_index['resources'], ctrler['resources'])
00319 if update_state or update_type:
00320 ctrler['item'].setText(self._column_index['state'], ctrler['state'])
00321 new_ctrlers[c.name] = ctrler
00322
00323
00324 for old_ctrler_name in self._ctrlers.keys():
00325 if old_ctrler_name not in new_ctrlers:
00326 self.remove_ctrler_from_list(old_ctrler_name)
00327
00328
00329 self._ctrlers = new_ctrlers
00330
00331 def start(self):
00332 self._timer_refresh_ctrlers.start(1000)
00333
00334 @Slot('QPoint')
00335 def handle_header_view_customContextMenuRequested(self, pos):
00336 header = self.ctrl_list_tree_widget.header()
00337
00338
00339 menu = QMenu(self)
00340 action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
00341 action = menu.exec_(header.mapToGlobal(pos))
00342
00343
00344 if action is action_toggle_auto_resize:
00345 if header.resizeMode(0) == QHeaderView.ResizeToContents:
00346 header.setResizeMode(QHeaderView.Interactive)
00347 else:
00348 header.setResizeMode(QHeaderView.ResizeToContents)
00349
00350 def load_cur_ctrl(self):
00351 ctrlman_ns = self.cm_namespace_combo.currentText()
00352 load_text = self.load_ctrl_combo.currentText()
00353 load_param = self.loadable_params[load_text]
00354 self.load_controller(ctrlman_ns, load_param)
00355
00356 def start_cur_ctrl(self):
00357 ctrlman_ns = self.cm_namespace_combo.currentText()
00358 load_text = self.load_ctrl_combo.currentText()
00359 load_param = self.loadable_params[load_text]
00360 self.load_controller(ctrlman_ns, load_param)
00361 self.start_stop_controller(ctrlman_ns, load_param, True)
00362
00363 @Slot('QPoint')
00364 def on_ctrl_list_tree_widget_customContextMenuRequested(self, pos):
00365 ctrlman_ns = self.cm_namespace_combo.currentText()
00366 item = self.ctrl_list_tree_widget.itemAt(pos)
00367 if item is None:
00368 return
00369 ctrl_name = item.data(0, Qt.UserRole)
00370 ctrl_state = self._ctrlers[ctrl_name]['state']
00371
00372
00373 menu = QMenu(self)
00374 if ctrl_state == 'running':
00375 action_stop = menu.addAction(
00376 QIcon.fromTheme('media-playback-stop'), 'Stop Controller')
00377 action_kill = menu.addAction(
00378 QIcon.fromTheme('list-remove'), 'Stop and Unload Controller')
00379 elif ctrl_state == 'stopped':
00380 action_start = menu.addAction(
00381 QIcon.fromTheme('media-playback-start'), 'Start Controller')
00382 action_unload = menu.addAction(
00383 QIcon.fromTheme('list-remove'), 'Unload Controller')
00384
00385 action = menu.exec_(self.ctrl_list_tree_widget.mapToGlobal(pos))
00386
00387
00388 if ctrl_state == 'running':
00389 if action is action_stop:
00390 self.start_stop_controller(ctrlman_ns, ctrl_name, False)
00391 elif action is action_kill:
00392 self.start_stop_controller(ctrlman_ns, ctrl_name, False)
00393 self.unload_controller(ctrlman_ns, ctrl_name)
00394 elif ctrl_state == 'stopped':
00395 if action is action_start:
00396 self.start_stop_controller(ctrlman_ns, ctrl_name, True)
00397 elif action is action_unload:
00398 self.unload_controller(ctrlman_ns, ctrl_name)
00399
00400 def load_controller(self, ctrlman_ns, name):
00401 rospy.wait_for_service(ctrlman_ns + '/controller_manager/load_controller', 0.2)
00402 try:
00403 resp = self.load_ctrler[ctrlman_ns].call(LoadControllerRequest(name))
00404 if resp.ok == 1:
00405
00406 return 0
00407 else:
00408
00409 return 1
00410 except rospy.ServiceException as e:
00411
00412 return 2
00413
00414 def unload_controller(self, ctrlman_ns, name):
00415 rospy.wait_for_service(ctrlman_ns + '/controller_manager/unload_controller', 0.2)
00416 try:
00417 resp = self.unload_ctrler[ctrlman_ns].call(UnloadControllerRequest(name))
00418 if resp.ok == 1:
00419
00420 return 0
00421 else:
00422
00423 return 1
00424 except rospy.ServiceException as e:
00425
00426 return 2
00427
00428 def start_stop_controller(self, ctrlman_ns, name, is_start):
00429 rospy.wait_for_service(ctrlman_ns + '/controller_manager/switch_controller', 0.2)
00430 start = []
00431 stop = []
00432 strictness = SwitchControllerRequest.STRICT
00433 if is_start:
00434 start = [name]
00435 else:
00436 stop = [name]
00437 try:
00438 resp = self.switch_ctrlers[ctrlman_ns].call(
00439 SwitchControllerRequest(start, stop, strictness))
00440 if resp.ok == 1:
00441
00442 return 0
00443 else:
00444
00445 return 1
00446 except rospy.ServiceException as e:
00447
00448 return 2
00449
00450 def shutdown_plugin(self):
00451 for ctrlman_ns in self.list_ctrlers.keys():
00452 self.controller_manager_disconnect(ctrlman_ns)
00453 self._timer_refresh_ctrlers.stop()
00454