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