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 from python_qt_binding.QtGui import QColor
34 import os
35 import roslib
36 import rospy
37
38 from node_manager_fkie.common import get_ros_home, masteruri_from_ros, utf8
42 LOGLEVEL = 'DEFAULT'
43 LOGLEVEL_ROSCPP = 'INFO'
44 LOGLEVEL_SUPERDEBUG = 'WARN'
45 CONSOLE_FORMAT = 'DEFAULT'
46
52
54 return ['loglevel',
55 'loglevel_roscpp',
56 'loglevel_superdebug',
57 'console_format'
58 ]
59
61 return getattr(self, attribute) == getattr(self, attribute.upper())
62
64 result = []
65 if attribute == 'console_format':
66 result = [self.CONSOLE_FORMAT,
67 '[${severity}] [${time}]: ${message}',
68 '[${severity}] [${time}] [${logger}]: ${message}']
69 elif attribute == 'loglevel':
70 result = ['DEFAULT', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
71 elif attribute == 'loglevel_roscpp':
72 result = ['INFO', 'DEBUG', 'WARN', 'ERROR', 'FATAL']
73 elif attribute == 'loglevel_superdebug':
74 result = ['WARN', 'DEBUG', 'INFO', 'ERROR', 'FATAL']
75 if not self.is_default(attribute):
76 result.insert(0, getattr(self, attribute))
77 return result
78
81
82 USER_DEFAULT = 'robot'
83
84
85 PKG_NAME = 'node_manager_fkie'
86 try:
87 PACKAGE_DIR = roslib.packages.get_pkg_dir(PKG_NAME)
88 except Exception:
89 PACKAGE_DIR = "%s/../.." % os.path.realpath(os.path.dirname(__file__))
90 if "dist-packages" in __file__:
91 PACKAGE_DIR = "%s/../../share/node_manager_fkie" % PACKAGE_DIR
92 print("PACKAGE_DIR: %s" % PACKAGE_DIR)
93 ROBOTS_DIR = os.path.join(PACKAGE_DIR, 'images')
94 CFG_PATH = os.path.join('.node_manager', os.sep)
95 '''@ivar: configuration path to store the history.'''
96 HELP_FILE = os.path.join(PACKAGE_DIR, 'README.rst')
97 CURRENT_DIALOG_PATH = os.path.expanduser('~')
98 LOG_PATH = os.environ.get('ROS_LOG_DIR') if os.environ.get('ROS_LOG_DIR') else os.path.join(os.path.expanduser('~'), '.ros/log/')
99
100 LOG_VIEWER = "/usr/bin/less -fKLnQrSU"
101 STARTER_SCRIPT = 'rosrun node_manager_fkie remote_nm.py'
102 RESPAWN_SCRIPT = 'rosrun node_manager_fkie respawn'
103 '''
104 the script used on remote hosts to start new ROS nodes
105 '''
106
107 LAUNCH_HISTORY_FILE = 'launch.history'
108 LAUNCH_HISTORY_LENGTH = 5
109
110 PARAM_HISTORY_FILE = 'param.history'
111 PARAM_HISTORY_LENGTH = 12
112
113 CFG_REDIRECT_FILE = 'redirect'
114 CFG_FILE = 'settings.ini'
115 CFG_GUI_FILE = 'settings.ini'
116
117 TIMEOUT_CONTROL = 5
118 TIMEOUT_UPDATES = 20
119
120 SEARCH_IN_EXT = ['.launch', '.yaml', '.conf', '.cfg', '.iface', '.nmprofile', '.sync', '.test', '.xml']
121 LAUNCH_VIEW_EXT = ['.yaml', '.conf', '.cfg', '.iface', '.nmprofile', '.sync', '.test']
122
123 STORE_GEOMETRY = True
124 MOVABLE_DOCK_WIDGETS = True
125 AUTOUPDATE = True
126 MAX_TIMEDIFF = 0.5
127
128 START_SYNC_WITH_DISCOVERY = False
129 CONFIRM_EXIT_WHEN_CLOSING = True
130 HIGHLIGHT_XML_BLOCKS = True
131 COLORIZE_HOSTS = True
132 CHECK_FOR_NODELETS_AT_START = True
133 SHOW_NOSCREEN_ERROR = True
134
135 SHOW_DOMAIN_SUFFIX = False
136
137 TRANSPOSE_PUB_SUB_DESCR = True
138 TIMEOUT_CLOSE_DIALOG = 5.0
139 GROUP_BY_NAMESPACE = True
140
141 DEAFULT_HOST_COLORS = [QColor(255, 255, 235).rgb()]
142
145
147 '''
148 Loads the settings from file or sets default values if no one exists.
149 '''
150 self._terminal_emulator = None
151 self._terminal_command_arg = 'e'
152 self._masteruri = masteruri_from_ros()
153 self.CFG_PATH = os.path.join(get_ros_home(), 'node_manager')
154
155
156 if not os.path.isdir(self.CFG_PATH):
157 os.makedirs(self.CFG_PATH)
158 self._cfg_path = self.CFG_PATH
159 else:
160 settings = self.qsettings(os.path.join(self.CFG_PATH, self.CFG_REDIRECT_FILE))
161 self._cfg_path = settings.value('cfg_path', self.CFG_PATH)
162
163 self._robots_path = self.ROBOTS_DIR
164 settings = self.qsettings(self.CFG_FILE)
165 self._default_user = settings.value('default_user', self.USER_DEFAULT)
166 settings.beginGroup('default_user_hosts')
167 self._default_user_hosts = dict()
168 for k in settings.childKeys():
169 self._default_user_hosts[k] = settings.value(k, self._default_user)
170 settings.endGroup()
171 try:
172 self._launch_history_length = int(settings.value('launch_history_length', self.LAUNCH_HISTORY_LENGTH))
173 except:
174 self._launch_history_length = self.LAUNCH_HISTORY_LENGTH
175 try:
176 self._param_history_length = int(settings.value('param_history_length', self.PARAM_HISTORY_LENGTH))
177 except:
178 self._param_history_length = self.PARAM_HISTORY_LENGTH
179 self._current_dialog_path = self.CURRENT_DIALOG_PATH
180 self._log_viewer = self.LOG_VIEWER
181 self._start_remote_script = self.STARTER_SCRIPT
182 self._respawn_script = self.RESPAWN_SCRIPT
183 self._launch_view_file_ext = self.str2list(settings.value('launch_view_file_ext', ', '.join(self.LAUNCH_VIEW_EXT)))
184 self._store_geometry = self.str2bool(settings.value('store_geometry', self.STORE_GEOMETRY))
185 self._movable_dock_widgets = self.str2bool(settings.value('movable_dock_widgets', self.MOVABLE_DOCK_WIDGETS))
186 self.SEARCH_IN_EXT = list(set(self.SEARCH_IN_EXT) | set(self._launch_view_file_ext))
187 self._autoupdate = self.str2bool(settings.value('autoupdate', self.AUTOUPDATE))
188 self._max_timediff = float(settings.value('max_timediff', self.MAX_TIMEDIFF))
189 self._rosconsole_cfg_file = 'rosconsole.config'
190 self.logging = LoggingConfig()
191 self.logging.loglevel = settings.value('logging/level', LoggingConfig.LOGLEVEL)
192 self.logging.loglevel_roscpp = settings.value('logging/level_roscpp', LoggingConfig.LOGLEVEL_ROSCPP)
193 self.logging.loglevel_superdebug = settings.value('logging/level_superdebug', LoggingConfig.LOGLEVEL_SUPERDEBUG)
194 self.logging.console_format = settings.value('logging/rosconsole_format', LoggingConfig.CONSOLE_FORMAT)
195 self._start_sync_with_discovery = self.str2bool(settings.value('start_sync_with_discovery', self.START_SYNC_WITH_DISCOVERY))
196 self._confirm_exit_when_closing = self.str2bool(settings.value('confirm_exit_when_closing', self.CONFIRM_EXIT_WHEN_CLOSING))
197 self._highlight_xml_blocks = self.str2bool(settings.value('highlight_xml_blocks', self.HIGHLIGHT_XML_BLOCKS))
198 self._colorize_hosts = self.str2bool(settings.value('colorize_hosts', self.COLORIZE_HOSTS))
199 self._check_for_nodelets_at_start = self.str2bool(settings.value('check_for_nodelets_at_start', self.CHECK_FOR_NODELETS_AT_START))
200 self._show_noscreen_error = self.str2bool(settings.value('show_noscreen_error', self.SHOW_NOSCREEN_ERROR))
201 self._show_domain_suffix = self.str2bool(settings.value('show_domain_suffix', self.SHOW_DOMAIN_SUFFIX))
202 self._transpose_pub_sub_descr = self.str2bool(settings.value('transpose_pub_sub_descr', self.TRANSPOSE_PUB_SUB_DESCR))
203 self._timeout_close_dialog = float(settings.value('timeout_close_dialog', self.TIMEOUT_CLOSE_DIALOG))
204 self._group_nodes_by_namespace = self.str2bool(settings.value('group_nodes_by_namespace', self.GROUP_BY_NAMESPACE))
205 settings.beginGroup('host_colors')
206 self._host_colors = dict()
207 for k in settings.childKeys():
208 self._host_colors[k] = settings.value(k, None)
209 settings.endGroup()
210 self.init_hosts_color_list()
211 self._launch_history = None
212
214 return self._masteruri
215
216 @property
218 return self._cfg_path
219
220 @cfg_path.setter
235
236 @property
238 return self._robots_path
239
240 @robots_path.setter
242 if path:
243 if not os.path.isdir(path):
244 os.makedirs(path)
245 self._robots_path = path
246 settings = self.qsettings(self.CFG_FILE)
247 settings.setValue('robots_path', self._robots_path)
248
249 @property
251 return self._default_user
252
253 @default_user.setter
259
261 if host in self._default_user_hosts:
262 return self._default_user_hosts[host]
263 return self.default_user
264
270
271 @property
273 return self._launch_history_length
274
275 @launch_history_length.setter
276 - def launch_history_length(self, length):
277 self._launch_history_length = length
278 settings = self.qsettings(self.CFG_FILE)
279 settings.setValue('launch_history_length', self._launch_history_length)
280
281 @property
283 return self._param_history_length
284
285 @param_history_length.setter
286 - def param_history_length(self, length):
287 self._param_history_length = length
288 settings = self.qsettings(self.CFG_FILE)
289 settings.setValue('param_history_length', self._param_history_length)
290
291 @property
293 return self._current_dialog_path
294
295 @current_dialog_path.setter
297 self._current_dialog_path = path
298
300 return os.path.join(self.ROBOTS_DIR, '%s.png' % robot_name)
301
302 @property
304 return self._log_viewer
305
306 @log_viewer.setter
308 self._log_viewer = viewer
309
310 @property
312 return self._start_remote_script
313
314 @start_remote_script.setter
316 self._start_remote_script = script
317
318 @property
320 return self._respawn_script
321
322 @respawn_script.setter
324 self._respawn_script = script
325
326 @property
328 return self._launch_view_file_ext
329
330 @launch_view_file_ext.setter
336
337 @property
339 return self._store_geometry
340
341 @store_geometry.setter
348
349 @property
352
353 @movable_dock_widgets.setter
360
361 @property
363 return self._autoupdate
364
365 @autoupdate.setter
372
373 @property
375 return self._max_timediff
376
377 @max_timediff.setter
384
386 result = os.path.join(self.LOG_PATH, '%s.%s' % (package, self._rosconsole_cfg_file))
387 with open(result, 'w') as cfg_file:
388 cfg_file.write('log4j.logger.ros=%s\n' % self.logging.loglevel)
389 cfg_file.write('log4j.logger.ros.roscpp=%s\n' % self.logging.loglevel_roscpp)
390 cfg_file.write('log4j.logger.ros.roscpp.superdebug=%s\n' % self.logging.loglevel_superdebug)
391 return result
392
394 settings = self.qsettings(self.CFG_FILE)
395 settings.setValue('logging/level', self.logging.loglevel)
396 settings.setValue('logging/level_roscpp', self.logging.loglevel_roscpp)
397 settings.setValue('logging/level_superdebug', self.logging.loglevel_superdebug)
398 settings.setValue('logging/rosconsole_format', self.logging.console_format)
399
400 @property
402 return self._start_sync_with_discovery
403
404 @start_sync_with_discovery.setter
406 v = self.str2bool(value)
407 if self._start_sync_with_discovery != v:
408 self._start_sync_with_discovery = v
409 settings = self.qsettings(self.CFG_FILE)
410 settings.setValue('start_sync_with_discovery', self._start_sync_with_discovery)
411
412 @property
414 return self._confirm_exit_when_closing
415
416 @confirm_exit_when_closing.setter
418 val = self.str2bool(value)
419 if self._confirm_exit_when_closing != val:
420 self._confirm_exit_when_closing = val
421 settings = self.qsettings(self.CFG_FILE)
422 settings.setValue('confirm_exit_when_closing', self._confirm_exit_when_closing)
423
424 @property
426 return self._highlight_xml_blocks
427
428 @highlight_xml_blocks.setter
435
436 @property
438 return self._colorize_hosts
439
440 @colorize_hosts.setter
447
448 @property
450 return self._check_for_nodelets_at_start
451
452 @check_for_nodelets_at_start.setter
454 val = self.str2bool(value)
455 if self._check_for_nodelets_at_start != val:
456 self._check_for_nodelets_at_start = val
457 settings = self.qsettings(self.CFG_FILE)
458 settings.setValue('check_for_nodelets_at_start', self._check_for_nodelets_at_start)
459
460 @property
462 return self._show_noscreen_error
463
464 @show_noscreen_error.setter
471
472 @property
474 return self._show_domain_suffix
475
476 @show_domain_suffix.setter
477 - def show_domain_suffix(self, value):
478 val = self.str2bool(value)
479 if self._show_domain_suffix != val:
480 self._show_domain_suffix = val
481 settings = self.qsettings(self.CFG_FILE)
482 settings.setValue('show_domain_suffix', self._show_domain_suffix)
483
484 @property
486 return self._transpose_pub_sub_descr
487
488 @transpose_pub_sub_descr.setter
490 val = self.str2bool(value)
491 if self._transpose_pub_sub_descr != val:
492 self._transpose_pub_sub_descr = val
493 settings = self.qsettings(self.CFG_FILE)
494 settings.setValue('transpose_pub_sub_descr', self._transpose_pub_sub_descr)
495
496 @property
498 return self._timeout_close_dialog
499
500 @timeout_close_dialog.setter
502 v = float(value)
503 if self._timeout_close_dialog != v:
504 self._timeout_close_dialog = v
505 settings = self.qsettings(self.CFG_FILE)
506 settings.setValue('timeout_close_dialog', self._timeout_close_dialog)
507
508 @property
510 return self._group_nodes_by_namespace
511
512 @group_nodes_by_namespace.setter
514 val = self.str2bool(value)
515 if self._group_nodes_by_namespace != val:
516 self._group_nodes_by_namespace = val
517 settings = self.qsettings(self.CFG_FILE)
518 settings.setValue('group_nodes_by_namespace', self._group_nodes_by_namespace)
519
533
539
540 @property
541 - def launch_history(self):
542 '''
543 Read the history of the recently loaded files from the file stored in ROS_HOME path.
544 @return: the list with file names
545 @rtype: C{[str]}
546 '''
547 if self._launch_history is not None:
548 return self._launch_history
549
550 result = list()
551 history_file = self.qsettings(self.LAUNCH_HISTORY_FILE)
552 size = history_file.beginReadArray("launch_history")
553 for i in range(size):
554 history_file.setArrayIndex(i)
555 if i >= self.launch_history_length:
556 break
557 launch_file = history_file.value("file")
558 if os.path.isfile(launch_file):
559 result.append(launch_file)
560 history_file.endArray()
561 self._launch_history = result
562 return self._launch_history
563
564 - def launch_history_add(self, path, replace=None):
565 '''
566 Adds a path to the list of recently loaded files.
567 :param path: the path with the file name
568 :type path: str
569 :param replace: the path to replace, e.g. rename
570 :type replace: str
571 '''
572 to_remove = replace
573 if replace is None:
574 to_remove = path
575 if self._launch_history is None:
576 self.launch_history
577 try:
578 self._launch_history.remove(to_remove)
579 except Exception:
580 pass
581 self._launch_history.append(path)
582 while len(self._launch_history) > self.launch_history_length:
583 self._launch_history.pop(0)
584 self._launch_history_save(self._launch_history)
585
586 - def launch_history_remove(self, path):
587 '''
588 Removes a path from the list of recently loaded files.
589 :param path: the path with the file name
590 :type path: str
591 '''
592 try:
593 self._launch_history.remove(path)
594 self._launch_history_save(self._launch_history)
595 except Exception:
596 pass
597
598 - def _launch_history_save(self, paths):
599 '''
600 Saves the list of recently loaded files to history. The existing history will be replaced!
601 :param paths: the list with filenames
602 :type paths: C{[str]}
603 '''
604 history_file = self.qsettings(self.LAUNCH_HISTORY_FILE)
605 history_file.beginWriteArray("launch_history")
606 for i, launch_file in enumerate(paths):
607 history_file.setArrayIndex(i)
608 history_file.setValue("file", launch_file)
609 history_file.endArray()
610 self._launch_history = list(paths)
611
613 if isinstance(v, bool):
614 return v
615 return v.lower() in ("yes", "true", "t", "1")
616
618 if isinstance(l, list):
619 return l
620 try:
621 l = l.strip('[]')
622 l = l.replace('u"', '')
623 l = l.replace('"', '')
624 l = l.replace("'", '')
625 l = l.replace(",", ' ')
626 return [utf8(i).strip() for i in l.split(' ') if i]
627 except:
628 return []
629
631 '''
632 Creates a command string to run with a terminal prefix
633 @param cmd: the list with a command and args
634 @type cmd: [str,..]
635 @param title: the title of the terminal
636 @type title: str
637 @return: command with a terminal prefix
638 @rtype: str
639 '''
640 noclose_str = '-hold'
641 if self._terminal_emulator is None:
642 self._terminal_emulator = ""
643 for t in ['/usr/bin/x-terminal-emulator', '/usr/bin/xterm', '/opt/x11/bin/xterm']:
644 if os.path.isfile(t) and os.access(t, os.X_OK):
645
646 if os.path.basename(os.path.realpath(t)) in ['terminator', 'gnome-terminal', 'xfce4-terminal']:
647 self._terminal_command_arg = 'x'
648 else:
649 self._terminal_command_arg = 'e'
650 if os.path.basename(os.path.realpath(t)) in ['terminator', 'gnome-terminal', 'gnome-terminal.wrapper']:
651 noclose_str = '--profile hold'
652 if noclose:
653 rospy.loginfo("If your terminal close after the execution, you can change this behavior in "
654 "profiles. You can also create a profile with name 'hold'. This profile will "
655 "be then load by node_manager.")
656 elif os.path.basename(os.path.realpath(t)) in ['xfce4-terminal']:
657 noclose_str = ''
658 self._terminal_emulator = t
659 break
660 if self._terminal_emulator == "":
661 raise Exception("No Terminal found! Please install one of ['/usr/bin/x-terminal-emulator', '/usr/bin/xterm', '/opt/x11/bin/xterm']")
662 noclose_str = noclose_str if noclose else ""
663 return '%s -T "%s" %s -%s %s' % (self._terminal_emulator, title, noclose_str, self._terminal_command_arg, ' '.join(cmd))
664
666 from python_qt_binding.QtCore import QSettings
667 path = settings_file
668 if not settings_file.startswith(os.path.sep):
669 path = os.path.join(self.cfg_path, settings_file)
670 return QSettings(path, QSettings.IniFormat)
671
673 self.DEAFULT_HOST_COLORS = [
674 QColor(255, 255, 235).rgb(),
675 QColor(87, 93, 94).rgb(),
676 QColor(205, 186, 136).rgb(),
677 QColor(249, 168, 0).rgb(),
678 QColor(232, 140, 0).rgb(),
679 QColor(175, 128, 79).rgb(),
680 QColor(221, 175, 39).rgb(),
681 QColor(227, 217, 198).rgb(),
682 QColor(186, 72, 27).rgb(),
683 QColor(246, 120, 40).rgb(),
684 QColor(255, 77, 6).rgb(),
685 QColor(89, 25, 31).rgb(),
686 QColor(216, 160, 166).rgb(),
687 QColor(129, 97, 131).rgb(),
688 QColor(196, 97, 140).rgb(),
689 QColor(118, 104, 154).rgb(),
690 QColor(188, 64, 119).rgb(),
691 QColor(0, 56, 123).rgb(),
692 QColor(15, 76, 100).rgb(),
693 QColor(0, 137, 182).rgb(),
694 QColor(99, 125, 150).rgb(),
695 QColor(5, 139, 140).rgb(),
696 QColor(34, 45, 90).rgb(),
697 QColor(60, 116, 96).rgb(),
698 QColor(54, 103, 53).rgb(),
699 QColor(80, 83, 60).rgb(),
700 QColor(17, 66, 50).rgb(),
701 QColor(108, 124, 89).rgb(),
702 QColor(97, 153, 59).rgb(),
703 QColor(185, 206, 172).rgb(),
704 QColor(0, 131, 81).rgb(),
705 QColor(126, 186, 181).rgb(),
706 QColor(0, 181, 26).rgb(),
707 QColor(122, 136, 142).rgb(),
708 QColor(108, 110, 107).rgb(),
709 QColor(118, 106, 94).rgb(),
710 QColor(56, 62, 66).rgb(),
711 QColor(128, 128, 118).rgb(),
712 QColor(127, 130, 116).rgb(),
713 QColor(197, 199, 196).rgb(),
714 QColor(137, 105, 62).rgb(),
715 QColor(112, 69, 42).rgb(),
716 QColor(141, 73, 49).rgb(),
717 QColor(90, 56, 38).rgb(),
718 QColor(233, 224, 210).rgb(),
719 QColor(236, 236, 231).rgb(),
720 QColor(43, 43, 44).rgb(),
721 QColor(121, 123, 122).rgb()
722 ]
723