From 95494cd68f01fda463436d408c9b5de90064e7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Fr=C3=B6hlich?= Date: Tue, 25 Jun 2024 13:03:22 +0200 Subject: [PATCH] [rqt_controller_manager] Add hardware components (#1455) (cherry picked from commit 7ffe1b7b5cd39c69d6e687f4021dac65c963cb21) --- .../doc/images/rqt_controller_manager.png | Bin 0 -> 49114 bytes controller_manager/doc/userdoc.rst | 15 + rqt_controller_manager/package.xml | 5 +- .../resource/controller_manager.ui | 36 ++- .../{controller_info.ui => popup_info.ui} | 3 - .../controller_manager.py | 305 +++++++++++++++--- 6 files changed, 315 insertions(+), 49 deletions(-) create mode 100644 controller_manager/doc/images/rqt_controller_manager.png rename rqt_controller_manager/resource/{controller_info.ui => popup_info.ui} (96%) diff --git a/controller_manager/doc/images/rqt_controller_manager.png b/controller_manager/doc/images/rqt_controller_manager.png new file mode 100644 index 0000000000000000000000000000000000000000..01c4f55bdf1021623377c893c95e6240d67b24ed GIT binary patch literal 49114 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 K+s`yT68m4dwC~dZ literal 0 HcmV?d00001 diff --git a/controller_manager/doc/userdoc.rst b/controller_manager/doc/userdoc.rst index b9c057f571..9c2e3a5990 100644 --- a/controller_manager/doc/userdoc.rst +++ b/controller_manager/doc/userdoc.rst @@ -127,6 +127,21 @@ There are two scripts to interact with controller manager from launch files: -c CONTROLLER_MANAGER, --controller-manager CONTROLLER_MANAGER Name of the controller manager ROS node +rqt_controller_manager +---------------------- +A GUI tool to interact with the controller manager services to be able to switch the lifecycle states of the controllers as well as the hardware components. + +.. image:: images/rqt_controller_manager.png + +It can be launched independently using the following command or as rqt plugin. + +.. code-block:: console + + ros2 run rqt_controller_manager rqt_controller_manager + + * Double-click on a controller or hardware component to show the additional info. + * Right-click on a controller or hardware component to show a context menu with options for lifecycle management. + Using the Controller Manager in a Process ----------------------------------------- diff --git a/rqt_controller_manager/package.xml b/rqt_controller_manager/package.xml index b4a7abfa9c..f411f3a1f2 100644 --- a/rqt_controller_manager/package.xml +++ b/rqt_controller_manager/package.xml @@ -12,11 +12,12 @@ https://github.com/ros-controls/ros2_control/issues https://github.com/ros-controls/ros2_control + Adolfo Rodríguez Tsouroukdissian Bence Magyar + Christoph Froehlich Enrique Fernandez - Mathias Lüdtke Kelsey Hawkins - Adolfo Rodríguez Tsouroukdissian + Mathias Lüdtke controller_manager controller_manager_msgs diff --git a/rqt_controller_manager/resource/controller_manager.ui b/rqt_controller_manager/resource/controller_manager.ui index 2ede975248..17031d9044 100644 --- a/rqt_controller_manager/resource/controller_manager.ui +++ b/rqt_controller_manager/resource/controller_manager.ui @@ -37,7 +37,7 @@ - + 0 @@ -70,6 +70,40 @@ + + + + + 0 + 0 + + + + true + + + QAbstractItemView::NoSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + true + + + false + + + diff --git a/rqt_controller_manager/resource/controller_info.ui b/rqt_controller_manager/resource/popup_info.ui similarity index 96% rename from rqt_controller_manager/resource/controller_info.ui rename to rqt_controller_manager/resource/popup_info.ui index b8c03799a9..b910397a8d 100644 --- a/rqt_controller_manager/resource/controller_info.ui +++ b/rqt_controller_manager/resource/popup_info.ui @@ -16,9 +16,6 @@ 0 - - Controller Information - diff --git a/rqt_controller_manager/rqt_controller_manager/controller_manager.py b/rqt_controller_manager/rqt_controller_manager/controller_manager.py index d142ea847b..3b4a9a7de1 100644 --- a/rqt_controller_manager/rqt_controller_manager/controller_manager.py +++ b/rqt_controller_manager/rqt_controller_manager/controller_manager.py @@ -20,12 +20,16 @@ from controller_manager.controller_manager_services import ( configure_controller, list_controllers, + list_hardware_components, + set_hardware_component_state, load_controller, switch_controllers, unload_controller, ) + from controller_manager_msgs.msg import ControllerState from controller_manager_msgs.srv import SwitchController +from lifecycle_msgs.msg import State from python_qt_binding import loadUi from python_qt_binding.QtCore import QAbstractTableModel, Qt, QTimer from python_qt_binding.QtGui import QCursor, QFont, QIcon, QStandardItem, QStandardItemModel @@ -60,7 +64,7 @@ def __init__(self, context): # Pop-up that displays controller information self._popup_widget = QWidget() ui_file = os.path.join( - get_package_share_directory("rqt_controller_manager"), "resource", "controller_info.ui" + get_package_share_directory("rqt_controller_manager"), "resource", "popup_info.ui" ) loadUi(ui_file, self._popup_widget) self._popup_widget.setObjectName("ControllerInfoUi") @@ -78,7 +82,9 @@ def __init__(self, context): # Initialize members self._cm_name = "" # Name of the selected controller manager's node self._controllers = [] # State of each controller - self._table_model = None + self._hw_components = [] # State of each hw component + self._ctrl_table_model = None + self._hw_table_model = None # Store reference to node self._node = context.node @@ -93,16 +99,26 @@ def __init__(self, context): } # Controllers display - table_view = self._widget.table_view - table_view.setContextMenuPolicy(Qt.CustomContextMenu) - table_view.customContextMenuRequested.connect(self._on_ctrl_menu) - - table_view.doubleClicked.connect(self._on_ctrl_info) - - header = table_view.horizontalHeader() - header.setSectionResizeMode(QHeaderView.ResizeToContents) - header.setContextMenuPolicy(Qt.CustomContextMenu) - header.customContextMenuRequested.connect(self._on_header_menu) + ctrl_table_view = self._widget.ctrl_table_view + ctrl_table_view.setContextMenuPolicy(Qt.CustomContextMenu) + ctrl_table_view.customContextMenuRequested.connect(self._on_ctrl_menu) + ctrl_table_view.doubleClicked.connect(self._on_ctrl_info) + + ctrl_header = ctrl_table_view.horizontalHeader() + ctrl_header.setSectionResizeMode(QHeaderView.ResizeToContents) + ctrl_header.setContextMenuPolicy(Qt.CustomContextMenu) + ctrl_header.customContextMenuRequested.connect(self._on_ctrl_header_menu) + + # Hardware components display + hw_table_view = self._widget.hw_table_view + hw_table_view.setContextMenuPolicy(Qt.CustomContextMenu) + hw_table_view.customContextMenuRequested.connect(self._on_hw_menu) + hw_table_view.doubleClicked.connect(self._on_hw_info) + + hw_header = hw_table_view.horizontalHeader() + hw_header.setSectionResizeMode(QHeaderView.ResizeToContents) + hw_header.setContextMenuPolicy(Qt.CustomContextMenu) + hw_header.customContextMenuRequested.connect(self._on_hw_header_menu) # Timer for controller manager updates self._update_cm_list_timer = QTimer(self) @@ -116,6 +132,12 @@ def __init__(self, context): self._update_ctrl_list_timer.timeout.connect(self._update_controllers) self._update_ctrl_list_timer.start() + # Timer for running hw components updates + self._update_hw_components_list_timer = QTimer(self) + self._update_hw_components_list_timer.setInterval(int(1000.0 / self._cm_update_freq)) + self._update_hw_components_list_timer.timeout.connect(self._update_hw_components) + self._update_hw_components_list_timer.start() + # Signal connections w = self._widget w.cm_combo.currentIndexChanged[str].connect(self._on_cm_change) @@ -148,6 +170,7 @@ def _on_cm_change(self, cm_name): if cm_name: self._update_controllers() + self._update_hw_components() def _update_controllers(self): @@ -191,20 +214,20 @@ def _list_controllers(self): return [] def _show_controllers(self): - table_view = self._widget.table_view - self._table_model = ControllerTable(self._controllers, self._icons) - table_view.setModel(self._table_model) + ctrl_table_view = self._widget.ctrl_table_view + self._ctrl_table_model = ControllerTable(self._controllers, self._icons) + ctrl_table_view.setModel(self._ctrl_table_model) def _on_ctrl_menu(self, pos): # Get data of selected controller - row = self._widget.table_view.rowAt(pos.y()) + row = self._widget.ctrl_table_view.rowAt(pos.y()) if row < 0: return # Cursor is not under a valid item ctrl = self._controllers[row] # Show context menu - menu = QMenu(self._widget.table_view) + menu = QMenu(self._widget.ctrl_table_view) if ctrl.state == "active": action_deactivate = menu.addAction(self._icons["inactive"], "Deactivate") action_kill = menu.addAction(self._icons["finalized"], "Deactivate and Unload") @@ -220,7 +243,7 @@ def _on_ctrl_menu(self, pos): action_configure = menu.addAction(self._icons["inactive"], "Load and Configure") action_activate = menu.addAction(self._icons["active"], "Load, Configure and Activate") - action = menu.exec_(self._widget.table_view.mapToGlobal(pos)) + action = menu.exec_(self._widget.ctrl_table_view.mapToGlobal(pos)) # Evaluate user action if ctrl.state == "active": @@ -254,6 +277,7 @@ def _on_ctrl_menu(self, pos): def _on_ctrl_info(self, index): popup = self._popup_widget + popup.setWindowTitle("Controller Information") ctrl = self._controllers[index.row()] popup.ctrl_name.setText(ctrl.name) @@ -272,42 +296,186 @@ def _on_ctrl_info(self, index): popup.move(QCursor.pos()) popup.show() - def _on_header_menu(self, pos): - header = self._widget.table_view.horizontalHeader() + def _on_ctrl_header_menu(self, pos): + ctrl_header = self._widget.ctrl_table_view.horizontalHeader() + + # Show context menu + menu = QMenu(self._widget.ctrl_table_view) + action_toggle_auto_resize = menu.addAction("Toggle Auto-Resize") + action = menu.exec_(ctrl_header.mapToGlobal(pos)) + + # Evaluate user action + if action is action_toggle_auto_resize: + if ctrl_header.resizeMode(0) == QHeaderView.ResizeToContents: + ctrl_header.setSectionResizeMode(QHeaderView.Interactive) + else: + ctrl_header.setSectionResizeMode(QHeaderView.ResizeToContents) + + def _update_hw_components(self): + if not self._cm_name: + return + + # Find hw_components associated to the selected controller manager + hw_components = self._list_hw_components() + + # Update controller display, if necessary + if self._hw_components != hw_components: + self._hw_components = hw_components + self._show_hw_components() # NOTE: Model is recomputed from scratch + + def _list_hw_components(self): + """ + List the hw_components associated to a controller manager node. + + @return List of hw_components associated to a controller manager + node. Contains both stopped/running hw_components, as returned by + the C{list_hardware_components} service + @rtype [str] + """ + # Add loaded hw_components first + try: + hw_components = list_hardware_components( + self._node, self._cm_name, 2.0 / self._cm_update_freq + ).component + return hw_components + except RuntimeError as e: + print(e) + return [] + + def _show_hw_components(self): + hw_table_view = self._widget.hw_table_view + self._hw_table_model = HwComponentTable(self._hw_components, self._icons) + hw_table_view.setModel(self._hw_table_model) + + def _on_hw_menu(self, pos): + # Get data of selected controller + row = self._widget.hw_table_view.rowAt(pos.y()) + if row < 0: + return # Cursor is not under a valid item + + hw_component = self._hw_components[row] + + # Show context menu + menu = QMenu(self._widget.hw_table_view) + if hw_component.state.label == "active": + action_deactivate = menu.addAction(self._icons["inactive"], "Deactivate") + action_cleanup = menu.addAction(self._icons["finalized"], "Deactivate and Cleanup") + elif hw_component.state.label == "inactive": + action_activate = menu.addAction(self._icons["active"], "Activate") + action_cleanup = menu.addAction(self._icons["unconfigured"], "Cleanup") + elif hw_component.state.label == "unconfigured": + action_configure = menu.addAction(self._icons["inactive"], "Configure") + action_spawn = menu.addAction(self._icons["active"], "Configure and Activate") + + action = menu.exec_(self._widget.hw_table_view.mapToGlobal(pos)) + + # Evaluate user action + if hw_component.state.label == "active": + if action is action_deactivate: + self._set_inactive_hw_component(hw_component.name) + elif action is action_cleanup: + self._set_unconfigured_hw_component(hw_component.name) + elif hw_component.state.label == "inactive": + if action is action_activate: + self._set_active_hw_component(hw_component.name) + elif action is action_cleanup: + self._set_unconfigured_hw_component(hw_component.name) + elif hw_component.state.label == "unconfigured": + if action is action_configure: + self._set_inactive_hw_component(hw_component.name) + elif action is action_spawn: + self._set_active_hw_component(hw_component.name) + + def _on_hw_info(self, index): + popup = self._popup_widget + popup.setWindowTitle("Hardware Component Info") + + hw_component = self._hw_components[index.row()] + popup.ctrl_name.setText(hw_component.name) + popup.ctrl_type.setText(hw_component.type) + + res_model = QStandardItemModel() + model_root = QStandardItem("Command Interfaces") + res_model.appendRow(model_root) + for command_interface in hw_component.command_interfaces: + hw_iface_item = QStandardItem(command_interface.name) + model_root.appendRow(hw_iface_item) + + model_root = QStandardItem("State Interfaces") + res_model.appendRow(model_root) + for state_interface in hw_component.state_interfaces: + hw_iface_item = QStandardItem(state_interface.name) + model_root.appendRow(hw_iface_item) + + popup.resource_tree.setModel(res_model) + popup.resource_tree.setItemDelegate(FontDelegate(popup.resource_tree)) + popup.resource_tree.expandAll() + popup.move(QCursor.pos()) + popup.show() + + def _on_hw_header_menu(self, pos): + hw_header = self._widget.hw_table_view.horizontalHeader() # Show context menu - menu = QMenu(self._widget.table_view) + menu = QMenu(self._widget.hw_table_view) action_toggle_auto_resize = menu.addAction("Toggle Auto-Resize") - action = menu.exec_(header.mapToGlobal(pos)) + action = menu.exec_(hw_header.mapToGlobal(pos)) # Evaluate user action if action is action_toggle_auto_resize: - if header.resizeMode(0) == QHeaderView.ResizeToContents: - header.setSectionResizeMode(QHeaderView.Interactive) + if hw_header.resizeMode(0) == QHeaderView.ResizeToContents: + hw_header.setSectionResizeMode(QHeaderView.Interactive) else: - header.setSectionResizeMode(QHeaderView.ResizeToContents) + hw_header.setSectionResizeMode(QHeaderView.ResizeToContents) def _activate_controller(self, name): - switch_controllers( - node=self._node, - controller_manager_name=self._cm_name, - deactivate_controllers=[], - activate_controllers=[name], - strict=SwitchController.Request.STRICT, - activate_asap=False, - timeout=0.3, - ) + self._switch_controllers([name], []) def _deactivate_controller(self, name): - switch_controllers( - node=self._node, - controller_manager_name=self._cm_name, - deactivate_controllers=[name], - activate_controllers=[], - strict=SwitchController.Request.STRICT, - activate_asap=False, - timeout=0.3, - ) + self._switch_controllers([], [name]) + + def _switch_controllers(self, activate, deactivate): + try: + switch_controllers( + node=self._node, + controller_manager_name=self._cm_name, + activate_controllers=activate, + deactivate_controllers=deactivate, + strict=SwitchController.Request.STRICT, + activate_asap=False, + timeout=0.3, + ) + except Exception as e: + print(e) + + def _set_active_hw_component(self, name): + active_state = State() + active_state.id = State.PRIMARY_STATE_ACTIVE + active_state.label = "active" + self._set_state_hw_component(name, active_state) + + def _set_inactive_hw_component(self, name): + inactive_state = State() + inactive_state.id = State.PRIMARY_STATE_INACTIVE + inactive_state.label = "inactive" + self._set_state_hw_component(name, inactive_state) + + def _set_unconfigured_hw_component(self, name): + unconfigure_state = State() + unconfigure_state.id = State.PRIMARY_STATE_UNCONFIGURED + unconfigure_state.label = "unconfigured" + self._set_state_hw_component(name, unconfigure_state) + + def _set_state_hw_component(self, name, state): + try: + set_hardware_component_state( + node=self._node, + controller_manager_name=self._cm_name, + component_name=name, + lifecyle_state=state, + ) + except Exception as e: + print(e) class ControllerTable(QAbstractTableModel): @@ -361,6 +529,57 @@ def data(self, index, role): return Qt.AlignCenter +class HwComponentTable(QAbstractTableModel): + """ + Model containing hardware component information for tabular display. + + The model allows display of basic read-only information like component + name and state. + """ + + def __init__(self, hw_component_info, icons, parent=None): + QAbstractTableModel.__init__(self, parent) + self._data = hw_component_info + self._icons = icons + + def rowCount(self, parent): + return len(self._data) + + def columnCount(self, parent): + return 2 + + def headerData(self, col, orientation, role): + if orientation != Qt.Horizontal or role != Qt.DisplayRole: + return None + if col == 0: + return "component" + elif col == 1: + return "state" + + def data(self, index, role): + if not index.isValid(): + return None + + hw_component = self._data[index.row()] + + if role == Qt.DisplayRole: + if index.column() == 0: + return hw_component.name + elif index.column() == 1: + return hw_component.state.label or "not loaded" + + if role == Qt.DecorationRole and index.column() == 0: + return self._icons.get(hw_component.state.label) + + if role == Qt.FontRole and index.column() == 0: + bf = QFont() + bf.setBold(True) + return bf + + if role == Qt.TextAlignmentRole and index.column() == 1: + return Qt.AlignCenter + + class FontDelegate(QStyledItemDelegate): """ Simple delegate for customizing font weight and italization.