Package node_manager_fkie :: Module main_window
[frames] | no frames]

Source Code for Module node_manager_fkie.main_window

   1  # encoding: utf-8
 
   2  #
 
   3  # Software License Agreement (BSD License)
 
   4  #
 
   5  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko
 
   6  # All rights reserved.
 
   7  #
 
   8  # Redistribution and use in source and binary forms, with or without
 
   9  # modification, are permitted provided that the following conditions
 
  10  # are met:
 
  11  #
 
  12  #  * Redistributions of source code must retain the above copyright
 
  13  #    notice, this list of conditions and the following disclaimer.
 
  14  #  * Redistributions in binary form must reproduce the above
 
  15  #    copyright notice, this list of conditions and the following
 
  16  #    disclaimer in the documentation and/or other materials provided
 
  17  #    with the distribution.
 
  18  #  * Neither the name of Fraunhofer nor the names of its
 
  19  #    contributors may be used to endorse or promote products derived
 
  20  #    from this software without specific prior written permission.
 
  21  #
 
  22  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
  23  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
  24  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 
  25  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 
  26  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 
  27  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 
  28  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 
  29  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 
  30  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 
  31  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 
  32  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
  33  # POSSIBILITY OF SUCH DAMAGE.
 
  34  
 
  35  from datetime import datetime 
  36  from multimaster_msgs_fkie.msg import MasterState 
  37  from python_qt_binding import loadUi, QT_BINDING_VERSION 
  38  from python_qt_binding.QtCore import QFile, QPoint, QSize, Qt, QTimer, Signal 
  39  from python_qt_binding.QtGui import QDesktopServices, QIcon, QKeySequence, QPixmap 
  40  from python_qt_binding.QtGui import QPalette, QColor 
  41  import getpass 
  42  import os 
  43  import roslib 
  44  import rospy 
  45  import socket 
  46  import time 
  47  import uuid 
  48  import xmlrpclib 
  49  
 
  50  from master_discovery_fkie.common import get_hostname, resolve_url, subdomain 
  51  
 
  52  import node_manager_fkie as nm 
  53  
 
  54  from .capability_table import CapabilityTable 
  55  from .common import masteruri_from_ros, package_name, utf8 
  56  from .detailed_msg_box import MessageBox 
  57  from .discovery_listener import MasterListService, MasterStateTopic, MasterStatisticTopic, OwnMasterMonitoring 
  58  from .editor import Editor 
  59  from .launch_config import LaunchConfig  # , LaunchConfigException 
  60  from .launch_files_widget import LaunchFilesWidget 
  61  from .log_widget import LogWidget 
  62  from .master_list_model import MasterModel 
  63  from .master_view_proxy import MasterViewProxy 
  64  from .menu_rqt import MenuRqt 
  65  from .network_discovery_dialog import NetworkDiscoveryDialog 
  66  from .parameter_dialog import ParameterDialog 
  67  from .profile_widget import ProfileWidget 
  68  from .progress_queue import ProgressQueue  # , ProgressThread 
  69  from .screen_handler import ScreenHandler 
  70  from .select_dialog import SelectDialog 
  71  from .settings_widget import SettingsWidget 
  72  from .sync_dialog import SyncDialog 
  73  from .update_handler import UpdateHandler 
  74  
 
  75  
 
  76  try: 
  77      from python_qt_binding.QtGui import QApplication, QFileDialog, QMainWindow, QStackedLayout, QWidget 
  78      from python_qt_binding.QtGui import QShortcut, QVBoxLayout, QColorDialog, QDialog, QRadioButton 
  79  except: 
  80      from python_qt_binding.QtWidgets import QApplication, QFileDialog, QMainWindow, QStackedLayout, QWidget 
  81      from python_qt_binding.QtWidgets import QShortcut, QVBoxLayout, QColorDialog, QDialog, QRadioButton 
  82  
 
  83  
 
  84  try: 
  85      import gui_resources 
  86  except: 
  87      print "no gui resources :-/" 
  88  
 
  89  # from python_qt_binding import QtUiTools
 
  90  try: 
  91      from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus 
  92      DIAGNOSTICS_AVAILABLE = True 
  93  except: 
  94      import sys 
  95      print >> sys.stderr, "Cannot import 'diagnostic_msgs', feature disabled." 
  96      DIAGNOSTICS_AVAILABLE = False 
  97  
 
  98  
 
99 -class MainWindow(QMainWindow):
100 ''' 101 The class to create the main window of the application. 102 ''' 103 DELAYED_NEXT_REQ_ON_ERR = 5.0 104 105 if DIAGNOSTICS_AVAILABLE: 106 diagnostics_signal = Signal(DiagnosticStatus) 107 '''@ivar: the signal is emitted if a message on topic nm_notifier was 108 reiceved (DiagnosticStatus)''' 109
110 - def __init__(self, files=[], restricted_to_one_master=False, parent=None):
111 ''' 112 Creates the window, connects the signals and init the class. 113 ''' 114 QMainWindow.__init__(self) 115 self.default_load_launch = os.path.abspath(resolve_url(files[0])) if files else '' 116 self.default_profile_load = os.path.isfile(self.default_load_launch) and self.default_load_launch.endswith('.nmprofile') 117 restricted_to_one_master = False 118 self._finished = False 119 self._history_selected_robot = '' 120 self.__icons = {'empty': (QIcon(), ''), 121 'default_pc': (QIcon(':/icons/crystal_clear_miscellaneous.png'), ':/icons/crystal_clear_miscellaneous.png'), 122 'log_warning': (QIcon(':/icons/crystal_clear_no_io.png'), ':/icons/crystal_clear_no_io.png'), 123 'show_io': (QIcon(':/icons/crystal_clear_show_io.png'), ':/icons/crystal_clear_show_io.png') 124 } # (masnter name : (QIcon, path)) 125 self.__current_icon = None 126 self.__current_master_label_name = None 127 self._changed_files = dict() 128 self._changed_binaries = dict() 129 self._changed_files_param = dict() 130 self._syncs_to_start = [] # hostnames 131 self._accept_next_update = False 132 # self.setAttribute(Qt.WA_AlwaysShowToolTips, True) 133 # setup main window frame 134 self.setObjectName('MainWindow') 135 # self = mainWindow = QMainWindow() 136 # self = mainWindow = loader.load(":/forms/MainWindow.ui") 137 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'MainWindow.ui') 138 loadUi(ui_file, self) 139 self.setObjectName('MainUI') 140 self.setDockOptions(QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks | QMainWindow.AnimatedDocks | QMainWindow.VerticalTabs) 141 self.user_frame.setVisible(False) 142 self._add_user_to_combo(getpass.getuser()) 143 self.userComboBox.editTextChanged.connect(self.on_user_changed) 144 self.masterInfoFrame.setEnabled(False) 145 self.infoButton.clicked.connect(self.on_info_clicked) 146 self.setTimeButton.clicked.connect(self.on_set_time_clicked) 147 self.refreshHostButton.clicked.connect(self.on_refresh_master_clicked) 148 self.masterLogButton.clicked.connect(self.on_master_log_clicked) 149 self.runButton.clicked.connect(self.on_run_node_clicked) 150 self.syncButton.released.connect(self.on_sync_dialog_released) 151 152 menu_rqt = MenuRqt(self.rqtButton) 153 menu_rqt.start_rqt_plugin_signal.connect(self.on_rqt_plugin_start) 154 155 pal = self.expert_tab.palette() 156 self._default_color = pal.color(QPalette.Window) 157 self._set_custom_colors() 158 159 # setup settings widget 160 self.profiler = ProfileWidget(self, self) 161 self.addDockWidget(Qt.LeftDockWidgetArea, self.profiler) 162 self.settings_dock = SettingsWidget(self) 163 self.addDockWidget(Qt.LeftDockWidgetArea, self.settings_dock) 164 # setup logger widget 165 self.log_dock = LogWidget() 166 self.log_dock.added_signal.connect(self._on_log_added) 167 self.log_dock.cleared_signal.connect(self._on_log_cleared) 168 self.log_dock.setVisible(False) 169 self.addDockWidget(Qt.BottomDockWidgetArea, self.log_dock) 170 self.logButton.clicked.connect(self._on_log_button_clicked) 171 # setup the launch files view 172 self.launch_dock = LaunchFilesWidget() 173 self.launch_dock.load_signal.connect(self.on_load_launch_file) 174 self.launch_dock.load_profile_signal.connect(self.profiler.on_load_profile_file) 175 self.launch_dock.load_as_default_signal.connect(self.on_load_launch_as_default) 176 self.launch_dock.edit_signal.connect(self.on_launch_edit) 177 self.launch_dock.transfer_signal.connect(self.on_launch_transfer) 178 self.addDockWidget(Qt.LeftDockWidgetArea, self.launch_dock) 179 180 self.mIcon = QIcon(":/icons/crystal_clear_prop_run.png") 181 self.setWindowIcon(self.mIcon) 182 self.setWindowTitle("Node Manager") 183 # self.setCentralWidget(mainWindow) 184 185 # init the stack layout which contains the information about different ros master 186 self.stackedLayout = QStackedLayout() 187 self.stackedLayout.setObjectName('stackedLayout') 188 emptyWidget = QWidget() 189 emptyWidget.setObjectName('emptyWidget') 190 self.stackedLayout.addWidget(emptyWidget) 191 self.tabWidget.currentChanged.connect(self.on_currentChanged_tab) 192 self.tabLayout = QVBoxLayout(self.tabPlace) 193 self.tabLayout.setObjectName("tabLayout") 194 self.tabLayout.setContentsMargins(0, 0, 0, 0) 195 self.tabLayout.addLayout(self.stackedLayout) 196 197 # initialize the progress queue 198 self._progress_queue = ProgressQueue(self.progressFrame, self.progressBar, self.progressCancelButton, 'Network') 199 self._progress_queue_sync = ProgressQueue(self.progressFrame_sync, self.progressBar_sync, self.progressCancelButton_sync, 'Sync') 200 201 # initialize the view for the discovered ROS master 202 self.master_model = MasterModel(self.getMasteruri()) 203 self.master_model.sync_start.connect(self.on_sync_start) 204 self.master_model.sync_stop.connect(self.on_sync_stop) 205 self.masterTableView.setModel(self.master_model) 206 self.master_model.parent_view = self.masterTableView 207 # self.masterTableView.setAlternatingRowColors(True) 208 # self.masterTableView.clicked.connect(self.on_master_table_clicked) 209 # self.masterTableView.pressed.connect(self.on_master_table_pressed) 210 self.masterTableView.activated.connect(self.on_master_table_activated) 211 sm = self.masterTableView.selectionModel() 212 sm.currentRowChanged.connect(self.on_masterTableView_selection_changed) 213 for i, (_, width) in enumerate(MasterModel.header): # _:=name 214 self.masterTableView.setColumnWidth(i, width) 215 self.refreshAllButton.clicked.connect(self.on_all_master_refresh_clicked) 216 self.discoveryButton.clicked.connect(self.on_discover_network_clicked) 217 self.startRobotButton.clicked.connect(self.on_start_robot_clicked) 218 219 # stores the widget to a 220 self.masters = dict() # masteruri : MasterViewProxy 221 self.currentMaster = None # MasterViewProxy 222 self._close_on_exit = True 223 224 nm.filewatcher().config_changed.connect(self.on_configfile_changed) 225 nm.filewatcher().binary_changed.connect(self.on_binaryfile_changed) 226 nm.file_watcher_param().config_changed.connect(self.on_configparamfile_changed) 227 self.__in_question = set() 228 229 ############################################################################ 230 self.capabilitiesTable = CapabilityTable(self.capabilities_tab) 231 self.capabilitiesTable.setObjectName("capabilitiesTable") 232 self.capabilitiesTable.start_nodes_signal.connect(self.on_start_nodes) 233 self.capabilitiesTable.stop_nodes_signal.connect(self.on_stop_nodes) 234 self.capabilitiesTable.description_requested_signal.connect(self.on_description_update_cap) 235 self.capabilities_tab.layout().addWidget(self.capabilitiesTable) 236 237 self.descriptionTextEdit.setOpenLinks(False) 238 self.descriptionTextEdit.anchorClicked.connect(self.on_description_anchorClicked) 239 self._shortcut_copy = QShortcut(QKeySequence(self.tr("Ctrl+Shift+C", "copy selected description")), self.descriptionTextEdit) 240 self._shortcut_copy.activated.connect(self.descriptionTextEdit.copy) 241 242 self.tabifyDockWidget(self.launch_dock, self.descriptionDock) 243 self.tabifyDockWidget(self.launch_dock, self.settings_dock) 244 self.tabifyDockWidget(self.launch_dock, self.helpDock) 245 self.launch_dock.raise_() 246 self.helpDock.setWindowIcon(QIcon(':icons/crystal_clear_helpcenter.png')) 247 248 flags = self.windowFlags() 249 self.setWindowFlags(flags | Qt.WindowContextHelpButtonHint) 250 251 if self.default_load_launch: 252 if os.path.isdir(self.default_load_launch): 253 self.launch_dock.launchlist_model.setPath(self.default_load_launch) 254 elif os.path.isfile(self.default_load_launch): 255 self.launch_dock.launchlist_model.setPath(os.path.dirname(self.default_load_launch)) 256 257 self._discover_dialog = None 258 self.restricted_to_one_master = restricted_to_one_master 259 if restricted_to_one_master: 260 self.syncButton.setEnabled(False) 261 self.refreshAllButton.setEnabled(False) 262 self.discoveryButton.setEnabled(False) 263 self.startRobotButton.setEnabled(False) 264 265 self._sync_dialog = SyncDialog() 266 self._shortcut_focus = QShortcut(QKeySequence(self.tr("Ctrl+Shift+F", "switch to next focus area")), self) 267 self._shortcut_focus.activated.connect(self._show_section_menu) 268 269 self.editor_dialogs = dict() # [file] = Editor 270 '''@ivar: stores the open Editor ''' 271 272 self.simTimeLabel.setVisible(False) 273 self.launchServerLabel.setVisible(False) 274 275 # since the is_local method is threaded for host names, call it to cache the localhost 276 nm.is_local("localhost") 277 278 # set the help text 279 try: 280 from docutils import examples 281 with file(nm.settings().HELP_FILE) as f: 282 self.textBrowser.setText(examples.html_body(utf8(f.read()))) 283 except: 284 import traceback 285 msg = "Error while generate help: %s" % traceback.format_exc(2) 286 rospy.logwarn(msg) 287 self.textBrowser.setText(msg) 288 289 try: 290 ScreenHandler.testScreen() 291 except Exception as e: 292 rospy.logerr("No SCREEN available! You can't launch nodes.") 293 # MessageBox.warning(self, "No SCREEN", 294 # "No SCREEN available! You can't launch nodes.", 295 # '%s'%utf8(e)) 296 297 self.imageLabel.mouseDoubleClickEvent = self.image_mouseDoubleClickEvent 298 self.masternameLabel.mouseDoubleClickEvent = self.mastername_mouseDoubleClickEvent 299 300 try: 301 self.readSettings() 302 self.launch_dock.raise_() 303 except Exception as e: 304 rospy.logwarn("Error while read settings: %s" % e) 305 # setup the hide button, which hides the docks on left side 306 docks = self._dock_widget_in(Qt.LeftDockWidgetArea, only_visible=True) 307 if not docks: 308 self.hideDocksButton.toggle() 309 self.on_hide_docks_toggled(True) 310 self.hideDocksButton.clicked.connect(self.on_hide_docks_toggled) 311 312 if not nm.settings().movable_dock_widgets: 313 self.networkDock.setFeatures(self.networkDock.NoDockWidgetFeatures) 314 self.launch_dock.setFeatures(self.launch_dock.NoDockWidgetFeatures) 315 self.descriptionDock.setFeatures(self.descriptionDock.NoDockWidgetFeatures) 316 self.settings_dock.setFeatures(self.settings_dock.NoDockWidgetFeatures) 317 self.helpDock.setFeatures(self.helpDock.NoDockWidgetFeatures) 318 self.log_dock.setFeatures(self.log_dock.NoDockWidgetFeatures) 319 320 # ============================= 321 # Initialize the update handler 322 # ============================= 323 324 # initialize the class to get the state of discovering of other ROS master 325 self._update_handler = UpdateHandler() 326 self._update_handler.master_info_signal.connect(self.on_master_info_retrieved) 327 self._update_handler.master_errors_signal.connect(self.on_master_errors_retrieved) 328 self._update_handler.timediff_signal.connect(self.on_master_timediff_retrieved) 329 self._update_handler.error_signal.connect(self.on_master_info_error) 330 331 # this monitor class is used, if no master_discovery node is running to get the state of the local ROS master 332 self.own_master_monitor = OwnMasterMonitoring() 333 self.own_master_monitor.init(22622) 334 self.own_master_monitor.state_signal.connect(self.on_master_state_changed) 335 self.own_master_monitor.err_signal.connect(self.on_master_monitor_err) 336 337 # get the name of the service and topic of the discovery node. The name are determine by the message type of those topics 338 self.masterlist_service = masterlist_service = MasterListService() 339 masterlist_service.masterlist_signal.connect(self.on_master_list_retrieved) 340 masterlist_service.masterlist_err_signal.connect(self.on_master_list_err_retrieved) 341 self.state_topic = MasterStateTopic() 342 self.state_topic.state_signal.connect(self.on_master_state_changed) 343 self.stats_topic = MasterStatisticTopic() 344 self.stats_topic.stats_signal.connect(self.on_conn_stats_updated) 345 346 # timer to update the showed update time of the ros state 347 self.master_timecheck_timer = QTimer() 348 self.master_timecheck_timer.timeout.connect(self.on_master_timecheck) 349 self.master_timecheck_timer.start(1000) 350 self._refresh_time = time.time() 351 self._last_time_view_update = time.time() 352 353 self._con_tries = dict() 354 self._subscribe() 355 if DIAGNOSTICS_AVAILABLE: 356 self._sub_extended_log = rospy.Subscriber('/diagnostics_agg', DiagnosticArray, self._callback_diagnostics) 357 self.launch_dock.launchlist_model.reloadPackages() 358 self._select_index = 0 359 self._shortcut_restart_nodes = QShortcut(QKeySequence(self.tr("Ctrl+R", "restart selected nodes")), self) 360 self._shortcut_restart_nodes.activated.connect(self._restart_nodes)
361
362 - def _dock_widget_in(self, area=Qt.LeftDockWidgetArea, only_visible=False):
363 result = [] 364 docks = [self.launch_dock, self.descriptionDock, self.helpDock, self.networkDock] 365 for dock in docks: 366 if self.dockWidgetArea(dock) == area: 367 if not only_visible or (only_visible and dock.isVisibleTo(self)): 368 result.append(dock) 369 return result
370
371 - def _on_log_button_clicked(self):
372 self.log_dock.setVisible(not self.log_dock.isVisible())
373
374 - def _on_log_added(self, info, warn, err, fatal):
375 self.logButton.setEnabled(True)
376
377 - def _on_log_cleared(self):
378 self.logButton.setIcon(self.__icons['show_io'][0]) 379 self.logButton.setText('')
380 # self.logButton.setEnabled(False) 381
382 - def on_hide_docks_toggled(self, checked):
383 if self.dockWidgetArea(self.launch_dock) == Qt.LeftDockWidgetArea: 384 self.launch_dock.setVisible(not checked) 385 if self.dockWidgetArea(self.descriptionDock) == Qt.LeftDockWidgetArea: 386 self.descriptionDock.setVisible(not checked) 387 if self.dockWidgetArea(self.helpDock) == Qt.LeftDockWidgetArea: 388 self.helpDock.setVisible(not checked) 389 if self.dockWidgetArea(self.networkDock) == Qt.LeftDockWidgetArea: 390 self.networkDock.setVisible(not checked) 391 if self.dockWidgetArea(self.settings_dock) == Qt.LeftDockWidgetArea: 392 self.settings_dock.setVisible(not checked) 393 self.hideDocksButton.setArrowType(Qt.RightArrow if checked else Qt.LeftArrow)
394
395 - def on_currentChanged_tab(self, index):
396 pass
397 # if index == self.tabWidget.widget(0): 398 # self.networkDock.show() 399 # self.launch_dock.show() 400 # else: 401 # self.networkDock.hide() 402 # self.launch_dock.hide() 403
404 - def readSettings(self):
405 if nm.settings().store_geometry: 406 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) 407 self._history_selected_robot = settings.value("selected_robot", '') 408 settings.beginGroup("mainwindow") 409 maximized = settings.value("maximized", 'false') == 'true' 410 if maximized: 411 self.showMaximized() 412 else: 413 self.resize(settings.value("size", QSize(1024, 720))) 414 self.move(settings.value("pos", QPoint(0, 0))) 415 try: 416 self.restoreState(settings.value("window_state")) 417 except: 418 pass 419 settings.endGroup()
420
421 - def storeSetting(self):
422 if nm.settings().store_geometry: 423 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) 424 settings.beginGroup("mainwindow") 425 settings.setValue("size", self.size()) 426 settings.setValue("pos", self.pos()) 427 settings.setValue("maximized", self.isMaximized()) 428 settings.setValue("window_state", self.saveState()) 429 settings.endGroup()
430
431 - def closeEvent(self, event):
432 # ask to close nodes on exit 433 if self._close_on_exit and nm.settings().confirm_exit_when_closing: 434 masters = [uri for uri, m in self.masters.items() if m.online] 435 res = SelectDialog.getValue('Stop nodes?', "Select masters where to stop:", 436 masters, False, False, '', self, 437 select_if_single=False, 438 checkitem1="don't show this dialog again", 439 closein=nm.settings().timeout_close_dialog) 440 masters2stop, self._close_on_exit = res[0], res[1] 441 nm.settings().confirm_exit_when_closing = not res[2] 442 if self._close_on_exit: 443 self._on_finish = True 444 self._stop_local_master = None 445 for uri in masters2stop: 446 try: 447 m = self.masters[uri] 448 if m is not None: 449 if m.is_local: 450 self._stop_updating() 451 self._stop_local_master = m 452 m.stop_nodes_by_name(m.getRunningNodesIfLocal(), True, [rospy.get_name(), '/rosout']) 453 if not m.is_local: 454 m.killall_roscore() 455 except Exception as e: 456 rospy.logwarn("Error while stop nodes on %s: %s" % (uri, utf8(e))) 457 QTimer.singleShot(200, self._test_for_finish) 458 else: 459 self._close_on_exit = True 460 event.ignore() 461 elif self._are_master_in_process(): 462 QTimer.singleShot(200, self._test_for_finish) 463 self.masternameLabel.setText('<span style=" font-size:14pt; font-weight:600;">%s ...closing...</span>' % self.masternameLabel.text()) 464 rospy.loginfo("Wait for running processes are finished...") 465 event.ignore() 466 else: 467 try: 468 self.storeSetting() 469 except Exception as e: 470 rospy.logwarn("Error while store settings: %s" % e) 471 self.finish() 472 QMainWindow.closeEvent(self, event)
473
474 - def _are_master_in_process(self):
475 for _uri, m in self.masters.items(): 476 if m.in_process(): 477 return True 478 return False
479
480 - def _test_for_finish(self):
481 # this method test on exit for running process queues with stopping jobs 482 if self._are_master_in_process(): 483 QTimer.singleShot(200, self._test_for_finish) 484 return 485 if hasattr(self, '_stop_local_master') and self._stop_local_master is not None: 486 self.finish() 487 self._stop_local_master.killall_roscore() 488 del self._stop_local_master 489 self._close_on_exit = False 490 self.close()
491
492 - def _stop_updating(self):
493 if hasattr(self, "_discover_dialog") and self._discover_dialog is not None: 494 self._discover_dialog.stop() 495 self.masterlist_service.stop() 496 self._progress_queue.stop() 497 self._progress_queue_sync.stop() 498 self._update_handler.stop() 499 self.state_topic.stop() 500 self.stats_topic.stop() 501 self.own_master_monitor.stop() 502 self.master_timecheck_timer.stop() 503 self.launch_dock.stop() 504 self.log_dock.stop()
505
506 - def finish(self):
507 if not self._finished: 508 self._finished = True 509 print "Mainwindow finish..." 510 self._stop_updating() 511 for _, editor in self.editor_dialogs.items(): 512 editor.close() 513 for _, master in self.masters.iteritems(): 514 master.stop() 515 print "Mainwindow finished!"
516
517 - def getMasteruri(self):
518 ''' 519 Requests the ROS master URI from the ROS master through the RPC interface and 520 returns it. The 'materuri' attribute will be set to the requested value. 521 @return: ROS master URI 522 @rtype: C{str} or C{None} 523 ''' 524 if not hasattr(self, 'materuri') or self.materuri is None: 525 masteruri = masteruri_from_ros() 526 master = xmlrpclib.ServerProxy(masteruri) 527 _, _, self.materuri = master.getUri(rospy.get_name()) # _:=code, message 528 nm.is_local(get_hostname(self.materuri)) 529 return self.materuri
530
531 - def setMasterOnline(self, masteruri, online=True):
532 if masteruri in self.masters: 533 self.masters[masteruri].online = online
534
535 - def removeMaster(self, masteruri):
536 ''' 537 Removed master with given master URI from the list. 538 @param masteruri: the URI of the ROS master 539 @type masteruri: C{str} 540 ''' 541 if masteruri in self.masters: 542 if self.currentMaster is not None and self.currentMaster.masteruri == masteruri: 543 self.setCurrentMaster(None) 544 self.masters[masteruri].stop() 545 self.masters[masteruri].updateHostRequest.disconnect() 546 self.masters[masteruri].host_description_updated.disconnect() 547 self.masters[masteruri].capabilities_update_signal.disconnect() 548 self.masters[masteruri].remove_config_signal.disconnect() 549 self.masters[masteruri].description_signal.disconnect() 550 self.masters[masteruri].request_xml_editor.disconnect() 551 self.masters[masteruri].stop_nodes_signal.disconnect() 552 self.masters[masteruri].robot_icon_updated.disconnect() 553 self.masters[masteruri].save_profile_signal.disconnect() 554 if DIAGNOSTICS_AVAILABLE: 555 self.diagnostics_signal.disconnect(self.masters[masteruri].append_diagnostic) 556 self.stackedLayout.removeWidget(self.masters[masteruri]) 557 self.tabPlace.layout().removeWidget(self.masters[masteruri]) 558 for cfg in self.masters[masteruri].default_cfgs: 559 self.capabilitiesTable.removeConfig(cfg) 560 self.masters[masteruri].setParent(None) 561 del self.masters[masteruri]
562
563 - def getMaster(self, masteruri, create_new=True):
564 ''' 565 @return: the Widget which represents the master of given ROS master URI. If no 566 Widget for given URI is available a new one will be created. 567 @rtype: L{MasterViewProxy} 568 ''' 569 if masteruri not in self.masters: 570 if not create_new: 571 return None 572 self.masters[masteruri] = MasterViewProxy(masteruri, self) 573 self.masters[masteruri].updateHostRequest.connect(self.on_host_update_request) 574 self.masters[masteruri].host_description_updated.connect(self.on_host_description_updated) 575 self.masters[masteruri].capabilities_update_signal.connect(self.on_capabilities_update) 576 self.masters[masteruri].remove_config_signal.connect(self.on_remove_config) 577 self.masters[masteruri].description_signal.connect(self.on_description_update) 578 self.masters[masteruri].request_xml_editor.connect(self.on_launch_edit) 579 self.masters[masteruri].stop_nodes_signal.connect(self.on_stop_nodes) 580 self.masters[masteruri].robot_icon_updated.connect(self._on_robot_icon_changed) 581 self.masters[masteruri].save_profile_signal.connect(self.profiler.on_save_profile) 582 if DIAGNOSTICS_AVAILABLE: 583 self.diagnostics_signal.connect(self.masters[masteruri].append_diagnostic) 584 self.stackedLayout.addWidget(self.masters[masteruri]) 585 if masteruri == self.getMasteruri(): 586 if self.default_load_launch: 587 try: 588 if os.path.isfile(self.default_load_launch): 589 if self.default_load_launch.endswith('.launch'): 590 args = list() 591 args.append('_package:=%s' % (package_name(os.path.dirname(self.default_load_launch))[0])) 592 args.append('_launch_file:="%s"' % os.path.basename(self.default_load_launch)) 593 host = '%s' % nm.nameres().address(masteruri) 594 node_name = roslib.names.SEP.join(['%s' % (nm.nameres().masteruri2name(masteruri)), 595 os.path.basename(self.default_load_launch).replace('.launch', ''), 596 'default_cfg']) 597 self.launch_dock.progress_queue.add2queue('%s' % uuid.uuid4(), 598 'start default config @%s' % host, 599 nm.starter().runNodeWithoutConfig, 600 ('%s' % (nm.nameres().mastername(masteruri)), 'default_cfg_fkie', 601 'default_cfg', node_name, 602 args, masteruri, False, 603 self.masters[masteruri].current_user)) 604 self.launch_dock.progress_queue.start() 605 except Exception as e: 606 MessageBox.warning(self, "Load default configuration", 607 'Load default configuration %s failed!' % self.default_load_launch, 608 '%s' % utf8(e)) 609 return self.masters[masteruri]
610
611 - def on_host_update_request(self, host):
612 for key, value in self.masters.items(): 613 if get_hostname(key) == host and value.master_state is not None: 614 self._update_handler.requestMasterInfo(value.master_state.uri, value.master_state.monitoruri)
615
616 - def on_host_description_updated(self, masteruri, host, descr):
617 # self.master_model.updateDescription(nm.nameres().mastername(masteruri, host), descr) 618 pass
619
620 - def on_capabilities_update(self, masteruri, address, config_node, descriptions):
621 for d in descriptions: 622 self.capabilitiesTable.updateCapabilities(masteruri, config_node, d) 623 if masteruri is not None: 624 master = self.getMaster(masteruri) 625 self.capabilitiesTable.updateState(masteruri, master.master_info)
626
627 - def on_remove_config(self, cfg):
628 self.capabilitiesTable.removeConfig(cfg)
629 630 # ====================================================================================================================== 631 # Handling of local monitoring 632 # (Backup, if no master_discovery node is running) 633 # ====================================================================================================================== 634
635 - def _subscribe(self):
636 ''' 637 Try to subscribe to the topics of the master_discovery node. If it fails, the 638 own local monitoring of the ROS master state will be enabled. 639 ''' 640 if not self.restricted_to_one_master: 641 try: 642 self.masterlist_service.retrieveMasterList(self.getMasteruri(), False) 643 except: 644 pass 645 else: 646 self._setLocalMonitoring(True)
647
648 - def _setLocalMonitoring(self, on, discoverer=''):
649 ''' 650 Enables the local monitoring of the ROS master state and disables the view of 651 the discoved ROS master. 652 @param on: the enable / disable the local monitoring 653 @type on: C{boolean} 654 ''' 655 if self.own_master_monitor.is_running() != on: 656 self.masterTableView.setEnabled(not on) 657 self.refreshAllButton.setEnabled(not on) 658 self.own_master_monitor.pause(not on) 659 if on: 660 self.masterTableView.setToolTip("use 'Start' button to enable the master discovering") 661 self.networkDock.setWindowTitle("ROS Network [disabled]") 662 else: 663 self.masterTableView.setToolTip('') 664 if on: 665 # remove discovered ROS master and set the local master to selected 666 for uri in self.masters.keys(): 667 master = self.masters[uri] 668 if nm.is_local(get_hostname(uri)) or uri == self.getMasteruri(): 669 if not self._history_selected_robot or master.mastername == self._history_selected_robot: 670 self.setCurrentMaster(master) 671 else: 672 if master.master_state is not None: 673 self.master_model.removeMaster(master.master_state.name) 674 #self.removeMaster(uri) 675 else: 676 try: 677 # determine the ROS network ID 678 mcast_group = rospy.get_param(rospy.names.ns_join(discoverer, 'mcast_port')) 679 self.networkDock.setWindowTitle("ROS Network [id: %d]" % (mcast_group - 11511)) 680 self._subscribe() 681 except: 682 # try to get the multicast port of master discovery from log 683 port = 0 684 network_id = -1 685 import re 686 with open(ScreenHandler.getROSLogFile(node=discoverer.rstrip('/')), 'r') as mdfile: 687 for line in mdfile: 688 if line.find("Listen for multicast at") > -1: 689 port = map(int, re.findall(r'\d+', line))[-1] 690 elif line.find("Network ID") > -1: 691 network_id = map(int, re.findall(r'\d+', line))[-1] 692 port = 11511 + network_id 693 if port > 0: 694 self.networkDock.setWindowTitle("ROS Network [id: %d]" % (port - 11511)) 695 else: 696 self.networkDock.setWindowTitle("ROS Network")
697
698 - def on_master_list_err_retrieved(self, masteruri, error):
699 ''' 700 The callback method connected to the signal, which is emitted on an error 701 while call the service to determine the discovered ROS master. On the error 702 the local monitoring will be enabled. 703 ''' 704 if 'no service' not in error: 705 rospy.logwarn(error) 706 self._setLocalMonitoring(True)
707
708 - def hasDiscoveryService(self, minfo):
709 ''' 710 Test whether the new retrieved MasterInfo contains the master_discovery node. 711 This is identified by a name of the contained 'list_masters' service. 712 @param minfo: the ROS master Info 713 @type minfo: U{master_discovery_fkie.MasterInfo<http://docs.ros.org/api/master_discovery_fkie/html/modules.html#module-master_discovery_fkie.master_info>} 714 ''' 715 # use no discovery services, if roscore is running on a remote host 716 if self.restricted_to_one_master: 717 return False 718 for service in minfo.services.keys(): 719 if service.endswith('list_masters'): 720 return True 721 return False
722 723 # ====================================================================================================================== 724 # Handling of received ROS master state messages 725 # ====================================================================================================================== 726
727 - def on_master_list_retrieved(self, masteruri, servic_name, master_list):
728 ''' 729 Handle the retrieved list with ROS master. 730 1. update the ROS Network view 731 @param master_list: a list with ROS masters 732 @type master_list: C{[U{master_discovery_fkie.msg.MasterState<http://docs.ros.org/api/multimaster_msgs_fkie/html/msg/MasterState.html>}]} 733 ''' 734 result_1 = self.state_topic.registerByROS(self.getMasteruri(), False) 735 result_2 = self.stats_topic.registerByROS(self.getMasteruri(), False) 736 local_mon = not result_1 or not result_2 737 self._setLocalMonitoring(local_mon, rospy.names.namespace(result_1)) 738 self._con_tries[masteruri] = 0 739 # remove ROS master which are not in the new list 740 new_uris = [m.uri for m in master_list if m.uri is not None] 741 for uri in self.masters.keys(): 742 if uri not in new_uris: 743 master = self.masters[uri] 744 if not (nm.is_local(get_hostname(uri)) or uri == self.getMasteruri()): 745 if master.master_state is not None: 746 self.master_model.removeMaster(master.master_state.name) 747 self.setMasterOnline(uri, False) 748 # self.removeMaster(uri) 749 # add or update master 750 for m in master_list: 751 if m.uri is not None: 752 host = get_hostname(m.uri) 753 nm.nameres().add_master_entry(m.uri, m.name, host) 754 m.name = nm.nameres().mastername(m.uri) 755 master = self.getMaster(m.uri) 756 master.master_state = m 757 master.online = True 758 master.force_next_update() 759 self._assigne_icon(m.name) 760 self.master_model.updateMaster(m) 761 self._update_handler.requestMasterInfo(m.uri, m.monitoruri)
762
763 - def on_master_state_changed(self, msg):
764 ''' 765 Handle the received master state message. 766 1. update the ROS Network view 767 2. enable local master monitoring, if all masters are removed (the local master too) 768 @param msg: the ROS message with new master state 769 @type msg: U{master_discovery_fkie.msg.MasterState<http://docs.ros.org/api/multimaster_msgs_fkie/html/msg/MasterState.html>} 770 ''' 771 # do not update while closing 772 if hasattr(self, "_on_finish"): 773 rospy.logdebug("ignore changes on %s, because currently on closing...", msg.master.uri) 774 return 775 host = get_hostname(msg.master.uri) 776 if msg.state == MasterState.STATE_CHANGED: 777 nm.nameres().add_master_entry(msg.master.uri, msg.master.name, host) 778 msg.master.name = nm.nameres().mastername(msg.master.uri) 779 self.getMaster(msg.master.uri).master_state = msg.master 780 self._assigne_icon(msg.master.name) 781 self.master_model.updateMaster(msg.master) 782 if nm.settings().autoupdate: 783 self._update_handler.requestMasterInfo(msg.master.uri, msg.master.monitoruri) 784 else: 785 rospy.loginfo("Autoupdate disabled, the data will not be updated for %s" % msg.master.uri) 786 if not msg.master.online: 787 host = get_hostname(msg.master.uri) 788 rospy.loginfo("remove SSH connection for '%s' because the master is now offline" % host) 789 nm.ssh().remove(host) 790 if msg.state == MasterState.STATE_NEW: 791 # if new master with uri of the local master is received update the master list 792 if msg.master.uri == self.getMasteruri(): 793 self.masterlist_service.retrieveMasterList(msg.master.uri, False) 794 nm.nameres().add_master_entry(msg.master.uri, msg.master.name, host) 795 msg.master.name = nm.nameres().mastername(msg.master.uri) 796 self.getMaster(msg.master.uri).master_state = msg.master 797 self._assigne_icon(msg.master.name) 798 self.master_model.updateMaster(msg.master) 799 if nm.settings().autoupdate: 800 self._update_handler.requestMasterInfo(msg.master.uri, msg.master.monitoruri) 801 else: 802 rospy.loginfo("Autoupdate disabled, the data will not be updated for %s" % msg.master.uri) 803 if msg.state == MasterState.STATE_REMOVED: 804 if msg.master.uri == self.getMasteruri(): 805 # switch to locale monitoring, if the local master discovering was removed 806 self._setLocalMonitoring(True) 807 else: 808 nm.nameres().remove_master_entry(msg.master.uri) 809 self.master_model.removeMaster(msg.master.name) 810 self.setMasterOnline(msg.master.uri, False) 811 # self.removeMaster(msg.master.uri) 812 # start master_sync, if it was selected in the start dialog to start with master_dsicovery 813 if self._syncs_to_start: 814 if msg.state in [MasterState.STATE_NEW, MasterState.STATE_CHANGED]: 815 # we don't know which name for host was used to start master discovery 816 try: 817 if host in self._syncs_to_start: 818 self.on_sync_start(msg.master.uri) 819 self._syncs_to_start.remove(host) 820 elif msg.master.name in self._syncs_to_start: 821 self.on_sync_start(msg.master.uri) 822 self._syncs_to_start.remove(msg.master.name) 823 else: 824 addresses = nm.nameres().addresses(msg.master.uri) 825 for address in addresses: 826 if address in self._syncs_to_start: 827 self.on_sync_start(msg.master.uri) 828 self._syncs_to_start.remove(address) 829 except ValueError: 830 pass
831 # if len(self.masters) == 0: 832 # self._setLocalMonitoring(True) 833
834 - def _assigne_icon(self, name, path=None):
835 ''' 836 Sets the new icon to the given robot. If the path is `None` set search for 837 .png file with robot name. 838 :param name: robot name 839 :type name: str 840 :param path: path of the icon (Default: None) 841 :type path: str 842 ''' 843 icon_path = path if path else nm.settings().robot_image_file(name) 844 if name not in self.__icons or self.__icons[name][1] != path: 845 if QFile.exists(icon_path): 846 self.__icons[name] = (QIcon(icon_path), icon_path) 847 elif name in self.__icons: 848 del self.__icons[name]
849
850 - def on_master_monitor_err(self, msg):
851 self._con_tries[self.getMasteruri()] += 1
852
853 - def on_master_info_retrieved(self, minfo):
854 ''' 855 Integrate the received master info. 856 @param minfo: the ROS master Info 857 @type minfo: U{master_discovery_fkie.MasterInfo<http://docs.ros.org/api/master_discovery_fkie/html/modules.html#module-master_discovery_fkie.master_info>} 858 ''' 859 if hasattr(self, "_on_finish"): 860 rospy.logdebug("ignore changes on %s, because currently on closing...", minfo.masteruri) 861 return 862 rospy.logdebug("MASTERINFO from %s (%s) received", minfo.mastername, minfo.masteruri) 863 self._con_tries[minfo.masteruri] = 0 864 # cputimes_m = os.times() 865 # cputime_init_m = cputimes_m[0] + cputimes_m[1] 866 if minfo.masteruri in self.masters: 867 for _, master in self.masters.items(): # _:=uri 868 try: 869 if not master.online and master.masteruri != minfo.masteruri: 870 continue 871 # check for running discovery service 872 new_info = master.master_info is None or master.master_info.timestamp < minfo.timestamp 873 # cputimes = os.times() 874 # cputime_init = cputimes[0] + cputimes[1] 875 master.master_info = minfo 876 # cputimes = os.times() 877 # cputime = cputimes[0] + cputimes[1] - cputime_init 878 # print master.master_state.name, cputime 879 if master.master_info is not None: 880 if self._history_selected_robot == minfo.mastername and self._history_selected_robot == master.mastername and self.currentMaster != master: 881 if self.currentMaster is not None and not self.currentMaster.is_local: 882 self.setCurrentMaster(master) 883 elif nm.is_local(get_hostname(master.master_info.masteruri)) or self.restricted_to_one_master: 884 if new_info: 885 has_discovery_service = self.hasDiscoveryService(minfo) 886 if (not self.own_master_monitor.isPaused() or not self.masterTableView.isEnabled()) and has_discovery_service: 887 self._subscribe() 888 if self.currentMaster is None and (not self._history_selected_robot or self._history_selected_robot == minfo.mastername): 889 self.setCurrentMaster(master) 890 891 # update the list view, whether master is synchronized or not 892 if master.master_info.masteruri == minfo.masteruri: 893 self.master_model.setChecked(master.master_state.name, not minfo.getNodeEndsWith('master_sync') is None) 894 if self.default_profile_load: 895 self.default_profile_load = False 896 QTimer.singleShot(2000, self._load_default_profile_slot) 897 self.capabilitiesTable.updateState(minfo.masteruri, minfo) 898 except Exception, e: 899 rospy.logwarn("Error while process received master info from %s: %s", minfo.masteruri, utf8(e)) 900 # update the duplicate nodes 901 self.updateDuplicateNodes() 902 # update the buttons, whether master is synchronized or not 903 if self.currentMaster is not None and self.currentMaster.master_info is not None and not self.restricted_to_one_master: 904 self.syncButton.setEnabled(True) 905 self.syncButton.setChecked(not self.currentMaster.master_info.getNodeEndsWith('master_sync') is None) 906 else: 907 self.masterlist_service.retrieveMasterList(minfo.masteruri, False) 908 self.profiler.update_progress()
909 # cputimes_m = os.times() 910 # cputime_m = cputimes_m[0] + cputimes_m[1] - cputime_init_m 911 # print "ALL:", cputime_m 912
913 - def _load_default_profile_slot(self):
914 if not hasattr(self, "_on_finish"): 915 self.profiler.on_load_profile_file(self.default_load_launch)
916
917 - def on_master_errors_retrieved(self, masteruri, error_list):
918 self.master_model.updateMasterErrors(nm.nameres().mastername(masteruri), error_list)
919
920 - def on_master_timediff_retrieved(self, masteruri, timediff):
921 self.master_model.updateTimeDiff(nm.nameres().mastername(masteruri), timediff)
922
923 - def on_master_info_error(self, masteruri, error):
924 if masteruri not in self._con_tries: 925 self._con_tries[masteruri] = 0 926 self._con_tries[masteruri] += 1 927 if masteruri == self.getMasteruri(): 928 rospy.logwarn("Error while connect to local master_discovery %s: %s", masteruri, error) 929 # switch to local monitoring after 3 timeouts 930 if self._con_tries[masteruri] > 2: 931 self._setLocalMonitoring(True) 932 master = self.getMaster(masteruri, False) 933 if master and master.master_state is not None: 934 self._update_handler.requestMasterInfo(master.master_state.uri, master.master_state.monitoruri, self.DELAYED_NEXT_REQ_ON_ERR)
935
936 - def on_conn_stats_updated(self, stats):
937 ''' 938 Handle the retrieved connection statistics. 939 1. update the ROS Network view 940 @param stats: a list with connection statistics 941 @type stats: C{[U{master_discovery_fkie.msg.LinkState<http://docs.ros.org/api/multimaster_msgs_fkie/html/msg/LinkState.html>}]} 942 ''' 943 for stat in stats.links: 944 self.master_model.updateMasterStat(stat.destination, stat.quality)
945 946 # ====================================================================================================================== 947 # Handling of master info frame 948 # ====================================================================================================================== 949
950 - def on_info_clicked(self):
951 text = '<dl>' 952 text = '%s<dt><b>Maintainer</b>: Alexander Tiderko <font color=gray>alexander.tiderko@gmail.com</font></dt>' % text 953 text = '%s<dt><b>Author</b>: Alexander Tiderko, Timo Roehling</dt>' % text 954 text = '%s<dt><b>License</b>: BSD, some icons are licensed under the GNU Lesser General Public License (LGPL) or Creative Commons Attribution-Noncommercial 3.0 License</dt>' % text 955 text = '%s</dl>' % text 956 if nm.__date__ == 'unknown': 957 text = '%s<dt><b>Version</b>: %s</dt>' % (text, nm.__version__) 958 else: 959 text = '%s<dt><b>Version</b>: %s (%s)</dt>' % (text, nm.__version__, nm.__date__) 960 MessageBox.about(self, 'About Node Manager', text)
961
962 - def on_master_log_clicked(self):
963 ''' 964 Tries to get the log of master_discovery node on the machine requested by a dialog. 965 ''' 966 # get the history list 967 user_list = [self.userComboBox.itemText(i) for i in reversed(range(self.userComboBox.count()))] 968 user_list.insert(0, 'last used') 969 params = {'Host': ('string', 'localhost'), 970 'Show master discovery log': ('bool', True), 971 'Show master sync log': ('bool', False), 972 'Username': ('string', user_list), 973 'Only screen log': ('bool', True), 974 # 'Optional Parameter': ('list', params_optional) 975 } 976 dia = ParameterDialog(params, sidebar_var='Host') 977 dia.setFilterVisible(False) 978 dia.setWindowTitle('Show log') 979 dia.resize(450, 150) 980 dia.setFocusField('Host') 981 if dia.exec_(): 982 try: 983 params = dia.getKeywords() 984 hostnames = params['Host'] if isinstance(params['Host'], list) else [params['Host']] 985 log_master_discovery = params['Show master discovery log'] 986 log_master_sync = params['Show master sync log'] 987 username = params['Username'] 988 screen_only = params['Only screen log'] 989 for hostname in hostnames: 990 try: 991 usr = username 992 if username == 'last used': 993 usr = nm.settings().host_user(hostname) 994 if log_master_discovery: 995 self._progress_queue.add2queue(utf8(uuid.uuid4()), 996 '%s: show log of master discovery' % hostname, 997 nm.starter().openLog, 998 ('/master_discovery', hostname, usr, screen_only)) 999 if log_master_sync: 1000 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1001 '%s: show log of master sync' % hostname, 1002 nm.starter().openLog, 1003 ('/master_sync', hostname, usr, screen_only)) 1004 except (Exception, nm.StartException) as err: 1005 import traceback 1006 print traceback.format_exc(1) 1007 rospy.logwarn("Error while show LOG for master_discovery %s: %s" % (utf8(hostname), utf8(err))) 1008 MessageBox.warning(self, "Show log error", 1009 'Error while show log of master_discovery', 1010 '%s' % utf8(err)) 1011 self._progress_queue.start() 1012 except Exception as err: 1013 MessageBox.warning(self, "Show log error", 1014 'Error while parse parameter', 1015 '%s' % utf8(err))
1016
1017 - def on_set_time_clicked(self):
1018 if self.currentMaster is not None: # and not self.currentMaster.is_local: 1019 time_dialog = QDialog() 1020 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'TimeInput.ui') 1021 loadUi(ui_file, time_dialog) 1022 host = get_hostname(self.currentMaster.master_state.uri) 1023 time_dialog.setWindowTitle('Set time on %s' % host) 1024 time_dialog.hostsComboBox.addItems(nm.history().cachedParamValues('/ntp')) 1025 if self.currentMaster.is_local: 1026 time_dialog.dateFrame.setVisible(False) 1027 if time_dialog.exec_(): 1028 running_nodes = self.currentMaster.getRunningNodesIfLocal(remove_system_nodes=True) 1029 if running_nodes: 1030 ret = MessageBox.question(self, 'Set Time', 'There are running nodes. Stop them?', buttons=MessageBox.Yes | MessageBox.No) 1031 if ret == MessageBox.Yes: 1032 self.currentMaster.stop_nodes_by_name(running_nodes) 1033 if time_dialog.dateRadioButton.isChecked(): 1034 try: 1035 rospy.loginfo("Set remote host time to local time: %s" % self.currentMaster.master_state.uri) 1036 socket.setdefaulttimeout(10) 1037 p = xmlrpclib.ServerProxy(self.currentMaster.master_state.monitoruri) 1038 uri, success, newtime, errormsg = p.setTime(time.time()) 1039 if not success: 1040 if errormsg.find('password') > -1: 1041 errormsg += "\nPlease modify /etc/sudoers with sudoedit and add user privilege, e.g:" 1042 errormsg += "\n%s ALL=NOPASSWD: /bin/date" % self.currentMaster.current_user 1043 errormsg += "\n!!!needed to be at the very end of file, don't forget a new line at the end!!!" 1044 errormsg += "\n\nBe aware, it does not replace the time synchronization!" 1045 errormsg += "\nIt sets approximate time without undue delays on communication layer." 1046 MessageBox.warning(self, "Time set error", 1047 'Error while set time on %s' % uri, '%s' % utf8(errormsg)) 1048 else: 1049 timediff = time.time() - newtime 1050 rospy.loginfo(" New time difference to %s is approx.: %.3fs" % (self.currentMaster.master_state.uri, timediff)) 1051 self.on_master_timediff_retrieved(self.currentMaster.master_state.uri, timediff) 1052 except Exception as e: 1053 errormsg = '%s' % e 1054 if errormsg.find('setTime') > -1: 1055 errormsg += "\nUpdate remote multimaster_fkie!" 1056 rospy.logwarn("Error while set time on %s: %s" % (self.currentMaster.master_state.uri, utf8(errormsg))) 1057 MessageBox.warning(self, "Time sync error", 1058 'Error while set time on %s' % self.currentMaster.master_state.uri, 1059 '%s' % utf8(errormsg)) 1060 finally: 1061 socket.setdefaulttimeout(None) 1062 elif time_dialog.ntpdateRadioButton.isChecked(): 1063 ntp_host = time_dialog.hostsComboBox.currentText() 1064 nm.history().addParamCache('/ntp', ntp_host) 1065 cmd = "%s %s" % ('sudo ntpdate -v -u -t 1', ntp_host) 1066 nm.starter().ntpdate(host, cmd)
1067
1068 - def on_refresh_master_clicked(self):
1069 if self.currentMaster is not None: 1070 rospy.loginfo("Request an update from %s", utf8(self.currentMaster.master_state.monitoruri)) 1071 if self.currentMaster.master_info is not None: 1072 check_ts = self.currentMaster.master_info.check_ts 1073 self.currentMaster.master_info.timestamp = self.currentMaster.master_info.timestamp - 1.0 1074 self.currentMaster.master_info.check_ts = check_ts 1075 if self.currentMaster.master_state is not None: 1076 self._update_handler.requestMasterInfo(self.currentMaster.master_state.uri, self.currentMaster.master_state.monitoruri) 1077 self.currentMaster.force_next_update()
1078 # self.currentMaster.remove_all_def_configs() 1079
1080 - def on_run_node_clicked(self):
1081 ''' 1082 Open a dialog to run a ROS node without a configuration 1083 ''' 1084 from run_dialog import RunDialog 1085 if self.currentMaster is not None: 1086 dia = RunDialog(get_hostname(self.currentMaster.masteruri), self.currentMaster.masteruri) 1087 if dia.exec_(): 1088 params = dia.run_params() 1089 if params: 1090 params = params + (False, self.currentMaster.current_user,) # autorequest must be false 1091 try: 1092 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1093 'run `%s` on %s' % (params[2], params[0]), 1094 nm.starter().runNodeWithoutConfig, 1095 params) 1096 self._progress_queue.start() 1097 except (Exception, nm.StartException), e: 1098 rospy.logwarn("Error while run `%s` on %s: %s", params[2], params[0], utf8(e)) 1099 MessageBox.warning(self, "Run error", 1100 'Error while run node %s [%s]' % (params[2], params[1]), 1101 utf8(e)) 1102 else: 1103 MessageBox.critical(self, "Run error", 1104 'No binary specified')
1105
1106 - def on_rqt_plugin_start(self, name, plugin):
1107 if self.currentMaster is not None: 1108 try: 1109 args = [] 1110 package = 'rqt_gui' 1111 binary = 'rqt_gui' 1112 prefix = 'rqt_' 1113 suffix = '' 1114 if name == 'RViz': 1115 prefix = 'rviz_' 1116 package = 'rviz' 1117 binary = 'rviz' 1118 if plugin: 1119 args = ['-s', plugin] 1120 if name == 'rosbag record': 1121 package = 'rosbag' 1122 binary = 'record' 1123 prefix = '' 1124 topic_names = [] 1125 current_tab = self.currentMaster.masterTab.tabWidget.tabText(self.currentMaster.masterTab.tabWidget.currentIndex()) 1126 if (current_tab == 'Nodes'): 1127 nodes = self.currentMaster.nodesFromIndexes(self.currentMaster.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 1128 if nodes: 1129 for n in nodes: 1130 topic_names.extend(n.published) 1131 else: 1132 topics = self.currentMaster.topicsFromIndexes(self.currentMaster.masterTab.topicsView.selectionModel().selectedIndexes()) 1133 if topics: 1134 topic_names.extend([t.name for t in topics]) 1135 count_topics = 'ALL' 1136 if topic_names: 1137 args = [' '.join(topic_names)] 1138 count_topics = '%d selected' % len(topic_names) 1139 else: 1140 args = ['-a'] 1141 ret = MessageBox.question(self, 'Start rosbag', 'Start rosbag record with %s topics to %s/record_TIMESTAMP?' % (count_topics, nm.settings().LOG_PATH), buttons=MessageBox.Yes | MessageBox.No) 1142 if ret == MessageBox.No: 1143 return 1144 args.append("-o %s/record" % nm.settings().LOG_PATH) 1145 suffix = "_%d" % int(time.time()) 1146 node_name = '%s%s_%s%s' % (prefix, name.lower().replace(' ', '_'), 1147 self.currentMaster.master_state.name, suffix) 1148 self.currentMaster._progress_queue.add2queue(utf8(uuid.uuid4()), 1149 'start %s' % name, 1150 nm.starter().runNodeWithoutConfig, 1151 ('localhost', package, binary, 1152 nm.nameres().normalize_name(node_name), args, 1153 '%s' % self.currentMaster.master_state.uri, 1154 False)) 1155 except (Exception, nm.StartException), e: 1156 import traceback 1157 print utf8(traceback.format_exc(1)) 1158 rospy.logwarn("Error while start %s: %s" % (name, utf8(e))) 1159 MessageBox.warning(self, "Start error", 1160 'Error while start %s' % name, 1161 '%s' % utf8(e)) 1162 self.currentMaster._progress_queue.start()
1163
1164 - def on_sync_dialog_released(self, released=False, masteruri=None, external_call=False):
1165 self.syncButton.setEnabled(False) 1166 master = self.currentMaster 1167 sync_node = None 1168 if masteruri is not None: 1169 master = self.getMaster(masteruri, False) 1170 if master is not None and master.master_info is not None: 1171 sync_node = master.master_info.getNodeEndsWith('master_sync') 1172 if master is not None and (sync_node is None or external_call): 1173 self._sync_dialog.resize(350, 190) 1174 if self._sync_dialog.exec_(): 1175 try: 1176 host = get_hostname(master.masteruri) 1177 if self._sync_dialog.interface_filename is not None: 1178 # copy the interface file to remote machine 1179 self._progress_queue_sync.add2queue(utf8(uuid.uuid4()), 1180 'Transfer sync interface %s' % host, 1181 nm.starter().transfer_files, 1182 ("%s" % host, self._sync_dialog.interface_filename, False, master.current_user)) 1183 self._progress_queue_sync.add2queue(utf8(uuid.uuid4()), 1184 'Start sync on %s' % host, 1185 nm.starter().runNodeWithoutConfig, 1186 ("%s" % host, 'master_sync_fkie', 'master_sync', 'master_sync', self._sync_dialog.sync_args, "%s" % master.masteruri, False, master.current_user)) 1187 self._progress_queue_sync.start() 1188 except: 1189 import traceback 1190 MessageBox.warning(self, "Start sync error", 1191 "Error while start sync node", 1192 utf8(traceback.format_exc(1))) 1193 else: 1194 self.syncButton.setChecked(False) 1195 elif sync_node is not None: 1196 master.stop_nodes([sync_node]) 1197 self.syncButton.setEnabled(True)
1198
1199 - def on_sync_start(self, masteruri=None):
1200 ''' 1201 Enable or disable the synchronization of the master cores 1202 ''' 1203 key_mod = QApplication.keyboardModifiers() 1204 if (key_mod & Qt.ShiftModifier or key_mod & Qt.ControlModifier): 1205 self.on_sync_dialog_released(masteruri=masteruri, external_call=True) 1206 # if not master.master_info is None: 1207 # node = master.master_info.getNodeEndsWith('master_sync') 1208 # self.syncButton.setChecked(not node is None) 1209 else: 1210 self.syncButton.setEnabled(False) 1211 master = self.currentMaster 1212 if masteruri is not None: 1213 master = self.getMaster(masteruri, False) 1214 if master is not None: 1215 # ask the user to start the master_sync with loaded launch file 1216 if master.master_info is not None: 1217 node = master.getNode('/master_sync') 1218 if node and node[0].has_configs(): 1219 def_cfg_info = '\nNote: default_cfg parameter will be changed!' if node[0].has_default_cfgs(node[0].cfgs) else '' 1220 ret = MessageBox.question(self, 'Start synchronization', 'Start the synchronization using loaded configuration?\n\n `No` starts the master_sync with default parameter.%s' % def_cfg_info, buttons=MessageBox.Yes | MessageBox.No) 1221 if ret == MessageBox.Yes: 1222 master.start_nodes([node[0]]) 1223 return 1224 1225 # start the master sync with default settings 1226 default_sync_args = ["_interface_url:='.'", 1227 '_sync_topics_on_demand:=False', 1228 '_ignore_hosts:=[]', '_sync_hosts:=[]', 1229 '_ignore_nodes:=[]', '_sync_nodes:=[]', 1230 '_ignore_topics:=[]', '_sync_topics:=[]', 1231 '_ignore_services:=[]', '_sync_services:=[]', 1232 '_sync_remote_nodes:=False'] 1233 try: 1234 host = get_hostname(master.masteruri) 1235 self._progress_queue_sync.add2queue(utf8(uuid.uuid4()), 1236 'start sync on ' + utf8(host), 1237 nm.starter().runNodeWithoutConfig, 1238 (utf8(host), 'master_sync_fkie', 'master_sync', 'master_sync', default_sync_args, utf8(master.masteruri), False, master.current_user)) 1239 self._progress_queue_sync.start() 1240 except: 1241 pass 1242 self.syncButton.setEnabled(True)
1243
1244 - def on_sync_stop(self, masteruri=None):
1245 master = self.currentMaster 1246 if masteruri is not None: 1247 master = self.getMaster(masteruri, False) 1248 if master is not None and master.master_info is not None: 1249 node = master.master_info.getNodeEndsWith('master_sync') 1250 if node is not None: 1251 master.stop_nodes([node])
1252
1253 - def on_master_timecheck(self):
1254 # HACK: sometimes the local monitoring will not be activated. This is the detection. 1255 if len(self.masters) < 2 and self.currentMaster is None: 1256 self._subscribe() 1257 return 1258 # update the info panel of the robot. If the node manager is not selected the updates are rarer. 1259 current_time = time.time() 1260 if self.isActiveWindow() or current_time - self._last_time_view_update > 15: 1261 self._last_time_view_update = current_time 1262 if self.currentMaster is not None and self.currentMaster.master_state is not None: 1263 master = self.getMaster(self.currentMaster.master_state.uri) 1264 name = master.master_state.name 1265 masteruri = master.master_state.uri 1266 if self.restricted_to_one_master: 1267 name = ''.join([name, ' <span style=" color:red;">(restricted)</span>']) 1268 if not self.masternameLabel.toolTip(): 1269 self.masternameLabel.setToolTip('The multicore options are disabled, because the roscore is running on remote host!') 1270 if master.master_info is not None: 1271 self.showMasterName(masteruri, name, self.timestampStr(master.master_info.check_ts), master.master_state.online) 1272 pass 1273 elif master.master_state is not None: 1274 text = 'Try to get info!!!' 1275 if not nm.settings().autoupdate: 1276 text = 'Press F5 or click on reload to get info' 1277 self.showMasterName(masteruri, name, text, master.master_state.online) 1278 else: 1279 self.showMasterName('', 'No robot selected', None, False) 1280 if (current_time - self._refresh_time > 30.0): 1281 masteruri = self.getMasteruri() 1282 if masteruri is not None: 1283 master = self.getMaster(masteruri) 1284 if master is not None and master.master_state is not None and nm.settings().autoupdate: 1285 self._update_handler.requestMasterInfo(master.master_state.uri, master.master_state.monitoruri) 1286 self._refresh_time = current_time
1287
1288 - def showMasterName(self, masteruri, name, timestamp, online=True):
1289 ''' 1290 Update the view of the info frame. 1291 ''' 1292 con_err = '' 1293 try: 1294 tries = self._con_tries[masteruri] 1295 if tries > 1: 1296 con_err = '<span style=" color:red;">connection problems (%s tries)! </span>' % utf8(tries) 1297 except: 1298 pass 1299 if self.__current_master_label_name != name: 1300 self.__current_master_label_name = name 1301 show_name = name if nm.settings().show_domain_suffix else subdomain(name) 1302 self.masternameLabel.setText('<span style=" font-size:14pt; font-weight:600;">%s</span>' % show_name) 1303 color = QColor.fromRgb(nm.settings().host_color(self.__current_master_label_name, self._default_color.rgb())) 1304 self._new_color(color) 1305 ts = 'updated: %s' % utf8(timestamp) if timestamp is not None else '' 1306 if not nm.settings().autoupdate: 1307 ts = '%s<span style=" color:orange;"> AU off</span>' % ts 1308 self.masterInfoLabel.setText('<span style=" font-size:8pt;">%s%s</span>' % (con_err, ts)) 1309 1310 # load the robot image, if one exists 1311 if self.masternameLabel.isEnabled(): 1312 if name in self.__icons: 1313 if self.__icons[name][0] != self.__current_icon: 1314 icon = self.__icons[name][0] 1315 self.__current_icon = icon 1316 self.imageLabel.setPixmap(icon.pixmap(self.nameFrame.size())) 1317 self.imageLabel.setToolTip(''.join(['<html><head></head><body><img src="', self.__icons[name][1], '" alt="', name, '"></body></html>'])) 1318 elif self.__icons['default_pc'][0] != self.__current_icon: 1319 icon = self.__icons['default_pc'][0] 1320 self.__current_icon = icon 1321 self.imageLabel.setPixmap(icon.pixmap(self.nameFrame.size())) 1322 self.imageLabel.setToolTip('') 1323 # set sim_time info 1324 master = self.getMaster(masteruri, False) 1325 sim_time_enabled = self.masternameLabel.isEnabled() and master is not None and master.use_sim_time 1326 self.simTimeLabel.setVisible(bool(sim_time_enabled)) 1327 launch_server_enabled = self.masternameLabel.isEnabled() and (master is not None) and master.has_launch_server() 1328 self.launchServerLabel.setVisible(launch_server_enabled) 1329 self.masternameLabel.setEnabled(online) 1330 self.masterInfoFrame.setEnabled((timestamp is not None)) 1331 # update warning symbol / text 1332 if not self.log_dock.isVisible() and self.log_dock.count(): 1333 if self.logButton.text(): 1334 self.logButton.setIcon(self.__icons['log_warning'][0]) 1335 self.logButton.setText('') 1336 else: 1337 self.logButton.setText('%d' % self.log_dock.count()) 1338 self.logButton.setIcon(self.__icons['empty'][0])
1339
1340 - def timestampStr(self, timestamp):
1341 dt = datetime.fromtimestamp(timestamp) 1342 diff = time.time() - timestamp 1343 diff_dt = datetime.fromtimestamp(diff) 1344 before = '0 sec' 1345 if (diff < 60): 1346 before = diff_dt.strftime('%S sec') 1347 elif (diff < 3600): 1348 before = diff_dt.strftime('%M:%S min') 1349 elif (diff < 86400): 1350 before = diff_dt.strftime('%H:%M:%S std') 1351 else: 1352 before = diff_dt.strftime('%d Day(s) %H:%M:%S') 1353 return '%s (%s)' % (dt.strftime('%H:%M:%S'), before)
1354
1355 - def updateDuplicateNodes(self):
1356 # update the duplicate nodes 1357 running_nodes = dict() 1358 for _, m in self.masters.items(): 1359 if m.master_state is not None and m.master_state.online: 1360 running_nodes.update(m.getRunningNodesIfLocal()) 1361 for _, m in self.masters.items(): 1362 if m.master_state is not None: 1363 m.markNodesAsDuplicateOf(running_nodes)
1364 1365 1366 # ====================================================================================================================== 1367 # Handling of master list view 1368 # ====================================================================================================================== 1369
1370 - def on_master_table_pressed(self, selected):
1371 pass
1372
1373 - def on_master_table_clicked(self, selected):
1374 ''' 1375 On click on the sync item, the master_sync node will be started or stopped, 1376 depending on run state. 1377 ''' 1378 pass
1379 # item = self.master_model.itemFromIndex(selected) 1380 # if isinstance(item, MasterSyncItem): 1381 # pass 1382
1383 - def on_master_table_activated(self, selected):
1384 item = self.master_model.itemFromIndex(selected) 1385 MessageBox.information(self, item.name, item.toolTip())
1386
1387 - def on_master_selection_changed(self, selected):
1388 ''' 1389 If a master was selected, set the corresponding Widget of the stacked layout 1390 to the current widget and shows the state of the selected master. 1391 ''' 1392 # si = self.masterTableView.selectedIndexes() 1393 # for index in si: 1394 # if index.row() == selected.row(): 1395 item = self.master_model.itemFromIndex(selected) 1396 if item is not None: 1397 self._history_selected_robot = item.master.name 1398 self.setCurrentMaster(item.master.uri) 1399 if self.currentMaster.master_info is not None and not self.restricted_to_one_master: 1400 node = self.currentMaster.master_info.getNodeEndsWith('master_sync') 1401 self.syncButton.setEnabled(True) 1402 self.syncButton.setChecked(node is not None) 1403 else: 1404 self.syncButton.setEnabled(False) 1405 return 1406 self.launch_dock.raise_()
1407
1408 - def setCurrentMaster(self, master):
1409 ''' 1410 Changes the view of the master. 1411 :param master: the MasterViewProxy object or masteruri 1412 :type master: MasterViewProxy or str 1413 ''' 1414 show_user_field = False 1415 if isinstance(master, MasterViewProxy): 1416 self.currentMaster = master 1417 self.stackedLayout.setCurrentWidget(master) 1418 show_user_field = not master.is_local 1419 self._add_user_to_combo(self.currentMaster.current_user) 1420 self.userComboBox.setEditText(self.currentMaster.current_user) 1421 elif master is None: 1422 self.currentMaster = None 1423 self.stackedLayout.setCurrentIndex(0) 1424 else: # it's masteruri 1425 self.currentMaster = self.getMaster(master) 1426 if self.currentMaster is not None: 1427 self.stackedLayout.setCurrentWidget(self.currentMaster) 1428 show_user_field = not self.currentMaster.is_local 1429 self._add_user_to_combo(self.currentMaster.current_user) 1430 self.userComboBox.setEditText(self.currentMaster.current_user) 1431 else: 1432 self.stackedLayout.setCurrentIndex(0) 1433 self.user_frame.setVisible(show_user_field) 1434 self.on_master_timecheck()
1435
1436 - def _add_user_to_combo(self, user):
1437 for i in range(self.userComboBox.count()): 1438 if user.lower() == self.userComboBox.itemText(i).lower(): 1439 return 1440 self.userComboBox.addItem(user)
1441
1442 - def on_user_changed(self, user):
1443 if self.currentMaster is not None: 1444 self.currentMaster.current_user = user
1445
1446 - def on_masterTableView_selection_changed(self, selected, deselected):
1447 ''' 1448 On selection of a master list. 1449 ''' 1450 if selected.isValid(): 1451 self.on_master_selection_changed(selected)
1452
1454 ''' 1455 Retrieves from the master_discovery node the list of all discovered ROS 1456 master and get their current state. 1457 ''' 1458 # set the timestamp of the current master info back 1459 for _, m in self.masters.items(): 1460 if m.master_info is not None: 1461 check_ts = m.master_info.check_ts 1462 m.master_info.timestamp = m.master_info.timestamp - 1.0 1463 m.master_info.check_ts = check_ts 1464 self.masterlist_service.refresh(self.getMasteruri(), False)
1465
1467 try: 1468 self._discover_dialog.raise_() 1469 except: 1470 mcast_group = rospy.get_param('/master_discovery/mcast_group', '226.0.0.0') 1471 self._discover_dialog = NetworkDiscoveryDialog(mcast_group, 11511, 100, self) 1472 self._discover_dialog.network_join_request.connect(self._join_network) 1473 self._discover_dialog.show()
1474
1475 - def on_start_robot_clicked(self):
1476 ''' 1477 Tries to start the master_discovery node on the machine requested by a dialog. 1478 ''' 1479 # get the history list 1480 user_list = [self.userComboBox.itemText(i) for i in reversed(range(self.userComboBox.count()))] 1481 user_list.insert(0, 'last used') 1482 params_optional = {'Discovery type': ('string', ['master_discovery', 'zeroconf']), 1483 'ROS Master Name': ('string', 'autodetect'), 1484 'ROS Master URI': ('string', 'ROS_MASTER_URI'), 1485 'Robot hosts': ('string', ''), 1486 'Username': ('string', user_list), 1487 'MCast Group': ('string', '226.0.0.0'), 1488 'Heartbeat [Hz]': ('float', 0.5) 1489 } 1490 params = {'Host': ('string', 'localhost'), 1491 'Network(0..99)': ('int', '0'), 1492 'Start sync': ('bool', nm.settings().start_sync_with_discovery), 1493 'Optional Parameter': ('list', params_optional) 1494 } 1495 dia = ParameterDialog(params, sidebar_var='Host') 1496 dia.setFilterVisible(False) 1497 dia.setWindowTitle('Start discovery') 1498 dia.resize(450, 330) 1499 dia.setFocusField('Host') 1500 if dia.exec_(): 1501 try: 1502 params = dia.getKeywords() 1503 hostnames = params['Host'] if isinstance(params['Host'], list) else [params['Host']] 1504 port = params['Network(0..99)'] 1505 start_sync = params['Start sync'] 1506 discovery_type = params['Optional Parameter']['Discovery type'] 1507 mastername = 'autodetect' 1508 masteruri = 'ROS_MASTER_URI' 1509 if len(hostnames) < 2: 1510 mastername = params['Optional Parameter']['ROS Master Name'] 1511 masteruri = params['Optional Parameter']['ROS Master URI'] 1512 robot_hosts = params['Optional Parameter']['Robot hosts'] 1513 username = params['Optional Parameter']['Username'] 1514 mcast_group = params['Optional Parameter']['MCast Group'] 1515 heartbeat_hz = params['Optional Parameter']['Heartbeat [Hz]'] 1516 if robot_hosts: 1517 robot_hosts = robot_hosts.replace(' ', ',') 1518 robot_hosts = robot_hosts.replace(',,', ',') 1519 robot_hosts = robot_hosts.replace('[', '') 1520 robot_hosts = robot_hosts.replace(']', '') 1521 for hostname in hostnames: 1522 try: 1523 args = [] 1524 if port is not None and port and int(port) < 100 and int(port) >= 0: 1525 args.append('_mcast_port:=%s' % (11511 + int(port))) 1526 else: 1527 args.append('_mcast_port:=%s' % (11511)) 1528 if not mastername == 'autodetect': 1529 args.append('_name:=%s' % (mastername)) 1530 args.append('_mcast_group:=%s' % mcast_group) 1531 args.append('_robot_hosts:=[%s]' % robot_hosts) 1532 args.append('_heartbeat_hz:=%s' % heartbeat_hz) 1533 # TODO: remove the name parameter from the ROS parameter server 1534 usr = username 1535 if username == 'last used': 1536 usr = nm.settings().host_user(hostname) 1537 muri = None if masteruri == 'ROS_MASTER_URI' else utf8(masteruri) 1538 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1539 'start discovering on %s' % hostname, 1540 nm.starter().runNodeWithoutConfig, 1541 (utf8(hostname), 'master_discovery_fkie', utf8(discovery_type), utf8(discovery_type), args, muri, False, usr)) 1542 1543 # start the master sync with default settings 1544 if start_sync: 1545 if nm.is_local(hostname): 1546 default_sync_args = ["_interface_url:='.'", 1547 '_sync_topics_on_demand:=False', 1548 '_ignore_hosts:=[]', '_sync_hosts:=[]', 1549 '_ignore_nodes:=[]', '_sync_nodes:=[]', 1550 '_ignore_topics:=[]', '_sync_topics:=[]', 1551 '_ignore_services:=[]', '_sync_services:=[]', 1552 '_sync_remote_nodes:=False'] 1553 self._progress_queue_sync.add2queue(utf8(uuid.uuid4()), 1554 'start sync on %s' % hostname, 1555 nm.starter().runNodeWithoutConfig, 1556 (utf8(hostname), 'master_sync_fkie', 'master_sync', 'master_sync', default_sync_args, muri, False, usr)) 1557 self._progress_queue_sync.start() 1558 else: 1559 if hostname not in self._syncs_to_start: 1560 self._syncs_to_start.append(hostname) 1561 except (Exception, nm.StartException) as e: 1562 import traceback 1563 print traceback.format_exc(1) 1564 rospy.logwarn("Error while start master_discovery for %s: %s" % (utf8(hostname), utf8(e))) 1565 MessageBox.warning(self, "Start error", 1566 'Error while start master_discovery', 1567 utf8(e)) 1568 self._progress_queue.start() 1569 except Exception as e: 1570 MessageBox.warning(self, "Start error", 1571 'Error while parse parameter', 1572 utf8(e))
1573
1574 - def _join_network(self, network):
1575 try: 1576 hostname = 'localhost' 1577 args = [] 1578 if network < 100 and network >= 0: 1579 args.append(''.join(['_mcast_port:=', utf8(11511 + int(network))])) 1580 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1581 'start discovering on ' + utf8(hostname), 1582 nm.starter().runNodeWithoutConfig, 1583 (utf8(hostname), 'master_discovery_fkie', 'master_discovery', 'master_discovery', args, None, False)) 1584 self._progress_queue.start() 1585 except (Exception, nm.StartException), e: 1586 rospy.logwarn("Error while start master_discovery for %s: %s", utf8(hostname), utf8(e)) 1587 MessageBox.warning(self, "Start error", 1588 'Error while start master_discovery', 1589 utf8(e))
1590
1591 - def poweroff_host(self, host):
1592 try: 1593 if nm.is_local(utf8(host)): 1594 ret = MessageBox.warning(self, "ROS Node Manager", 1595 "Do you really want to shutdown localhost?", 1596 buttons=MessageBox.Ok | MessageBox.Cancel) 1597 if ret == MessageBox.Cancel: 1598 return 1599 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1600 'poweroff `%s`' % host, 1601 nm.starter().poweroff, 1602 ('%s' % host,)) 1603 masteruris = nm.nameres().masterurisbyaddr(host) 1604 for masteruri in masteruris: 1605 master = self.getMaster(masteruri) 1606 master.stop_nodes_by_name(['/master_discovery']) 1607 self._progress_queue.start() 1608 self.on_description_update('Description', '') 1609 self.launch_dock.raise_() 1610 except (Exception, nm.StartException), e: 1611 rospy.logwarn("Error while poweroff %s: %s", host, utf8(e)) 1612 MessageBox.warning(self, "Run error", 1613 'Error while poweroff %s' % host, 1614 '%s' % utf8(e))
1615
1616 - def rosclean(self, host):
1617 try: 1618 ret = MessageBox.warning(self, "ROS Node Manager", 1619 "Do you really want delete all logs on `%s`?" % host, 1620 buttons=MessageBox.Ok | MessageBox.Cancel) 1621 if ret == MessageBox.Cancel: 1622 return 1623 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1624 'rosclean `%s`' % host, 1625 nm.starter().rosclean, 1626 ('%s' % host,)) 1627 self._progress_queue.start() 1628 self.launch_dock.raise_() 1629 except (Exception, nm.StartException), e: 1630 rospy.logwarn("Error while rosclean %s: %s", host, utf8(e)) 1631 MessageBox.warning(self, "Run error", 1632 'Error while rosclean %s' % host, 1633 '%s' % utf8(e))
1634 1635 # ====================================================================================================================== 1636 # Handling of the launch view signals 1637 # ====================================================================================================================== 1638
1639 - def on_load_launch_file(self, path, argv=[], masteruri=None):
1640 ''' 1641 Load the launch file. A ROS master must be selected first. 1642 :param path: the path of the launch file. 1643 :type path: str 1644 ''' 1645 master_proxy = None 1646 if masteruri is not None: 1647 master_proxy = self.getMaster(masteruri, False) 1648 if master_proxy is None: 1649 master_proxy = self.stackedLayout.currentWidget() 1650 if isinstance(master_proxy, MasterViewProxy): 1651 try: 1652 master_proxy.launchfiles = (path, argv) 1653 except Exception, e: 1654 import traceback 1655 print utf8(traceback.format_exc(1)) 1656 MessageBox.warning(self, "Loading launch file", path, '%s' % utf8(e)) 1657 # self.setCursor(cursor) 1658 else: 1659 MessageBox.information(self, "Load of launch file", "Select a master first!",)
1660
1661 - def on_load_launch_as_default_bypkg(self, pkg, launch_file, master_proxy, args=[], host=None):
1662 argv = list(args) 1663 argv.append('_package:=%s' % pkg) 1664 argv.append('_launch_file:="%s"' % launch_file) 1665 hostname = host if host else nm.nameres().address(master_proxy.masteruri) 1666 name_file_prefix = launch_file.replace('.launch', '').replace(' ', '_') 1667 node_name = roslib.names.SEP.join(['%s' % nm.nameres().masteruri2name(master_proxy.masteruri), 1668 name_file_prefix, 1669 'default_cfg']) 1670 self.launch_dock.progress_queue.add2queue('%s' % uuid.uuid4(), 1671 'start default config %s' % hostname, 1672 nm.starter().runNodeWithoutConfig, 1673 ('%s' % hostname, 'default_cfg_fkie', 'default_cfg', 1674 node_name, argv, master_proxy.masteruri, False, 1675 master_proxy.current_user)) 1676 self.launch_dock.progress_queue.start()
1677
1678 - def on_load_launch_as_default(self, path, host=None):
1679 ''' 1680 Load the launch file as default configuration. A ROS master must be selected first. 1681 :param path: the path of the launch file. 1682 :type path: str 1683 :param host: The host name, where the configuration start. 1684 :type host: str (Default: None) 1685 ''' 1686 rospy.loginfo("LOAD launch as default: %s" % path) 1687 master_proxy = self.stackedLayout.currentWidget() 1688 if isinstance(master_proxy, MasterViewProxy): 1689 args = list() 1690 args.append('_package:=%s' % (package_name(os.path.dirname(path))[0])) 1691 args.append('_launch_file:="%s"' % os.path.basename(path)) 1692 try: 1693 # test for requerid args 1694 launchConfig = LaunchConfig(path) 1695 req_args = launchConfig.getArgs() 1696 if req_args: 1697 params = dict() 1698 arg_dict = launchConfig.argvToDict(req_args) 1699 for arg in arg_dict.keys(): 1700 params[arg] = ('string', [arg_dict[arg]]) 1701 inputDia = ParameterDialog(params) 1702 inputDia.setFilterVisible(False) 1703 inputDia.setWindowTitle('Enter the argv for %s' % path) 1704 if inputDia.exec_(): 1705 params = inputDia.getKeywords() 1706 args.extend(launchConfig.resolveArgs([''.join([p, ":='", v, "'"]) for p, v in params.items() if v])) 1707 else: 1708 return 1709 except: 1710 import traceback 1711 rospy.logwarn('Error while load %s as default: %s' % (path, traceback.format_exc(1))) 1712 hostname = host if host else nm.nameres().address(master_proxy.masteruri) 1713 name_file_prefix = os.path.basename(path).replace('.launch', '').replace(' ', '_') 1714 node_name = roslib.names.SEP.join(['%s' % nm.nameres().masteruri2name(master_proxy.masteruri), 1715 name_file_prefix, 1716 'default_cfg']) 1717 self.launch_dock.progress_queue.add2queue('%s' % uuid.uuid4(), 1718 'start default config %s' % hostname, 1719 nm.starter().runNodeWithoutConfig, 1720 ('%s' % hostname, 'default_cfg_fkie', 'default_cfg', 1721 node_name, args, master_proxy.masteruri, False, 1722 master_proxy.current_user)) 1723 self.launch_dock.progress_queue.start() 1724 else: 1725 MessageBox.information(self, "Load of launch file", "Select a master first!",)
1726
1727 - def on_launch_edit(self, files, search_text='', trynr=1):
1728 ''' 1729 Opens the given files in an editor. If the first file is already open, select 1730 the editor. If search text is given, search for the text in files an goto the 1731 line. 1732 :param file: A list with paths 1733 :type file: list of strings 1734 :param search_text: A string to search in file 1735 :type search_text: str 1736 ''' 1737 if files: 1738 path = files[0] 1739 if path in self.editor_dialogs: 1740 last_path = files[-1] 1741 try: 1742 self.editor_dialogs[path].on_load_request(last_path, search_text) 1743 self.editor_dialogs[path].restore() 1744 self.editor_dialogs[path].activateWindow() 1745 except Exception: 1746 if trynr > 1: 1747 raise 1748 del self.editor_dialogs[path] 1749 self.on_launch_edit(files, search_text, 2) 1750 else: 1751 editor = Editor(files, search_text) 1752 self.editor_dialogs[path] = editor 1753 editor.finished_signal.connect(self._editor_dialog_closed) 1754 editor.show()
1755
1756 - def _editor_dialog_closed(self, files):
1757 ''' 1758 If a editor dialog is closed, remove it from the list... 1759 ''' 1760 if files[0] in self.editor_dialogs: 1761 del self.editor_dialogs[files[0]]
1762
1763 - def on_launch_transfer(self, files):
1764 ''' 1765 Copies the selected file to a remote host 1766 :param file: A list with paths 1767 :type file: list of strings 1768 ''' 1769 if files: 1770 host = 'localhost' 1771 username = nm.settings().default_user 1772 if self.currentMaster is not None: 1773 host = get_hostname(self.currentMaster.masteruri) 1774 username = self.currentMaster.current_user 1775 params = {'Host': ('string', host), 1776 'recursive': ('bool', 'False'), 1777 'Username': ('string', '%s' % username) 1778 } 1779 dia = ParameterDialog(params) 1780 dia.setFilterVisible(False) 1781 dia.setWindowTitle('Transfer file') 1782 dia.resize(350, 120) 1783 dia.setFocusField('Host') 1784 if dia.exec_(): 1785 try: 1786 params = dia.getKeywords() 1787 host = params['Host'] 1788 recursive = params['recursive'] 1789 username = params['Username'] 1790 for path in files: 1791 rospy.loginfo("TRANSFER to %s@%s: %s" % (username, host, path)) 1792 self.launch_dock.progress_queue.add2queue('%s' % uuid.uuid4(), 1793 'transfer files to %s' % host, 1794 nm.starter().transfer_files, 1795 ('%s' % host, path, False, username)) 1796 if recursive: 1797 for f in LaunchConfig.included_files(path): 1798 self.launch_dock.progress_queue.add2queue(utf8(uuid.uuid4()), 1799 'transfer files to %s' % host, 1800 nm.starter().transfer_files, 1801 ('%s' % host, f, False, username)) 1802 self.launch_dock.progress_queue.start() 1803 except Exception, e: 1804 MessageBox.warning(self, "Transfer error", 1805 'Error while transfer files', '%s' % utf8(e))
1806
1807 - def _reload_globals_at_next_start(self, launch_file):
1808 if self.currentMaster is not None: 1809 self.currentMaster.reload_global_parameter_at_next_start(launch_file)
1810 1811 # ====================================================================================================================== 1812 # Change file detection 1813 # ====================================================================================================================== 1814
1815 - def on_configfile_changed(self, changed, affected):
1816 ''' 1817 Signal hander to handle the changes of a loaded configuration file 1818 @param changed: the changed file 1819 @type changed: C{str} 1820 @param affected: the list of tuples with masteruri and launchfile, which are affected by file change 1821 @type affected: list 1822 ''' 1823 # create a list of launch files and masters, which are affected by the changed file 1824 # and are not currently in question 1825 if self.isActiveWindow(): 1826 self._changed_files[changed] = affected 1827 self._check_for_changed_files() 1828 else: 1829 self._changed_files[changed] = affected
1830
1831 - def on_binaryfile_changed(self, changed, affected):
1832 ''' 1833 Signal hander to handle the changes started binaries. 1834 @param changed: the changed file 1835 @type changed: str 1836 @param affected: list of tuples(node name, masteruri, launchfile), which are 1837 affected by file change 1838 @type affected: list 1839 ''' 1840 if self.isActiveWindow(): 1841 self._changed_binaries[changed] = affected 1842 self._check_for_changed_binaries() 1843 else: 1844 self._changed_binaries[changed] = affected
1845
1846 - def _check_for_changed_files(self):
1847 ''' 1848 Check the dictinary with changed files and notify the masters about changes. 1849 ''' 1850 for changed, affected in self._changed_files.items(): # :=changed 1851 for (muri, lfile) in affected: 1852 master = self.getMaster(muri) 1853 if master is not None: 1854 master.question_reload_changed_file(changed, lfile) 1855 self._changed_files.clear()
1856
1858 ''' 1859 Check the dictinary with changed binaries and notify the masters about changes. 1860 ''' 1861 new_affected = list() 1862 for _, affected in self._changed_binaries.items(): # :=changed 1863 for (nname, muri, lfile) in affected: 1864 if not (nname, muri, lfile) in self.__in_question: 1865 self.__in_question.add((nname, muri, lfile)) 1866 new_affected.append((nname, muri, lfile)) 1867 # if there are no question to restart the nodes -> ask 1868 if new_affected: 1869 choices = dict() 1870 for (nname, muri, lfile) in new_affected: 1871 master = self.getMaster(muri) 1872 if master is not None and master.online: 1873 master_nodes = master.getNode(nname) 1874 if master_nodes and master_nodes[0].is_running(): 1875 choices[nname] = (master, lfile) 1876 else: 1877 nm.filewatcher().rem_binary(nname) 1878 if choices: 1879 nodes, _ = SelectDialog.getValue('Restart nodes?', 1880 '<b>%s</b> was changed.<br>Select affected nodes to restart:' % ', '.join([os.path.basename(f) for f in self._changed_binaries.keys()]), choices.keys(), 1881 False, True, 1882 '', 1883 self) 1884 for (nname, muri, lfile) in new_affected: 1885 self.__in_question.remove((nname, muri, lfile)) 1886 for nname in nodes: 1887 choices[nname][0].stop_nodes_by_name([nname]) 1888 for nname in nodes: 1889 choices[nname][0].start_nodes_by_name([nname], choices[nname][1], True) 1890 self._changed_binaries.clear()
1891
1892 - def on_configparamfile_changed(self, changed, affected):
1893 ''' 1894 Signal handler to handle the changes of a configuration file referenced by a parameter value 1895 @param changed: the changed file 1896 @type changed: C{str} 1897 @param affected: the list of tuples with masteruri and launchfile, which are affected by file change 1898 @type affected: list 1899 ''' 1900 # create a list of launch files and masters, which are affected by the changed file 1901 if self.isActiveWindow(): 1902 self._changed_files_param[changed] = affected 1903 self._check_for_changed_files_param() 1904 else: 1905 self._changed_files_param[changed] = affected
1906
1908 ''' 1909 Check the dictinary with changed files and notify about the transfer of changed file. 1910 ''' 1911 for changed, affected in self._changed_files_param.items(): 1912 for (muri, lfile) in affected: 1913 master = self.getMaster(muri) 1914 if master is not None: 1915 master.question_transfer_changed_file(changed, lfile) 1916 self._changed_files_param.clear()
1917
1918 - def changeEvent(self, event):
1919 ''' 1920 Check for changed files, if the main gui is activated. 1921 ''' 1922 QMainWindow.changeEvent(self, event) 1923 self._check_for_changed_files() 1924 self._check_for_changed_binaries() 1925 self._check_for_changed_files_param()
1926 1927 # ====================================================================================================================== 1928 # Capabilities handling 1929 # ====================================================================================================================== 1930
1931 - def on_start_nodes(self, masteruri, cfg, nodes):
1932 if masteruri is not None: 1933 master = self.getMaster(masteruri) 1934 master.start_nodes_by_name(nodes, roslib.names.ns_join(cfg, 'run'))
1935
1936 - def on_stop_nodes(self, masteruri, nodes):
1937 if masteruri is not None: 1938 master = self.getMaster(masteruri) 1939 master.stop_nodes_by_name(nodes)
1940
1941 - def on_description_update(self, title, text, force=False):
1942 same_title = self.descriptionDock.windowTitle() == title 1943 valid_sender = self.sender() == self.currentMaster or not isinstance(self.sender(), MasterViewProxy) 1944 no_focus = not self.descriptionTextEdit.hasFocus() 1945 if (valid_sender) and (same_title or no_focus or self._accept_next_update): 1946 self._accept_next_update = False 1947 self.descriptionDock.setWindowTitle(title) 1948 self.descriptionTextEdit.setText(text) 1949 if text and force: # and not (self.launch_dock.hasFocus() or self.launch_dock.xmlFileView.hasFocus()): 1950 self.descriptionDock.raise_()
1951 # else: 1952 # self.launch_dock.raise_() 1953
1954 - def on_description_update_cap(self, title, text):
1955 self.descriptionDock.setWindowTitle(title) 1956 self.descriptionTextEdit.setText(text)
1957
1958 - def on_description_anchorClicked(self, url):
1959 self._accept_next_update = True 1960 if url.toString().startswith('open-sync-dialog://'): 1961 self.on_sync_dialog_released(False, url.toString().replace('open-sync-dialog', 'http'), True) 1962 elif url.toString().startswith('show-all-screens://'): 1963 master = self.getMaster(url.toString().replace('show-all-screens', 'http'), False) 1964 if master is not None: 1965 master.on_show_all_screens() 1966 elif url.toString().startswith('remove-all-launch-server://'): 1967 master = self.getMaster(url.toString().replace('remove-all-launch-server', 'http'), False) 1968 if master is not None: 1969 master.on_remove_all_launch_server() 1970 elif url.toString().startswith('node://'): 1971 if self.currentMaster is not None: 1972 self.currentMaster.on_node_selection_changed(