1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import os
34 import time
35 import uuid
36 import xmlrpclib
37 import getpass
38
39 from datetime import datetime
40
41 from python_qt_binding import QtGui
42 from python_qt_binding import QtCore
43
44 from python_qt_binding import loadUi
45
46 import roslib; roslib.load_manifest('node_manager_fkie')
47 import rospy
48
49 import gui_resources
50 from .discovery_listener import MasterListService, MasterStateTopic, MasterStatisticTopic, OwnMasterMonitoring
51 from .update_handler import UpdateHandler
52 from .master_view_proxy import MasterViewProxy
53 from .launch_config import LaunchConfig
54 from .capability_table import CapabilityTable
55 from .xml_editor import XmlEditor
56 from .detailed_msg_box import WarningMessageBox
57 from .network_discovery_dialog import NetworkDiscoveryDialog
58 from .parameter_dialog import ParameterDialog
59 from .progress_queue import ProgressQueue
60 from .screen_handler import ScreenHandler
61 from .sync_dialog import SyncDialog
62 from .common import masteruri_from_ros, package_name
63 from .select_dialog import SelectDialog
64 from .master_list_model import MasterModel, MasterSyncItem
65 from .log_widget import LogWidget
66 from .launch_files_widget import LaunchFilesWidget
67 from .settings_widget import SettingsWidget
68 from .menu_rqt import MenuRqt
69
70 import node_manager_fkie as nm
71
72 from multimaster_msgs_fkie.msg import LinkState, LinkStatesStamped, MasterState
73 from master_discovery_fkie.common import resolve_url
74
75
76
77 -class MainWindow(QtGui.QMainWindow):
78 '''
79 The class to create the main window of the application.
80 '''
81 DELAYED_NEXT_REQ_ON_ERR = 5.0
82
83 - def __init__(self, files=[], restricted_to_one_master=False, parent=None):
84 '''
85 Creates the window, connects the signals and init the class.
86 '''
87 QtGui.QMainWindow.__init__(self)
88 restricted_to_one_master = False
89 self._finished = False
90 self._history_selected_robot = ''
91 self.__icons = {'empty' : (QtGui.QIcon(), ''),
92 'default_pc' : (QtGui.QIcon(':/icons/crystal_clear_miscellaneous.png'), ':/icons/crystal_clear_miscellaneous.png'),
93 'log_warning' : (QtGui.QIcon(':/icons/crystal_clear_warning.png'), ':/icons/crystal_clear_warning.png')
94 }
95 self.__current_icon = None
96 self.__current_master_label_name = None
97 self._changed_files = dict()
98 self._changed_files_param = dict()
99
100
101 self.setObjectName('MainWindow')
102
103
104 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'MainWindow.ui')
105
106 loadUi(ui_file, self)
107 self.setObjectName('MainUI')
108 self.user_frame.setVisible(False)
109 self._add_user_to_combo(getpass.getuser())
110 self.userComboBox.editTextChanged.connect(self.on_user_changed)
111 self.masterInfoFrame.setEnabled(False)
112 self.infoButton.clicked.connect(self.on_info_clicked)
113 self.refreshHostButton.clicked.connect(self.on_refresh_master_clicked)
114 self.runButton.clicked.connect(self.on_run_node_clicked)
115 self.syncButton.released.connect(self.on_sync_dialog_released)
116
117 menu_rqt = MenuRqt(self.rqtButton)
118 menu_rqt.start_rqt_plugin_signal.connect(self.on_rqt_plugin_start)
119
120
121 self.settings_dock = SettingsWidget(self)
122 self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.settings_dock)
123
124 self.log_dock = LogWidget(self)
125 self.log_dock.added_signal.connect(self._on_log_added)
126 self.log_dock.cleared_signal.connect(self._on_log_cleared)
127 self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.log_dock)
128 self.logButton.clicked.connect(self._on_log_button_clicked)
129
130 self.launch_dock = LaunchFilesWidget(self)
131 self.launch_dock.load_signal.connect(self.on_load_launch_file)
132 self.launch_dock.load_as_default_signal.connect(self.on_load_launch_as_default)
133 self.launch_dock.edit_signal.connect(self.on_launch_edit)
134 self.launch_dock.transfer_signal.connect(self.on_launch_transfer)
135 self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.launch_dock)
136
137 self.mIcon = QtGui.QIcon(":/icons/crystal_clear_prop_run.png")
138 self.setWindowIcon(self.mIcon)
139 self.setWindowTitle("Node Manager")
140
141
142
143 self.stackedLayout = QtGui.QStackedLayout()
144 self.stackedLayout.setObjectName('stackedLayout')
145 emptyWidget = QtGui.QWidget()
146 emptyWidget.setObjectName('emptyWidget')
147 self.stackedLayout.addWidget(emptyWidget)
148 self.tabWidget.currentChanged.connect(self.on_currentChanged_tab)
149 self.tabLayout = QtGui.QVBoxLayout(self.tabPlace)
150 self.tabLayout.setObjectName("tabLayout")
151 self.tabLayout.setContentsMargins(0, 0, 0, 0)
152 self.tabLayout.addLayout(self.stackedLayout)
153
154
155 self._progress_queue = ProgressQueue(self.progressFrame, self.progressBar, self.progressCancelButton)
156 self._progress_queue_sync = ProgressQueue(self.progressFrame_sync, self.progressBar_sync, self.progressCancelButton_sync)
157
158
159 self.master_model = MasterModel(self.getMasteruri())
160 self.masterTableView.setModel(self.master_model)
161
162 self.masterTableView.clicked.connect(self.on_master_table_clicked)
163 self.masterTableView.pressed.connect(self.on_master_table_pressed)
164 self.masterTableView.activated.connect(self.on_master_table_activated)
165 sm = self.masterTableView.selectionModel()
166 sm.currentRowChanged.connect(self.on_masterTableView_selection_changed)
167 for i, (_, width) in enumerate(MasterModel.header):
168 self.masterTableView.setColumnWidth(i, width)
169 self.refreshAllButton.clicked.connect(self.on_all_master_refresh_clicked)
170 self.discoveryButton.clicked.connect(self.on_discover_network_clicked)
171 self.startRobotButton.clicked.connect(self.on_start_robot_clicked)
172
173
174 self.masters = dict()
175 self.currentMaster = None
176 self._close_on_exit = True
177
178 nm.file_watcher().file_changed.connect(self.on_configfile_changed)
179 nm.file_watcher_param().file_changed.connect(self.on_configparamfile_changed)
180 self.__in_question = set()
181
182
183 self.capabilitiesTable = CapabilityTable(self.capabilities_tab)
184 self.capabilitiesTable.setObjectName("capabilitiesTable")
185 self.capabilitiesTable.start_nodes_signal.connect(self.on_start_nodes)
186 self.capabilitiesTable.stop_nodes_signal.connect(self.on_stop_nodes)
187 self.capabilitiesTable.description_requested_signal.connect(self.on_description_update_cap)
188 self.capabilities_tab.layout().addWidget(self.capabilitiesTable)
189
190 self.descriptionTextEdit.setOpenLinks(False)
191 self.descriptionTextEdit.anchorClicked.connect(self.on_description_anchorClicked)
192 self._shortcut_copy = QtGui.QShortcut(QtGui.QKeySequence(self.tr("Ctrl+Shift+C", "copy selected description")), self.descriptionTextEdit)
193 self._shortcut_copy.activated.connect(self.descriptionTextEdit.copy)
194
195 self.tabifyDockWidget(self.launch_dock, self.descriptionDock)
196 self.tabifyDockWidget(self.launch_dock, self.settings_dock)
197 self.tabifyDockWidget(self.launch_dock, self.helpDock)
198 self.launch_dock.raise_()
199 self.helpDock.setWindowIcon(QtGui.QIcon(':icons/crystal_clear_helpcenter.png'))
200
201 flags = self.windowFlags()
202 self.setWindowFlags(flags | QtCore.Qt.WindowContextHelpButtonHint)
203
204 self.default_load_launch = os.path.abspath(resolve_url(files[0])) if files else ''
205 if self.default_load_launch:
206 if os.path.isdir(self.default_load_launch):
207 self.launch_dock.launchlist_model.setPath(self.default_load_launch)
208 elif os.path.isfile(self.default_load_launch):
209 self.launch_dock.launchlist_model.setPath(os.path.dirname(self.default_load_launch))
210
211 self._discover_dialog = None
212 self.restricted_to_one_master = restricted_to_one_master
213 if restricted_to_one_master:
214 self.syncButton.setEnabled(False)
215 self.refreshAllButton.setEnabled(False)
216 self.discoveryButton.setEnabled(False)
217 self.startRobotButton.setEnabled(False)
218
219 self._sync_dialog = SyncDialog()
220
221 self.editor_dialogs = dict()
222 '''@ivar: stores the open XmlEditor '''
223
224 self.simTimeLabel.setVisible(False)
225 self.launchServerLabel.setVisible(False)
226
227
228 nm.is_local("localhost")
229
230
231 try:
232 from docutils import examples
233 with file(nm.settings().HELP_FILE) as f:
234 self.textBrowser.setText(examples.html_body(unicode(f.read())))
235 except:
236 import traceback
237 msg = "Error while generate help: %s"%traceback.format_exc(2)
238 rospy.logwarn(msg)
239 self.textBrowser.setText(msg)
240
241 try:
242 ScreenHandler.testScreen()
243 except Exception as e:
244 rospy.logerr("No SCREEN available! You can't launch nodes.")
245
246
247
248
249 self.imageLabel.mouseDoubleClickEvent = self.image_mouseDoubleClickEvent
250
251 try:
252 self.readSettings()
253 self.launch_dock.raise_()
254 except Exception as e:
255 rospy.logwarn("Error while read settings: %s"%e)
256
257 docks = self._dock_widget_in(QtCore.Qt.LeftDockWidgetArea, only_visible=True)
258 if not docks:
259 self.hideDocksButton.toggle()
260 self.on_hide_docks_toggled(True)
261 self.hideDocksButton.clicked.connect(self.on_hide_docks_toggled)
262
263
264
265
266
267
268 self._update_handler = UpdateHandler()
269 self._update_handler.master_info_signal.connect(self.on_master_info_retrieved)
270 self._update_handler.master_errors_signal.connect(self.on_master_errors_retrieved)
271 self._update_handler.error_signal.connect(self.on_master_info_error)
272
273
274 self.own_master_monitor = OwnMasterMonitoring()
275 self.own_master_monitor.init(22622)
276 self.own_master_monitor.state_signal.connect(self.on_master_state_changed)
277 self.own_master_monitor.err_signal.connect(self.on_master_monitor_err)
278
279
280 self.masterlist_service = masterlist_service = MasterListService()
281 masterlist_service.masterlist_signal.connect(self.on_master_list_retrieved)
282 masterlist_service.masterlist_err_signal.connect(self.on_master_list_err_retrieved)
283 self.state_topic = MasterStateTopic()
284 self.state_topic.state_signal.connect(self.on_master_state_changed)
285 self.stats_topic = MasterStatisticTopic()
286 self.stats_topic.stats_signal.connect(self.on_conn_stats_updated)
287
288
289 self.master_timecheck_timer = QtCore.QTimer()
290 self.master_timecheck_timer.timeout.connect(self.on_master_timecheck)
291 self.master_timecheck_timer.start(1000)
292 self._refresh_time = time.time()
293 self._last_time_view_update = time.time()
294
295 self._con_tries = dict()
296 self._subscribe()
297
298 - def _dock_widget_in(self, area=QtCore.Qt.LeftDockWidgetArea, only_visible=False):
299 result = []
300 docks = [self.launch_dock, self.descriptionDock, self.helpDock, self.networkDock]
301 for dock in docks:
302 if self.dockWidgetArea(dock) == area:
303 if not only_visible or (only_visible and dock.isVisibleTo(self)):
304 result.append(dock)
305 return result
306
308 self.log_dock.setVisible(not self.log_dock.isVisible())
309
310 - def _on_log_added(self, info, warn, err, fatal):
311 self.logButton.setEnabled(True)
312
313 - def _on_log_cleared(self):
314 self.logButton.setIcon(self.__icons['log_warning'][0])
315 self.logButton.setText('')
316 self.logButton.setEnabled(False)
317
318 - def on_hide_docks_toggled(self, checked):
319 if self.dockWidgetArea(self.launch_dock) == QtCore.Qt.LeftDockWidgetArea:
320 self.launch_dock.setVisible(not checked)
321 if self.dockWidgetArea(self.descriptionDock) == QtCore.Qt.LeftDockWidgetArea:
322 self.descriptionDock.setVisible(not checked)
323 if self.dockWidgetArea(self.helpDock) == QtCore.Qt.LeftDockWidgetArea:
324 self.helpDock.setVisible(not checked)
325 if self.dockWidgetArea(self.networkDock) == QtCore.Qt.LeftDockWidgetArea:
326 self.networkDock.setVisible(not checked)
327 if self.dockWidgetArea(self.settings_dock) == QtCore.Qt.LeftDockWidgetArea:
328 self.settings_dock.setVisible(not checked)
329 self.hideDocksButton.setArrowType(QtCore.Qt.RightArrow if checked else QtCore.Qt.LeftArrow)
330
331 - def on_currentChanged_tab(self, index):
333
334
335
336
337
338
339
340 - def readSettings(self):
341 if nm.settings().store_geometry:
342 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE)
343 self._history_selected_robot = settings.value("selected_robot", '')
344 settings.beginGroup("mainwindow")
345 maximized = settings.value("maximized", 'false') == 'true'
346 if maximized:
347 self.showMaximized()
348 else:
349 self.resize(settings.value("size", QtCore.QSize(1024, 720)))
350 self.move(settings.value("pos", QtCore.QPoint(0, 0)))
351 try:
352 self.restoreState(settings.value("window_state"))
353 except:
354 pass
355 settings.endGroup()
356
357 - def storeSetting(self):
358 if nm.settings().store_geometry:
359 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE)
360 settings.beginGroup("mainwindow")
361 settings.setValue("size", self.size())
362 settings.setValue("pos", self.pos())
363 settings.setValue("maximized", self.isMaximized())
364 settings.setValue("window_state", self.saveState())
365 settings.endGroup()
366
367 - def closeEvent(self, event):
368
369 if self._close_on_exit:
370 masters2stop, self._close_on_exit = SelectDialog.getValue('Stop nodes?', "Select masters where to stop:", self.masters.keys(), False, False, '', self, select_if_single=False)
371 if self._close_on_exit:
372 self._on_finish = True
373 for uri in masters2stop:
374 try:
375 m = self.masters[uri]
376 if not m is None:
377 m.stop_nodes_by_name(m.getRunningNodesIfLocal())
378 except Exception as e:
379 rospy.logwarn("Error while stop nodes on %s: %s"%(uri, e))
380 QtCore.QTimer.singleShot(200, self._test_for_finish)
381 else:
382 self._close_on_exit = True
383 event.ignore()
384 else:
385 try:
386 self.storeSetting()
387 except Exception as e:
388 rospy.logwarn("Error while store settings: %s"%e)
389 self.finish()
390 QtGui.QMainWindow.closeEvent(self, event)
391
393
394 for uri, m in self.masters.items():
395 if m.in_process():
396 QtCore.QTimer.singleShot(200, self._test_for_finish)
397 return
398 self._close_on_exit = False
399 self.close()
400
402 if not self._finished:
403 self._finished = True
404 print "Mainwindow finish..."
405 self._progress_queue.stop()
406 self._progress_queue_sync.stop()
407 self._update_handler.stop()
408 self.state_topic.stop()
409 self.stats_topic.stop()
410 for _, master in self.masters.iteritems():
411 master.stop()
412 self.own_master_monitor.stop()
413 self.master_timecheck_timer.stop()
414 self.launch_dock.stop()
415 self.log_dock.stop()
416 print "Mainwindow finished!"
417
418 - def getMasteruri(self):
419 '''
420 Requests the ROS master URI from the ROS master through the RPC interface and
421 returns it. The 'materuri' attribute will be set to the requested value.
422 @return: ROS master URI
423 @rtype: C{str} or C{None}
424 '''
425 if not hasattr(self, 'materuri') or self.materuri is None:
426 masteruri = masteruri_from_ros()
427 master = xmlrpclib.ServerProxy(masteruri)
428 _, _, self.materuri = master.getUri(rospy.get_name())
429 nm.is_local(nm.nameres().getHostname(self.materuri))
430 return self.materuri
431
432 - def removeMaster(self, masteruri):
433 '''
434 Removed master with given master URI from the list.
435 @param masteruri: the URI of the ROS master
436 @type masteruri: C{str}
437 '''
438 if masteruri in self.masters:
439 if not self.currentMaster is None and self.currentMaster.masteruri == masteruri:
440 self.setCurrentMaster(None)
441 self.masters[masteruri].stop()
442 self.masters[masteruri].updateHostRequest.disconnect()
443 self.masters[masteruri].host_description_updated.disconnect()
444 self.masters[masteruri].capabilities_update_signal.disconnect()
445 self.masters[masteruri].remove_config_signal.disconnect()
446 self.masters[masteruri].description_signal.disconnect()
447 self.masters[masteruri].request_xml_editor.disconnect()
448 self.masters[masteruri].stop_nodes_signal.disconnect()
449 self.masters[masteruri].robot_icon_updated.disconnect()
450 self.stackedLayout.removeWidget(self.masters[masteruri])
451 self.tabPlace.layout().removeWidget(self.masters[masteruri])
452 for cfg in self.masters[masteruri].default_cfgs:
453 self.capabilitiesTable.removeConfig(cfg)
454 self.masters[masteruri].setParent(None)
455 del self.masters[masteruri]
456
457 - def getMaster(self, masteruri, create_new=True):
458 '''
459 @return: the Widget which represents the master of given ROS master URI. If no
460 Widget for given URI is available a new one will be created.
461 @rtype: L{MasterViewProxy}
462 '''
463 if not masteruri in self.masters:
464 if not create_new:
465 return None
466 self.masters[masteruri] = MasterViewProxy(masteruri, self)
467 self.masters[masteruri].updateHostRequest.connect(self.on_host_update_request)
468 self.masters[masteruri].host_description_updated.connect(self.on_host_description_updated)
469 self.masters[masteruri].capabilities_update_signal.connect(self.on_capabilities_update)
470 self.masters[masteruri].remove_config_signal.connect(self.on_remove_config)
471 self.masters[masteruri].description_signal.connect(self.on_description_update)
472 self.masters[masteruri].request_xml_editor.connect(self.on_launch_edit)
473 self.masters[masteruri].stop_nodes_signal.connect(self.on_stop_nodes)
474 self.masters[masteruri].robot_icon_updated.connect(self._on_robot_icon_changed)
475 self.stackedLayout.addWidget(self.masters[masteruri])
476 if masteruri == self.getMasteruri():
477 if self.default_load_launch:
478 try:
479 if os.path.isfile(self.default_load_launch):
480 args = list()
481 args.append('_package:=%s'%(package_name(os.path.dirname(self.default_load_launch))[0]))
482 args.append('_launch_file:="%s"'%os.path.basename(self.default_load_launch))
483 host = '%s'%nm.nameres().address(masteruri)
484 node_name = roslib.names.SEP.join(['%s'%(nm.nameres().mastername(masteruri)),
485 os.path.basename(self.default_load_launch).replace('.launch',''),
486 'default_cfg'])
487 self.launch_dock.progress_queue.add2queue('%s'%uuid.uuid4(),
488 'start default config @%s'%host,
489 nm.starter().runNodeWithoutConfig,
490 ('%s'%(nm.nameres().mastername(masteruri)), 'default_cfg_fkie',
491 'default_cfg', node_name,
492 args, masteruri, False,
493 self.masters[masteruri].current_user))
494 self.launch_dock.progress_queue.start()
495 except Exception as e:
496 WarningMessageBox(QtGui.QMessageBox.Warning, "Load default configuration",
497 'Load default configuration %s failed!'%self.default_load_launch,
498 '%s'%e).exec_()
499 return self.masters[masteruri]
500
502 for key, value in self.masters.items():
503 if nm.nameres().getHostname(key) == host and not value.master_state is None:
504 self._update_handler.requestMasterInfo(value.master_state.uri, value.master_state.monitoruri)
505
506 - def on_host_description_updated(self, masteruri, host, descr):
509
510 - def on_capabilities_update(self, masteruri, address, config_node, descriptions):
511 for d in descriptions:
512 self.capabilitiesTable.updateCapabilities(masteruri, config_node, d)
513 if not masteruri is None:
514 master = self.getMaster(masteruri)
515 self.capabilitiesTable.updateState(masteruri, master.master_info)
516
517 - def on_remove_config(self, cfg):
518 self.capabilitiesTable.removeConfig(cfg)
519
520
521
522
523
524
525 - def _subscribe(self):
526 '''
527 Try to subscribe to the topics of the master_discovery node. If it fails, the
528 own local monitoring of the ROS master state will be enabled.
529 '''
530 if not self.restricted_to_one_master:
531 try:
532 result_1 = self.state_topic.registerByROS(self.getMasteruri(), False)
533 result_2 = self.stats_topic.registerByROS(self.getMasteruri(), False)
534 self.masterlist_service.retrieveMasterList(self.getMasteruri(), False)
535 if not result_1 or not result_2:
536 self._setLocalMonitoring(True)
537 except:
538 pass
539 else:
540 self._setLocalMonitoring(True)
541
542 - def _setLocalMonitoring(self, on):
543 '''
544 Enables the local monitoring of the ROS master state and disables the view of
545 the discoved ROS master.
546 @param on: the enable / disable the local monitoring
547 @type on: C{boolean}
548 '''
549 self.masterTableView.setEnabled(not on)
550 self.refreshAllButton.setEnabled(not on)
551 self.own_master_monitor.pause(not on)
552 if on:
553 self.masterTableView.setToolTip("use 'Start' button to enable the master discovering")
554 else:
555 self.masterTableView.setToolTip('')
556 if on:
557
558 for uri in self.masters.keys():
559 master = self.masters[uri]
560 if nm.is_local(nm.nameres().getHostname(uri)) or uri == self.getMasteruri():
561 if not self._history_selected_robot or master.mastername == self._history_selected_robot:
562 self.setCurrentMaster(master)
563 else:
564 if not master.master_state is None:
565 self.master_model.removeMaster(master.master_state.name)
566 self.removeMaster(uri)
567
568
569
570
571 - def on_master_list_err_retrieved(self, masteruri, error):
572 '''
573 The callback method connected to the signal, which is emitted on an error
574 while call the service to determine the discovered ROS master. On the error
575 the local monitoring will be enabled.
576 '''
577 self._setLocalMonitoring(True)
578
579 - def hasDiscoveryService(self, minfo):
580 '''
581 Test whether the new retrieved MasterInfo contains the master_discovery node.
582 This is identified by a name of the contained 'list_masters' service.
583 @param minfo: the ROS master Info
584 @type minfo: L{master_discovery_fkie.MasterInfo}
585 '''
586
587 if self.restricted_to_one_master:
588 return False
589 for service in minfo.services.keys():
590 if service.endswith('list_masters'):
591 return True
592 return False
593
594
595
596
597
598
599 - def on_master_list_retrieved(self, masteruri, servic_name, master_list):
600 '''
601 Handle the retrieved list with ROS master.
602 1. update the ROS Network view
603 @param master_list: a list with ROS masters
604 @type master_list: C{[L{master_discovery_fkie.msg.MasterState}]}
605 '''
606 self._setLocalMonitoring(False)
607 self._con_tries[masteruri] = 0
608
609 new_uris = [m.uri for m in master_list if not m.uri is None]
610 for uri in self.masters.keys():
611 if uri not in new_uris:
612 master = self.masters[uri]
613 if not (nm.is_local(nm.nameres().getHostname(uri)) or uri == self.getMasteruri()):
614 if not master.master_state is None:
615 self.master_model.removeMaster(master.master_state.name)
616 self.removeMaster(uri)
617
618 for m in master_list:
619 if not m.uri is None:
620 host = nm.nameres().getHostname(m.uri)
621 nm.nameres().addMasterEntry(m.uri, m.name, host, host)
622 m.name = nm.nameres().mastername(m.uri)
623 master = self.getMaster(m.uri)
624 master.master_state = m
625 master.force_next_update()
626 self._assigne_icon(m.name)
627 self.master_model.updateMaster(m)
628 self._update_handler.requestMasterInfo(m.uri, m.monitoruri)
629
631 '''
632 Handle the received master state message.
633 1. update the ROS Network view
634 2. enable local master monitoring, if all masters are removed (the local master too)
635 @param msg: the ROS message with new master state
636 @type msg: L{master_discovery_fkie.msg.MasterState}
637 '''
638
639
640 if hasattr(self, "_on_finish"):
641 rospy.logdebug("ignore changes on %s, because currently on closing...", msg.master.uri)
642 return;
643 host=nm.nameres().getHostname(msg.master.uri)
644 if msg.state == MasterState.STATE_CHANGED:
645 nm.nameres().addMasterEntry(msg.master.uri, msg.master.name, host, host)
646 msg.master.name = nm.nameres().mastername(msg.master.uri)
647 self.getMaster(msg.master.uri).master_state = msg.master
648 self._assigne_icon(msg.master.name)
649 self.master_model.updateMaster(msg.master)
650
651 if nm.settings().autoupdate:
652 self._update_handler.requestMasterInfo(msg.master.uri, msg.master.monitoruri)
653 else:
654 rospy.loginfo("Autoupdate disabled, the data will not be updated for %s"%msg.master.uri)
655 if msg.state == MasterState.STATE_NEW:
656
657 if msg.master.uri == self.getMasteruri():
658 self.masterlist_service.retrieveMasterList(msg.master.uri, False)
659 nm.nameres().addMasterEntry(msg.master.uri, msg.master.name, host, host)
660 msg.master.name = nm.nameres().mastername(msg.master.uri)
661 self.getMaster(msg.master.uri).master_state = msg.master
662 self._assigne_icon(msg.master.name)
663 self.master_model.updateMaster(msg.master)
664
665 if nm.settings().autoupdate:
666 self._update_handler.requestMasterInfo(msg.master.uri, msg.master.monitoruri)
667 else:
668 rospy.loginfo("Autoupdate disabled, the data will not be updated for %s"%msg.master.uri)
669 if msg.state == MasterState.STATE_REMOVED:
670 if msg.master.uri == self.getMasteruri():
671
672 self._setLocalMonitoring(True)
673 else:
674 nm.nameres().removeMasterEntry(msg.master.uri)
675 self.master_model.removeMaster(msg.master.name)
676
677 self.removeMaster(msg.master.uri)
678
679
680
681
682 - def _assigne_icon(self, name, path=None):
683 '''
684 Sets the new icon to the given robot. If the path is `None` set search for
685 .png file with robot name.
686 :param name: robot name
687 :type name: str
688 :param path: path of the icon (Default: None)
689 :type path: str
690 '''
691 icon_path = path if path else nm.settings().robot_image_file(name)
692 if not self.__icons.has_key(name) or self.__icons[name][1] != path:
693 if QtCore.QFile.exists(icon_path):
694 self.__icons[name] = (QtGui.QIcon(icon_path), icon_path)
695 elif self.__icons.has_key(name):
696 del self.__icons[name]
697
699 self._con_tries[self.getMasteruri()] += 1
700
702 '''
703 Integrate the received master info.
704 @param minfo: the ROS master Info
705 @type minfo: L{master_discovery_fkie.MasterInfo}
706 '''
707 rospy.logdebug("MASTERINFO from %s (%s) received", minfo.mastername, minfo.masteruri)
708 self._con_tries[minfo.masteruri] = 0
709
710
711 if minfo.masteruri in self.masters:
712 for _, master in self.masters.items():
713 try:
714
715 new_info = master.master_info is None or master.master_info.timestamp < minfo.timestamp
716
717
718 master.master_info = minfo
719
720
721
722 if not master.master_info is None:
723 if self._history_selected_robot == minfo.mastername and self._history_selected_robot == master.mastername and self.currentMaster != master:
724 if not self.currentMaster is None and not self.currentMaster.is_local:
725 self.setCurrentMaster(master)
726 elif nm.is_local(nm.nameres().getHostname(master.master_info.masteruri)) or self.restricted_to_one_master:
727 if new_info:
728 has_discovery_service = self.hasDiscoveryService(minfo)
729 if (not self.own_master_monitor.isPaused() or not self.masterTableView.isEnabled()) and has_discovery_service:
730 self._subscribe()
731 if self.currentMaster is None and (not self._history_selected_robot or self._history_selected_robot == minfo.mastername):
732 self.setCurrentMaster(master)
733
734
735 if master.master_info.masteruri == minfo.masteruri:
736 self.master_model.setChecked(master.master_state.name, not minfo.getNodeEndsWith('master_sync') is None)
737 self.capabilitiesTable.updateState(minfo.masteruri, minfo)
738 except Exception, e:
739 rospy.logwarn("Error while process received master info from %s: %s", minfo.masteruri, str(e))
740
741 self.updateDuplicateNodes()
742
743 if not self.currentMaster is None and not self.currentMaster.master_info is None and not self.restricted_to_one_master:
744 self.syncButton.setEnabled(True)
745 self.syncButton.setChecked(not self.currentMaster.master_info.getNodeEndsWith('master_sync') is None)
746 else:
747 self.masterlist_service.retrieveMasterList(minfo.masteruri, False)
748
749
750
751
752 - def on_master_errors_retrieved(self, masteruri, error_list):
753 self.master_model.updateMasterErrors(nm.nameres().mastername(masteruri), error_list)
754
755 - def on_master_info_error(self, masteruri, error):
756 if not self._con_tries.has_key(masteruri):
757 self._con_tries[masteruri] = 0
758 self._con_tries[masteruri] += 1
759 if masteruri == self.getMasteruri():
760 rospy.logwarn("Error while connect to local master_discovery %s: %s", masteruri, error)
761
762 if self._con_tries[masteruri] > 2:
763 self._setLocalMonitoring(True)
764 master = self.getMaster(masteruri, False)
765 if master and not master.master_state is None:
766 self._update_handler.requestMasterInfo(master.master_state.uri, master.master_state.monitoruri, self.DELAYED_NEXT_REQ_ON_ERR)
767
768 - def on_conn_stats_updated(self, stats):
769 '''
770 Handle the retrieved connection statistics.
771 1. update the ROS Network view
772 @param stats: a list with connection statistics
773 @type stats: C{[L{master_discovery_fkie.msg.LinkState}]}
774 '''
775
776 for stat in stats.links:
777 self.master_model.updateMasterStat(stat.destination, stat.quality)
778
779
780
781
782
783
784
785 - def on_info_clicked(self):
786 text = ''.join(['<dl>'])
787 text = ''.join([text, '<dt><b>Maintainer</b>: ', 'Alexander Tiderko ', '<font color=gray>alexander.tiderko@gmail.com</font>', '</dt>'])
788 text = ''.join([text, '<dt><b>Author</b>: ', 'Alexander Tiderko, Timo Roehling', '</dt>'])
789 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>'])
790 text = ''.join([text, '</dl>'])
791 text = ''.join([text, '<dt><b>Version</b>: ', nm.__version__, ' (', nm.__date__,')', '</dt>'])
792 QtGui.QMessageBox.about(self, 'About Node Manager', text)
793
795 if not self.currentMaster is None:
796 rospy.loginfo("Request an update from %s", str(self.currentMaster.master_state.monitoruri))
797 if not self.currentMaster.master_info is None:
798 check_ts = self.currentMaster.master_info.check_ts
799 self.currentMaster.master_info.timestamp = self.currentMaster.master_info.timestamp - 1.0
800 self.currentMaster.master_info.check_ts = check_ts
801 if not self.currentMaster.master_state is None:
802 self._update_handler.requestMasterInfo(self.currentMaster.master_state.uri, self.currentMaster.master_state.monitoruri)
803 self.currentMaster.force_next_update()
804
805
807 '''
808 Open a dialog to run a ROS node without a configuration
809 '''
810 from run_dialog import RunDialog
811 if not self.currentMaster is None:
812 dia = RunDialog(nm.nameres().getHostname(self.currentMaster.masteruri), self.currentMaster.masteruri)
813 if dia.exec_():
814 params = dia.run_params()
815 if params:
816 params = params + (False, self.currentMaster.current_user,)
817 try:
818 self._progress_queue.add2queue(str(uuid.uuid4()),
819 'run `%s` on %s'%(params[2], params[0]),
820 nm.starter().runNodeWithoutConfig,
821 params)
822 self._progress_queue.start()
823 except (Exception, nm.StartException), e:
824 rospy.logwarn("Error while run `%s` on %s: %s", params[2], params[0], str(e))
825 WarningMessageBox(QtGui.QMessageBox.Warning, "Run error",
826 'Error while run node %s [%s]'%(params[2], params[1]),
827 str(e)).exec_()
828
829 - def on_rqt_plugin_start(self, name, plugin):
830 if not self.currentMaster is None:
831 try:
832 args = []
833 if plugin:
834 args = ['-s', plugin]
835 node_name = 'rqt_%s_%s'%(name.lower().replace(' ', '_'),
836 self.currentMaster.master_state.name.replace('-', '_'))
837 self.currentMaster._progress_queue.add2queue(str(uuid.uuid4()),
838 'start logger level',
839 nm.starter().runNodeWithoutConfig,
840 ('localhost', 'rqt_gui', 'rqt_gui',
841 node_name, args,
842 '%s'%self.currentMaster.master_state.uri,
843 False))
844 except (Exception, nm.StartException), e:
845 import traceback
846 print traceback.format_exc(1)
847 rospy.logwarn("Error while start %s: %s"%(name, e))
848 WarningMessageBox(QtGui.QMessageBox.Warning, "Start error",
849 'Error while start %s'%name,
850 '%s'%e).exec_()
851 self.currentMaster._progress_queue.start()
852
853 - def on_sync_dialog_released(self, released=False, masteruri=None, external_call=False):
854 self.syncButton.setEnabled(False)
855 master = self.currentMaster
856 sync_node = None
857 if not masteruri is None:
858 master = self.getMaster(masteruri, False)
859 if master is not None and master.master_info is not None:
860 sync_node = master.master_info.getNodeEndsWith('master_sync')
861 if master is not None and (sync_node is None or external_call):
862 self._sync_dialog.resize(350,190)
863 if self._sync_dialog.exec_():
864 try:
865 host = nm.nameres().getHostname(master.masteruri)
866 if not self._sync_dialog.interface_filename is None:
867
868 self._progress_queue_sync.add2queue(str(uuid.uuid4()),
869 'Transfer sync interface %s'%host,
870 nm.starter().transfer_files,
871 ("%s"%host, self._sync_dialog.interface_filename, False, master.current_user))
872 self._progress_queue_sync.add2queue(str(uuid.uuid4()),
873 'Start sync on %s'%host,
874 nm.starter().runNodeWithoutConfig,
875 ("%s"%host, 'master_sync_fkie', 'master_sync', 'master_sync', self._sync_dialog.sync_args, "%s"%master.masteruri, False, master.current_user))
876 self._progress_queue_sync.start()
877 except:
878 import traceback
879 WarningMessageBox(QtGui.QMessageBox.Warning, "Start sync error",
880 "Error while start sync node",
881 str(traceback.format_exc(1))).exec_()
882 else:
883 self.syncButton.setChecked(False)
884 elif sync_node is not None:
885 master.stop_nodes([sync_node])
886 self.syncButton.setEnabled(True)
887
888 - def on_sync_released(self, external_call=False):
889 '''
890 Enable or disable the synchronization of the master cores
891 '''
892 key_mod = QtGui.QApplication.keyboardModifiers()
893 if (key_mod & QtCore.Qt.ShiftModifier or key_mod & QtCore.Qt.ControlModifier):
894 if external_call:
895 self.on_sync_dialog_released(external_call=external_call)
896
897
898 if not self.currentMaster.master_info is None:
899 node = self.currentMaster.master_info.getNodeEndsWith('master_sync')
900 self.syncButton.setChecked(not node is None)
901 else:
902 self.syncButton.setEnabled(False)
903 if not self.currentMaster is None:
904 if self.syncButton.isChecked():
905
906 if not self.currentMaster.master_info is None:
907 node = self.currentMaster.getNode('/master_sync')
908 if node and node[0].has_configs():
909 def_cfg_info = '\nNote: default_cfg parameter will be changed!' if node[0].has_default_cfgs(node[0].cfgs) else ''
910 ret = QtGui.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, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
911 if ret == QtGui.QMessageBox.Yes:
912 self.currentMaster.start_nodes([node[0]])
913 return
914
915
916 sync_args = []
917 sync_args.append(''.join(['_interface_url:=', "'.'"]))
918 sync_args.append(''.join(['_sync_topics_on_demand:=', 'False']))
919 sync_args.append(''.join(['_ignore_hosts:=', '[]']))
920 sync_args.append(''.join(['_sync_hosts:=', '[]']))
921 sync_args.append(''.join(['_ignore_nodes:=', '[]']))
922 sync_args.append(''.join(['_sync_nodes:=', '[]']))
923 sync_args.append(''.join(['_ignore_topics:=', '[]']))
924 sync_args.append(''.join(['_sync_topics:=', '[]']))
925 sync_args.append(''.join(['_ignore_services:=', '[]']))
926 sync_args.append(''.join(['_sync_services:=', '[]']))
927 sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
928
929 try:
930 host = nm.nameres().getHostname(self.currentMaster.masteruri)
931 self._progress_queue_sync.add2queue(str(uuid.uuid4()),
932 'start sync on '+str(host),
933 nm.starter().runNodeWithoutConfig,
934 (str(host), 'master_sync_fkie', 'master_sync', 'master_sync', sync_args, str(self.currentMaster.masteruri), False, self.currentMaster.current_user))
935 self._progress_queue_sync.start()
936 except:
937 pass
938 elif not self.currentMaster.master_info is None:
939 node = self.currentMaster.master_info.getNodeEndsWith('master_sync')
940 if not node is None:
941 self.currentMaster.stop_nodes([node])
942 self.syncButton.setEnabled(True)
943
945
946 if len(self.masters) < 2 and self.currentMaster is None:
947 self._subscribe()
948 return
949
950 current_time = time.time()
951 if self.isActiveWindow() or current_time - self._last_time_view_update > 5:
952 self._last_time_view_update = current_time
953 if not self.currentMaster is None and not self.currentMaster.master_state is None:
954 master = self.getMaster(self.currentMaster.master_state.uri)
955 name = master.master_state.name
956 masteruri = master.master_state.uri
957 if self.restricted_to_one_master:
958 name = ''.join([name, ' <span style=" color:red;">(restricted)</span>'])
959 if not self.masternameLabel.toolTip():
960 self.masternameLabel.setToolTip('The multicore options are disabled, because the roscore is running on remote host!')
961 if not master.master_info is None:
962 self.showMasterName(masteruri, name, self.timestampStr(master.master_info.check_ts), master.master_state.online)
963 pass
964 elif not master.master_state is None:
965 text = 'Try to get info!!!'
966 if not nm.settings().autoupdate:
967 text = 'Press F5 or click on reload to get info'
968 self.showMasterName(masteruri, name, text, master.master_state.online)
969 else:
970 self.showMasterName('', 'No robot selected', None, False)
971 if (current_time - self._refresh_time > 30.0):
972 masteruri = self.getMasteruri()
973 if not masteruri is None:
974 master = self.getMaster(masteruri)
975 if not master is None and not master.master_state is None and nm.settings().autoupdate:
976 self._update_handler.requestMasterInfo(master.master_state.uri, master.master_state.monitoruri)
977 self._refresh_time = current_time
978
979
980 - def showMasterName(self, masteruri, name, timestamp, online=True):
981 '''
982 Update the view of the info frame.
983 '''
984 con_err = ''
985 try:
986 tries = self._con_tries[masteruri]
987 if tries > 1:
988 con_err = '<span style=" color:red;">connection problems (%s tries)! </span>'%str(tries)
989 except:
990 pass
991 if self.__current_master_label_name != name:
992 self.__current_master_label_name = name
993 self.masternameLabel.setText('<span style=" font-size:14pt; font-weight:600;">%s</span>'%name)
994 ts = 'updated: %s'%str(timestamp) if not timestamp is None else ''
995 if not nm.settings().autoupdate:
996 ts = '%s<span style=" color:orange;"> AU off</span>'%ts
997 self.masterInfoLabel.setText('<span style=" font-size:8pt;">%s%s</span>'%(con_err, ts))
998
999
1000 if self.masternameLabel.isEnabled():
1001 if self.__icons.has_key(name):
1002 if self.__icons[name][0] != self.__current_icon:
1003 icon = self.__icons[name][0]
1004 self.__current_icon = icon
1005 self.imageLabel.setPixmap(icon.pixmap(self.nameFrame.size()))
1006 self.imageLabel.setToolTip(''.join(['<html><head></head><body><img src="', self.__icons[name][1], '" alt="', name,'"></body></html>']))
1007 elif self.__icons['default_pc'][0] != self.__current_icon:
1008 icon = self.__icons['default_pc'][0]
1009 self.__current_icon = icon
1010 self.imageLabel.setPixmap(icon.pixmap(self.nameFrame.size()))
1011 self.imageLabel.setToolTip('')
1012
1013 master = self.getMaster(masteruri, False)
1014 sim_time_enabled = self.masternameLabel.isEnabled() and not master is None and master.use_sim_time
1015 self.simTimeLabel.setVisible(bool(sim_time_enabled))
1016 launch_server_enabled = self.masternameLabel.isEnabled() and (not master is None) and master.has_launch_server()
1017 self.launchServerLabel.setVisible(launch_server_enabled)
1018 self.masternameLabel.setEnabled(online)
1019 self.masterInfoFrame.setEnabled((not timestamp is None))
1020
1021 if self.logButton.isEnabled():
1022 if self.logButton.text():
1023 self.logButton.setIcon(self.__icons['log_warning'][0])
1024 self.logButton.setText('')
1025 else:
1026 self.logButton.setText('%d'%self.log_dock.count())
1027 self.logButton.setIcon(self.__icons['empty'][0])
1028
1029
1030 - def timestampStr(self, timestamp):
1031 dt = datetime.fromtimestamp(timestamp)
1032 diff = time.time()-timestamp
1033 diff_dt = datetime.fromtimestamp(diff)
1034 before = '0 sec'
1035 if (diff < 60):
1036 before = diff_dt.strftime('%S sec')
1037 elif (diff < 3600):
1038 before = diff_dt.strftime('%M:%S min')
1039 elif (diff < 86400):
1040 before = diff_dt.strftime('%H:%M:%S std')
1041 else:
1042 before = diff_dt.strftime('%d Day(s) %H:%M:%S')
1043 return '%s (%s)'%(dt.strftime('%H:%M:%S'), before)
1044
1046
1047 running_nodes = dict()
1048 for _, m in self.masters.items():
1049 if not m.master_state is None and m.master_state.online:
1050 running_nodes.update(m.getRunningNodesIfLocal())
1051 for _, m in self.masters.items():
1052 if not m.master_state is None:
1053 m.markNodesAsDuplicateOf(running_nodes)
1054
1055
1056
1057
1058
1059
1060
1061 - def on_master_table_pressed(self, selected):
1063
1064 - def on_master_table_clicked(self, selected):
1065 '''
1066 On click on the sync item, the master_sync node will be started or stopped,
1067 depending on run state.
1068 '''
1069 item = self.master_model.itemFromIndex(selected)
1070 if isinstance(item, MasterSyncItem):
1071 if MasterSyncItem.START_SYNC != item.synchronized:
1072 self.syncButton.setChecked(item.synchronized != MasterSyncItem.SYNC)
1073 item.synchronized = MasterSyncItem.START_SYNC
1074 self.on_sync_released(True)
1075
1076 - def on_master_table_activated(self, selected):
1077 item = self.master_model.itemFromIndex(selected)
1078 QtGui.QMessageBox.information(self, item.name, item.toolTip())
1079
1081 '''
1082 If a master was selected, set the corresponding Widget of the stacked layout
1083 to the current widget and shows the state of the selected master.
1084 '''
1085
1086
1087
1088 item = self.master_model.itemFromIndex(selected)
1089 if not item is None:
1090 self._history_selected_robot = item.master.name
1091 self.setCurrentMaster(item.master.uri)
1092 if not self.currentMaster.master_info is None and not self.restricted_to_one_master:
1093 node = self.currentMaster.master_info.getNodeEndsWith('master_sync')
1094 self.syncButton.setEnabled(True)
1095 self.syncButton.setChecked(not node is None)
1096 else:
1097 self.syncButton.setEnabled(False)
1098 return
1099 self.launch_dock.raise_()
1100
1101 - def setCurrentMaster(self, master):
1102 '''
1103 Changes the view of the master.
1104 :param master: the MasterViewProxy object or masteruri
1105 :type master: MasterViewProxy or str
1106 '''
1107 show_user_field = False
1108 if isinstance(master, MasterViewProxy):
1109 self.currentMaster = master
1110 self.stackedLayout.setCurrentWidget(master)
1111 show_user_field = not master.is_local
1112 self._add_user_to_combo(self.currentMaster.current_user)
1113 self.userComboBox.setEditText(self.currentMaster.current_user)
1114 elif master is None:
1115 self.currentMaster = None
1116 self.stackedLayout.setCurrentIndex(0)
1117 else:
1118 self.currentMaster = self.getMaster(master)
1119 if not self.currentMaster is None:
1120 self.stackedLayout.setCurrentWidget(self.currentMaster)
1121 show_user_field = not self.currentMaster.is_local
1122 self._add_user_to_combo(self.currentMaster.current_user)
1123 self.userComboBox.setEditText(self.currentMaster.current_user)
1124 else:
1125 self.stackedLayout.setCurrentIndex(0)
1126 self.user_frame.setVisible(show_user_field)
1127 self.on_master_timecheck()
1128
1129 - def _add_user_to_combo(self, user):
1130 for i in range(self.userComboBox.count()):
1131 if user.lower() == self.userComboBox.itemText(i).lower():
1132 return
1133 self.userComboBox.addItem(user)
1134
1135 - def on_user_changed(self, user):
1136 if not self.currentMaster is None:
1137 self.currentMaster.current_user = user
1138
1139 - def on_masterTableView_selection_changed(self, selected, deselected):
1140 '''
1141 On selection of a master list.
1142 '''
1143 if selected.isValid():
1144 self.on_master_selection_changed(selected)
1145
1147 '''
1148 Retrieves from the master_discovery node the list of all discovered ROS
1149 master and get their current state.
1150 '''
1151
1152 for _, m in self.masters.items():
1153 if not m.master_info is None:
1154 check_ts = m.master_info.check_ts
1155 m.master_info.timestamp = m.master_info.timestamp - 1.0
1156 m.master_info.check_ts = check_ts
1157 self.masterlist_service.refresh(self.getMasteruri(), False)
1158
1160 try:
1161 self._discover_dialog.raise_()
1162 except:
1163 self._discover_dialog = NetworkDiscoveryDialog('226.0.0.0', 11511, 100, self)
1164 self._discover_dialog.network_join_request.connect(self._join_network)
1165 self._discover_dialog.show()
1166
1168 '''
1169 Tries to start the master_discovery node on the machine requested by a dialog.
1170 '''
1171
1172 user_list = [self.userComboBox.itemText(i) for i in reversed(range(self.userComboBox.count()))]
1173 user_list.insert(0, 'last used')
1174 params_optional = {'Discovery type': ('string', ['master_discovery', 'zeroconf']),
1175 'ROS Master Name' : ('string', 'autodetect'),
1176 'ROS Master URI' : ('string', 'ROS_MASTER_URI'),
1177 'Robot hosts' : ('string', ''),
1178 'Username' : ('string', user_list),
1179 'MCast Group' : ('string', '226.0.0.0'),
1180 'Heartbeat [Hz]' : ('float', 0.5)
1181 }
1182 params = {'Host' : ('string', 'localhost'),
1183 'Network(0..99)' : ('int', '0'),
1184 'Optional Parameter' : ('list', params_optional) }
1185 dia = ParameterDialog(params, sidebar_var='Host')
1186 dia.setFilterVisible(False)
1187 dia.setWindowTitle('Start discovery')
1188 dia.resize(450,300)
1189 dia.setFocusField('Host')
1190 if dia.exec_():
1191 try:
1192 params = dia.getKeywords()
1193 hostnames = params['Host'] if isinstance(params['Host'], list) else [params['Host']]
1194 port = params['Network(0..99)']
1195 discovery_type = params['Optional Parameter']['Discovery type']
1196 mastername = 'autodetect'
1197 masteruri = 'ROS_MASTER_URI'
1198 if len(hostnames) < 2:
1199 mastername = params['Optional Parameter']['ROS Master Name']
1200 masteruri = params['Optional Parameter']['ROS Master URI']
1201 robot_hosts = params['Optional Parameter']['Robot hosts']
1202 username = params['Optional Parameter']['Username']
1203 mcast_group = params['Optional Parameter']['MCast Group']
1204 heartbeat_hz = params['Optional Parameter']['Heartbeat [Hz]']
1205 if robot_hosts:
1206 robot_hosts = robot_hosts.replace(' ', '')
1207 robot_hosts = robot_hosts.replace('[', '')
1208 robot_hosts = robot_hosts.replace(']', '')
1209 for hostname in hostnames:
1210 try:
1211 args = []
1212 if not port is None and port and int(port) < 100 and int(port) >= 0:
1213 args.append('_mcast_port:=%s'%(11511 + int(port)))
1214 else:
1215 args.append('_mcast_port:=%s'%(11511))
1216 if not mastername == 'autodetect':
1217 args.append('_name:=%s'%(mastername))
1218 args.append('_mcast_group:=%s'%mcast_group)
1219 args.append('_robot_hosts:=[%s]'%robot_hosts)
1220 args.append('_heartbeat_hz:=%s'%heartbeat_hz)
1221
1222 usr = username
1223 if username == 'last used':
1224 usr = nm.settings().host_user(hostname)
1225 self._progress_queue.add2queue(str(uuid.uuid4()),
1226 'start discovering on %s'%hostname,
1227 nm.starter().runNodeWithoutConfig,
1228 (str(hostname), 'master_discovery_fkie', str(discovery_type), str(discovery_type), args, (None if masteruri == 'ROS_MASTER_URI' else str(masteruri)), False, usr))
1229
1230 except (Exception, nm.StartException), e:
1231 import traceback
1232 print traceback.format_exc(1)
1233 rospy.logwarn("Error while start master_discovery for %s: %s"%(str(hostname), e))
1234 WarningMessageBox(QtGui.QMessageBox.Warning, "Start error",
1235 'Error while start master_discovery',
1236 str(e)).exec_()
1237 self._progress_queue.start()
1238 except Exception, e:
1239 WarningMessageBox(QtGui.QMessageBox.Warning, "Start error",
1240 'Error while parse parameter',
1241 str(e)).exec_()
1242
1243 - def _join_network(self, network):
1244 try:
1245 hostname = 'localhost'
1246 args = []
1247 if network < 100 and network >= 0:
1248 args.append(''.join(['_mcast_port:=', str(11511 + int(network))]))
1249 self._progress_queue.add2queue(str(uuid.uuid4()),
1250 'start discovering on '+str(hostname),
1251 nm.starter().runNodeWithoutConfig,
1252 (str(hostname), 'master_discovery_fkie', 'master_discovery', 'master_discovery', args, None, False))
1253 self._progress_queue.start()
1254 except (Exception, nm.StartException), e:
1255 rospy.logwarn("Error while start master_discovery for %s: %s", str(hostname), str(e))
1256 WarningMessageBox(QtGui.QMessageBox.Warning, "Start error",
1257 'Error while start master_discovery',
1258 str(e)).exec_()
1259
1260
1261
1262
1263
1264 - def on_load_launch_file(self, path):
1265 '''
1266 Load the launch file. A ROS master must be selected first.
1267 :param path: the path of the launch file.
1268 :type path: str
1269 '''
1270 rospy.loginfo("LOAD launch: %s"%path)
1271 master_proxy = self.stackedLayout.currentWidget()
1272 if isinstance(master_proxy, MasterViewProxy):
1273
1274
1275 try:
1276 master_proxy.launchfiles = path
1277 except Exception, e:
1278 import traceback
1279 print traceback.format_exc(1)
1280 WarningMessageBox(QtGui.QMessageBox.Warning, "Loading launch file", path, '%s'%e).exec_()
1281
1282 else:
1283 QtGui.QMessageBox.information(self, "Load of launch file",
1284 "Select a master first!", )
1285
1286 - def on_load_launch_as_default(self, path, host=None):
1287 '''
1288 Load the launch file as default configuration. A ROS master must be selected first.
1289 :param path: the path of the launch file.
1290 :type path: str
1291 :param host: The host name, where the configuration start.
1292 :type host: str (Default: None)
1293 '''
1294 rospy.loginfo("LOAD launch as default: %s"%path)
1295 master_proxy = self.stackedLayout.currentWidget()
1296 if isinstance(master_proxy, MasterViewProxy):
1297 args = list()
1298 args.append('_package:=%s'%(package_name(os.path.dirname(path))[0]))
1299 args.append('_launch_file:="%s"'%os.path.basename(path))
1300 try:
1301
1302 launchConfig = LaunchConfig(path)
1303 req_args = launchConfig.getArgs()
1304 if req_args:
1305 params = dict()
1306 arg_dict = launchConfig.argvToDict(req_args)
1307 for arg in arg_dict.keys():
1308 params[arg] = ('string', [arg_dict[arg]])
1309 inputDia = ParameterDialog(params)
1310 inputDia.setFilterVisible(False)
1311 inputDia.setWindowTitle('Enter the argv for %s'%path)
1312 if inputDia.exec_():
1313 params = inputDia.getKeywords()
1314 args.extend(launchConfig.resolveArgs([''.join([p, ":='", v, "'"]) for p,v in params.items() if v]))
1315 else:
1316 return
1317 except:
1318 import traceback
1319 rospy.logwarn('Error while load %s as default: %s'%(path, traceback.format_exc(1)))
1320 hostname = host if host else nm.nameres().address(master_proxy.masteruri)
1321 name_file_prefix = os.path.basename(path).replace('.launch','').replace(' ', '_')
1322 node_name = roslib.names.SEP.join([hostname,
1323 name_file_prefix,
1324 'default_cfg'])
1325 self.launch_dock.progress_queue.add2queue('%s'%uuid.uuid4(),
1326 'start default config %s'%hostname,
1327 nm.starter().runNodeWithoutConfig,
1328 ('%s'%hostname, 'default_cfg_fkie', 'default_cfg',
1329 node_name, args, master_proxy.masteruri, False,
1330 master_proxy.current_user))
1331 self.launch_dock.progress_queue.start()
1332 else:
1333 QtGui.QMessageBox.information(self, "Load of launch file",
1334 "Select a master first!", )
1335
1336
1337 - def on_launch_edit(self, files, search_text='', trynr=1):
1338 '''
1339 Opens the given files in an editor. If the first file is already open, select
1340 the editor. If search text is given, search for the text in files an goto the
1341 line.
1342 :param file: A list with paths
1343 :type file: list of strings
1344 :param search_text: A string to search in file
1345 :type search_text: str
1346 '''
1347 if files:
1348 path = files[0]
1349 if self.editor_dialogs.has_key(path):
1350 last_path = files[-1]
1351 try:
1352 self.editor_dialogs[path].on_load_request(last_path, search_text)
1353 self.editor_dialogs[path].raise_()
1354 self.editor_dialogs[path].activateWindow()
1355 except:
1356 if trynr > 1:
1357 raise
1358 del self.editor_dialogs[path]
1359 self.on_launch_edit(files, search_text, 2)
1360 else:
1361 editor = XmlEditor(files, search_text, self)
1362 self.editor_dialogs[path] = editor
1363 editor.finished_signal.connect(self._editor_dialog_closed)
1364 editor.show()
1365
1366 - def _editor_dialog_closed(self, files):
1367 '''
1368 If a editor dialog is closed, remove it from the list...
1369 '''
1370 if self.editor_dialogs.has_key(files[0]):
1371 del self.editor_dialogs[files[0]]
1372
1373 - def on_launch_transfer(self, files):
1374 '''
1375 Copies the selected file to a remote host
1376 :param file: A list with paths
1377 :type file: list of strings
1378 '''
1379 if files:
1380 host = 'localhost'
1381 username = nm.settings().default_user
1382 if not self.currentMaster is None:
1383 host = nm.nameres().getHostname(self.currentMaster.masteruri)
1384 username = self.currentMaster.current_user
1385 params = {'Host' : ('string', host),
1386 'recursive' : ('bool', 'False'),
1387 'Username' : ('string', '%s'%username) }
1388 dia = ParameterDialog(params)
1389 dia.setFilterVisible(False)
1390 dia.setWindowTitle('Transfer file')
1391 dia.resize(350,120)
1392 dia.setFocusField('Host')
1393 if dia.exec_():
1394 try:
1395 params = dia.getKeywords()
1396 host = params['Host']
1397 recursive = params['recursive']
1398 username = params['Username']
1399 for path in files:
1400 rospy.loginfo("TRANSFER to %s@%s: %s"%(username, host, path))
1401 self.launch_dock.progress_queue.add2queue('%s'%uuid.uuid4(),
1402 'transfer files to %s'%host,
1403 nm.starter().transfer_files,
1404 ('%s'%host, path, False, username))
1405 if recursive:
1406 for f in LaunchConfig.getIncludedFiles(path):
1407 self.launch_dock.progress_queue.add2queue(str(uuid.uuid4()),
1408 'transfer files to %s'%host,
1409 nm.starter().transfer_files,
1410 ('%s'%host, f, False, username))
1411 self.launch_dock.progress_queue.start()
1412 except Exception, e:
1413 WarningMessageBox(QtGui.QMessageBox.Warning, "Transfer error",
1414 'Error while transfer files', '%s'%e).exec_()
1415
1416 - def _reload_globals_at_next_start(self, launch_file):
1417 if not self.currentMaster is None:
1418 self.currentMaster.reload_global_parameter_at_next_start(launch_file)
1419
1420
1421
1422
1423
1424 - def on_configfile_changed(self, changed, affected):
1425 '''
1426 Signal hander to handle the changes of a loaded configuration file
1427 @param changed: the changed file
1428 @type changed: C{str}
1429 @param affected: the list of tuples with masteruri and launchfile, which are affected by file change
1430 @type affected: list
1431 '''
1432
1433
1434 if self.isActiveWindow():
1435 self._changed_files[changed] = affected
1436 self._check_for_changed_files()
1437 else:
1438 self._changed_files[changed] = affected
1439
1441 '''
1442 Check the dictinary with changed files and notify the masters about changes.
1443 '''
1444 new_affected = list()
1445 for _, affected in self._changed_files.items():
1446 for (muri, lfile) in affected:
1447 if not (muri, lfile) in self.__in_question:
1448 self.__in_question.add((muri, lfile))
1449 new_affected.append((muri, lfile))
1450
1451 if new_affected:
1452 choices = dict()
1453 for (muri, lfile) in new_affected:
1454 master = self.getMaster(muri)
1455 if not master is None:
1456 master.launchfile = lfile
1457 choices[''.join([os.path.basename(lfile), ' [', master.mastername, ']'])] = (master, lfile)
1458 cfgs, _ = SelectDialog.getValue('Reload configurations?',
1459 '<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(),
1460 False, True,
1461 ':/icons/crystal_clear_launch_file.png',
1462 self)
1463 for (muri, lfile) in new_affected:
1464 self.__in_question.remove((muri, lfile))
1465 for c in cfgs:
1466 choices[c][0].launchfiles = choices[c][1]
1467 self._changed_files.clear()
1468
1469 - def on_configparamfile_changed(self, changed, affected):
1470 '''
1471 Signal handler to handle the changes of a configuration file referenced by a parameter value
1472 @param changed: the changed file
1473 @type changed: C{str}
1474 @param affected: the list of tuples with masteruri and launchfile, which are affected by file change
1475 @type affected: list
1476 '''
1477
1478
1479 if self.isActiveWindow():
1480 self._changed_files_param[changed] = affected
1481 self._check_for_changed_files_param()
1482 else:
1483 self._changed_files_param[changed] = affected
1484
1486 '''
1487 Check the dictinary with changed files and notify about the transfer of changed file.
1488 '''
1489 new_affected = list()
1490 for changed, affected in self._changed_files_param.items():
1491 for (muri, lfile) in affected:
1492 if not (muri, changed) in self.__in_question:
1493 self.__in_question.add((muri, changed))
1494 new_affected.append((muri, changed))
1495
1496 if new_affected:
1497 choices = dict()
1498 for (muri, lfile) in new_affected:
1499 master = self.getMaster(muri)
1500 if not master is None:
1501 master.launchfile = lfile
1502 choices[''.join([os.path.basename(lfile), ' [', master.mastername, ']'])] = (master, lfile)
1503 cfgs, _ = SelectDialog.getValue('Transfer configurations?',
1504 'Configuration files referenced by parameter are changed.<br>Select affected configurations for copy to remote host: (don\'t forget to restart the nodes!)',
1505 choices.keys(), False, True,
1506 ':/icons/crystal_clear_launch_file_transfer.png',
1507 self)
1508 for (muri, lfile) in new_affected:
1509 self.__in_question.remove((muri, lfile))
1510 for c in cfgs:
1511 host = '%s'%nm.nameres().getHostname(choices[c][0].masteruri)
1512 username = choices[c][0].current_user
1513 self.launch_dock.progress_queue.add2queue(str(uuid.uuid4()),
1514 'transfer files to %s'%host,
1515 nm.starter().transfer_files,
1516 (host, choices[c][1], False, username))
1517 self.launch_dock.progress_queue.start()
1518 self._changed_files_param.clear()
1519
1520 - def changeEvent(self, event):
1521 '''
1522 Check for changed files, if the main gui is activated.
1523 '''
1524 QtGui.QMainWindow.changeEvent(self, event)
1525 self._check_for_changed_files()
1526 self._check_for_changed_files_param()
1527
1528
1529
1530
1531
1532 - def on_start_nodes(self, masteruri, cfg, nodes):
1536
1537 - def on_stop_nodes(self, masteruri, nodes):
1541
1542 - def on_description_update(self, title, text):
1543 if (self.sender() == self.currentMaster or not isinstance(self.sender(), MasterViewProxy)) and (not self.descriptionTextEdit.hasFocus() or self._accept_next_update):
1544 self._accept_next_update = False
1545 self.descriptionDock.setWindowTitle(title)
1546 self.descriptionTextEdit.setText(text)
1547 if text and not (self.launch_dock.hasFocus() or self.launch_dock.xmlFileView.hasFocus()):
1548 self.descriptionDock.raise_()
1549 else:
1550 self.launch_dock.raise_()
1551
1552 - def on_description_update_cap(self, title, text):
1553 self.descriptionDock.setWindowTitle(title)
1554 self.descriptionTextEdit.setText(text)
1555
1557 self._accept_next_update = True
1558 if url.toString().startswith('open_sync_dialog://'):
1559 self.on_sync_dialog_released(False, str(url.encodedPath()).replace('open_sync_dialog', 'http'), True)
1560 elif url.toString().startswith('show_all_screens://'):
1561 master = self.getMaster(str(url.encodedPath()).replace('show_all_screens', 'http'), False)
1562 if not master is None:
1563 master.on_show_all_screens()
1564 elif url.toString().startswith('remove_all_launch_server://'):
1565 master = self.getMaster(str(url.encodedPath()).replace('remove_all_launch_server', 'http'), False)
1566 if not master is None:
1567 master.on_remove_all_launch_server()
1568 elif url.toString().startswith('node://'):
1569 if not self.currentMaster is None:
1570 self.currentMaster.on_node_selection_changed(None, None, True, url.encodedPath())
1571 elif url.toString().startswith('topic://'):
1572 if not self.currentMaster is None:
1573 self.currentMaster.on_topic_selection_changed(None, None, True, url.encodedPath())
1574 elif url.toString().startswith('topicecho://'):
1575 if not self.currentMaster is None:
1576 self.currentMaster.show_topic_output(url.encodedPath(), False)
1577 elif url.toString().startswith('topichz://'):
1578 if not self.currentMaster is None:
1579 self.currentMaster.show_topic_output(url.encodedPath(), True)
1580 elif url.toString().startswith('topichzssh://'):
1581 if not self.currentMaster is None:
1582 self.currentMaster.show_topic_output(url.encodedPath(), True, use_ssh=True)
1583 elif url.toString().startswith('topicpub://'):
1584 if not self.currentMaster is None:
1585 self.currentMaster.start_publisher(url.encodedPath())
1586 elif url.toString().startswith('topicrepub://'):
1587 if not self.currentMaster is None:
1588 self.currentMaster.start_publisher(url.encodedPath(), True)
1589 elif url.toString().startswith('topicstop://'):
1590 if not self.currentMaster is None:
1591 self.currentMaster.on_topic_pub_stop_clicked(url.encodedPath())
1592 elif url.toString().startswith('service://'):
1593 if not self.currentMaster is None:
1594 self.currentMaster.on_service_selection_changed(None, None, True, url.encodedPath())
1595 elif url.toString().startswith('servicecall://'):
1596 if not self.currentMaster is None:
1597 self.currentMaster.service_call(url.encodedPath())
1598 elif url.toString().startswith('unregister_node://'):
1599 if not self.currentMaster is None:
1600 self.currentMaster.on_unregister_nodes()
1601 elif url.toString().startswith('start_node://'):
1602 if not self.currentMaster is None:
1603 self.currentMaster.on_start_clicked()
1604 elif url.toString().startswith('restart_node://'):
1605 if not self.currentMaster is None:
1606 self.currentMaster.on_force_start_nodes()
1607 elif url.toString().startswith('start_node_at_host://'):
1608 if not self.currentMaster is None:
1609 self.currentMaster.on_start_nodes_at_host()
1610 elif url.toString().startswith('kill_node://'):
1611 if not self.currentMaster is None:
1612 self.currentMaster.on_kill_nodes()
1613 elif url.toString().startswith('kill_screen://'):
1614 if not self.currentMaster is None:
1615 self.currentMaster.on_kill_screens()
1616 elif url.toString().startswith('copy_log_path://'):
1617 if not self.currentMaster is None:
1618 self.currentMaster.on_log_path_copy()
1619 elif url.toString().startswith('launch://'):
1620 self.on_launch_edit([str(url.encodedPath())], '')
1621 elif url.toString().startswith('reload_globals://'):
1622 self._reload_globals_at_next_start(str(url.encodedPath()).replace('reload_globals://', ''))
1623 else:
1624 QtGui.QDesktopServices.openUrl(url)
1625
1626 - def keyReleaseEvent(self, event):
1627 '''
1628 Defines some of shortcuts for navigation/management in launch
1629 list view or topics view.
1630 '''
1631 key_mod = QtGui.QApplication.keyboardModifiers()
1632 if not self.currentMaster is None and self.currentMaster.masterTab.nodeTreeView.hasFocus():
1633 if event.key() == QtCore.Qt.Key_F4 and not key_mod:
1634 if self.currentMaster.masterTab.editConfigButton.isEnabled():
1635 self.currentMaster.on_edit_config_clicked()
1636 elif self.currentMaster.masterTab.editRosParamButton.isEnabled():
1637 self.currentMaster.on_edit_rosparam_clicked()
1638 elif event.key() == QtCore.Qt.Key_F3 and not key_mod and self.currentMaster.masterTab.ioButton.isEnabled():
1639 self.currentMaster.on_io_clicked()
1640 QtGui.QMainWindow.keyReleaseEvent(self, event)
1641
1643 '''
1644 Set the robot image
1645 '''
1646 if self.currentMaster:
1647 try:
1648 if not os.path.isdir(nm.settings().ROBOTS_DIR):
1649 os.makedirs(nm.settings().ROBOTS_DIR)
1650 (fileName, _) = QtGui.QFileDialog.getOpenFileName(self,
1651 "Set robot image",
1652 nm.settings().ROBOTS_DIR,
1653 "Image files (*.bmp *.gif *.jpg *.jpeg *.png *.pbm *.xbm);;All files (*)")
1654 if fileName and self.__current_master_label_name:
1655 p = QtGui.QPixmap(fileName)
1656 p.save(nm.settings().robot_image_file(self.__current_master_label_name))
1657 if self.__icons.has_key(self.__current_master_label_name):
1658 del self.__icons[self.__current_master_label_name]
1659 self._assigne_icon(self.__current_master_label_name)
1660 except Exception as e:
1661 WarningMessageBox(QtGui.QMessageBox.Warning, "Error",
1662 ''.join(['Set robot image for ', str(self.__current_master_label_name), ' failed!']),
1663 str(e)).exec_()
1664 rospy.logwarn("Error while set robot image for %s: %s", str(self.__current_master_label_name), str(e))
1665
1666 - def _on_robot_icon_changed(self, masteruri, path):
1667 '''
1668 One of the robot icons was chagned. Update the icon.
1669 '''
1670 master = self.getMaster(masteruri, False)
1671 if master:
1672 self._assigne_icon(master.mastername, resolve_url(path))
1673