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

Source Code for Module node_manager_fkie.main_window

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