Package node_manager_fkie
[frames] | no frames]

Source Code for Package node_manager_fkie

  1  #!/usr/bin/env python 
  2  # Software License Agreement (BSD License) 
  3  # 
  4  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions 
  9  # are met: 
 10  # 
 11  #  * Redistributions of source code must retain the above copyright 
 12  #    notice, this list of conditions and the following disclaimer. 
 13  #  * Redistributions in binary form must reproduce the above 
 14  #    copyright notice, this list of conditions and the following 
 15  #    disclaimer in the documentation and/or other materials provided 
 16  #    with the distribution. 
 17  #  * Neither the name of Fraunhofer nor the names of its 
 18  #    contributors may be used to endorse or promote products derived 
 19  #    from this software without specific prior written permission. 
 20  # 
 21  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 22  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 23  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 24  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 25  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 26  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 27  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 28  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 29  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 30  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 31  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 32  # POSSIBILITY OF SUCH DAMAGE. 
 33   
 34  import argparse 
 35  import os 
 36  import roslib.network 
 37  import rospy 
 38  import socket 
 39  import subprocess 
 40  import sys 
 41  import threading 
 42   
 43  from master_discovery_fkie.common import get_hostname 
 44  from node_manager_fkie.common import get_ros_home, masteruri_from_ros 
 45  from node_manager_fkie.file_watcher import FileWatcher 
 46  from node_manager_fkie.history import History 
 47  from node_manager_fkie.master_view_proxy import LaunchArgsSelectionRequest 
 48  from node_manager_fkie.name_resolution import NameResolution 
 49  from node_manager_fkie.progress_queue import InteractionNeededError 
 50  from node_manager_fkie.screen_handler import ScreenHandler, ScreenSelectionRequest, NoScreenOpenLogRequest 
 51  from node_manager_fkie.settings import Settings 
 52  from node_manager_fkie.ssh_handler import SSHhandler, AuthenticationRequest 
 53  from node_manager_fkie.start_handler import StartException, AdvRunCfg 
 54  from node_manager_fkie.start_handler import StartHandler, BinarySelectionRequest 
 55   
 56   
 57  PKG_NAME = 'node_manager_fkie' 
 58   
 59  __author__ = "Alexander Tiderko (Alexander.Tiderko@fkie.fraunhofer.de)" 
 60  __copyright__ = "Copyright (c) 2012 Alexander Tiderko, Fraunhofer FKIE/US" 
 61  __license__ = "BSD" 
 62  __version__ = "unknown"  # git describe --tags --dirty --always 
 63  __date__ = "unknown"  # git log -1 --date=iso 
 64   
 65  # PYTHONVER = (2, 7, 1) 
 66  # if sys.version_info < PYTHONVER: 
 67  #  print 'For full scope of operation this application requires python version > %s, current: %s' % (str(PYTHONVER), sys.version_info) 
 68   
 69   
 70  HOSTS_CACHE = dict() 
 71  ''' 
 72  the cache directory to store the results of tests for local hosts. 
 73  @see: L{is_local()} 
 74  ''' 
 75   
 76  _LOCK = threading.RLock() 
 77   
 78  _MAIN_FORM = None 
 79  _SETTINGS = None 
 80  _SSH_HANDLER = None 
 81  _SCREEN_HANDLER = None 
 82  _START_HANDLER = None 
 83  _NAME_RESOLUTION = None 
 84  _HISTORY = None 
 85  _FILE_WATCHER = None 
 86  _FILE_WATCHER_PARAM = None 
 87  _QAPP = None 
 88   
 89   
90 -def settings():
91 ''' 92 @return: The global settings 93 @rtype: L{Settings} 94 ''' 95 return _SETTINGS
96 97
98 -def ssh():
99 ''' 100 @return: The SSH handler to handle the SSH connections 101 @rtype: L{SSHhandler} 102 ''' 103 return _SSH_HANDLER
104 105
106 -def screen():
107 ''' 108 @return: The screen handler to the screens. 109 @rtype: L{ScreenHandler} 110 @see: U{http://linuxwiki.de/screen} 111 ''' 112 return _SCREEN_HANDLER
113 114
115 -def starter():
116 ''' 117 @return: The start handler to handle the start of new ROS nodes on local or 118 remote machines. 119 @rtype: L{StartHandler} 120 ''' 121 return _START_HANDLER
122 123
124 -def nameres():
125 ''' 126 @return: The name resolution object translate the the name to the host or 127 ROS master URI. 128 @rtype: L{NameResolution} 129 ''' 130 return _NAME_RESOLUTION
131 132
133 -def history():
134 ''' 135 @return: The history of entered parameter. 136 @rtype: L{History} 137 ''' 138 return _HISTORY
139 140
141 -def filewatcher():
142 ''' 143 @return: The file watcher object with all loaded configuration files. 144 @rtype: L{FileWatcher} 145 ''' 146 return _FILE_WATCHER
147 148
149 -def file_watcher_param():
150 ''' 151 @return: The file watcher object with all configuration files referenced by 152 parameter value. 153 @rtype: L{FileWatcher} 154 ''' 155 return _FILE_WATCHER_PARAM
156 157
158 -def get_ros_hostname(url):
159 ''' 160 Returns the host name used in a url, if it is a name. If it is an IP an 161 empty string will be returned. 162 163 @return: host or '' if url is an IP or invalid 164 165 @rtype: C{str} 166 ''' 167 return NameResolution.get_ros_hostname(url)
168 169
170 -def is_local(hostname, wait=False):
171 ''' 172 Test whether the given host name is the name of the local host or not. 173 @param hostname: the name or IP of the host 174 @type hostname: C{str} 175 @return: C{True} if the hostname is local or None 176 @rtype: C{bool} 177 @raise Exception: on errors while resolving host 178 ''' 179 if hostname is None: 180 return True 181 with _LOCK: 182 if hostname in HOSTS_CACHE: 183 if isinstance(HOSTS_CACHE[hostname], threading.Thread): 184 return False 185 return HOSTS_CACHE[hostname] 186 try: 187 socket.inet_aton(hostname) 188 local_addresses = ['localhost'] + roslib.network.get_local_addresses() 189 # check 127/8 and local addresses 190 result = hostname.startswith('127.') or hostname in local_addresses 191 with _LOCK: 192 HOSTS_CACHE[hostname] = result 193 return result 194 except socket.error: 195 # the hostname must be resolved => do it in a thread 196 if wait: 197 result = __is_local(hostname) 198 return result 199 else: 200 thread = threading.Thread(target=__is_local, args=((hostname,))) 201 thread.daemon = True 202 with _LOCK: 203 HOSTS_CACHE[hostname] = thread 204 thread.start() 205 return False
206 207
208 -def __is_local(hostname):
209 ''' 210 Test the hostname whether it is local or not. Uses socket.gethostbyname(). 211 ''' 212 try: 213 machine_addr = socket.gethostbyname(hostname) 214 except socket.gaierror: 215 with _LOCK: 216 HOSTS_CACHE[hostname] = False 217 return False 218 local_addresses = ['localhost'] + roslib.network.get_local_addresses() 219 # check 127/8 and local addresses 220 result = machine_addr.startswith('127.') or machine_addr in local_addresses 221 with _LOCK: 222 HOSTS_CACHE[hostname] = result 223 return result
224 225
226 -def detect_version():
227 ''' 228 Try to detect the current version from git, installed VERSION/DATE files or package.xml 229 ''' 230 try: 231 global __version__ 232 global __date__ 233 pkg_path = roslib.packages.get_pkg_dir(PKG_NAME) 234 if pkg_path is not None and os.path.isfile("%s/VERSION" % pkg_path): 235 try: 236 with open("%s/VERSION" % pkg_path) as f: 237 version = f.read() 238 __version__ = version.strip() 239 with open("%s/DATE" % pkg_path) as f: 240 datetag = f.read().split() 241 if datetag: 242 __date__ = datetag[0] 243 except Exception as err: 244 print >> sys.stderr, "version detection error: %s" % err 245 elif os.path.isdir("%s/../.git" % settings().PACKAGE_DIR): 246 try: 247 os.chdir(settings().PACKAGE_DIR) 248 ps = subprocess.Popen(args=['git', 'describe', '--tags', '--dirty', '--always'], stdin=None, stdout=subprocess.PIPE, stderr=None) 249 output = ps.stdout.read() 250 ps.wait() 251 __version__ = output.strip() 252 ps = subprocess.Popen(args=['git', 'show', '-s', '--format=%ci'], stdin=None, stdout=subprocess.PIPE, stderr=None) 253 output = ps.stdout.read().split() 254 if output: 255 __date__ = output[0] 256 ps.wait() 257 except Exception as err: 258 print >> sys.stderr, "version detection error: %s" % err 259 else: 260 import xml.dom 261 import xml.dom.minidom as dom 262 ppath = roslib.packages.find_resource(PKG_NAME, 'package.xml') 263 if ppath: 264 doc = dom.parse(ppath[0]) 265 version_tags = doc.getElementsByTagName("version") 266 if version_tags: 267 version = version_tags[0].firstChild.data 268 __version__ = version 269 else: 270 print >> sys.stderr, "version detection: no version tag in package.xml found!" 271 else: 272 print >> sys.stderr, "version detection: package.xml not found!" 273 except Exception as err: 274 print >> sys.stderr, "version detection error: %s" % err
275 276
277 -def finish(*arg):
278 ''' 279 Callback called on exit of the ros node. 280 ''' 281 # close all ssh sessions 282 if _SSH_HANDLER is not None: 283 _SSH_HANDLER.close() 284 # save the launch history 285 if _HISTORY is not None: 286 try: 287 _HISTORY.storeAll() 288 except Exception as err: 289 print >> sys.stderr, "Error while store history: %s" % err 290 from node_manager_fkie.main_window import MainWindow 291 # stop all threads in the main window 292 if isinstance(_MAIN_FORM, MainWindow): 293 _MAIN_FORM.finish() 294 if _QAPP is not None: 295 _QAPP.exit()
296 297
298 -def set_terminal_name(name):
299 ''' 300 Change the terminal name. 301 @param name: New name of the terminal 302 @type name: C{str} 303 ''' 304 sys.stdout.write("\x1b]2;%s\x07" % name)
305 306
307 -def set_process_name(name):
308 ''' 309 Change the process name. 310 @param name: New process name 311 @type name: C{str} 312 ''' 313 try: 314 from ctypes import cdll, byref, create_string_buffer 315 libc = cdll.LoadLibrary('libc.so.6') 316 buff = create_string_buffer(len(name) + 1) 317 buff.value = name 318 libc.prctl(15, byref(buff), 0, 0, 0) 319 except: 320 pass
321 322
323 -def init_settings():
324 global _SETTINGS 325 _SETTINGS = Settings()
326 327
328 -def init_globals(masteruri):
329 ''' 330 :return: True if the masteruri referred to localhost 331 :rtype: bool 332 ''' 333 # initialize the global handler 334 global _SSH_HANDLER 335 global _SCREEN_HANDLER 336 global _START_HANDLER 337 global _NAME_RESOLUTION 338 global _HISTORY 339 global _FILE_WATCHER 340 global _FILE_WATCHER_PARAM 341 _SSH_HANDLER = SSHhandler() 342 _SCREEN_HANDLER = ScreenHandler() 343 _START_HANDLER = StartHandler() 344 _NAME_RESOLUTION = NameResolution() 345 _HISTORY = History() 346 _FILE_WATCHER = FileWatcher() 347 _FILE_WATCHER_PARAM = FileWatcher() 348 349 # test where the roscore is running (local or remote) 350 __is_local('localhost') # fill cache 351 return __is_local(get_hostname(masteruri)) # fill cache
352 353
354 -def init_arg_parser():
355 parser = argparse.ArgumentParser() 356 parser.add_argument("--version", action="version", version="%s %s" % ("%(prog)s", __version__)) 357 parser.add_argument("-f", "--file", nargs=1, help="loads the given file as default on start") 358 parser.add_argument("-m", "--muri", nargs=1, default='', help="starts ROS master with given URI, usefull on hosts " 359 "with multiple interfaces. ROS_HOSTNAME will be set " 360 "to the host of this URI, but only if it is not an IP.") 361 362 group = parser.add_argument_group('echo') 363 group.add_argument("--echo", nargs=2, help="starts an echo dialog instead of node manager", metavar=('name', 'type')) 364 group.add_argument("--hz", action="store_true", help="shows only the Hz value instead of topic content in echo dialog") 365 group.add_argument("--ssh", action="store_true", help="connects via SSH") 366 367 return parser
368 369
370 -def init_echo_dialog(prog_name, masteruri, topic_name, topic_type, hz=False, use_ssh=False):
371 ''' 372 Intialize the environment to start an echo window. 373 ''' 374 # start ROS-Master, if not currently running 375 # StartHandler._prepareROSMaster(masteruri) 376 name = '%s_echo' % prog_name 377 rospy.init_node(name, anonymous=True, log_level=rospy.INFO) 378 set_terminal_name(name) 379 set_process_name(name) 380 from node_manager_fkie.echo_dialog import EchoDialog 381 global _SSH_HANDLER 382 _SSH_HANDLER = SSHhandler() 383 return EchoDialog(topic_name, topic_type, hz, masteruri, use_ssh=use_ssh)
384 385
386 -def init_main_window(prog_name, masteruri, launch_files=[]):
387 ''' 388 Intialize the environment to start Node Manager. 389 ''' 390 # start ROS-Master, if not currently running 391 StartHandler._prepareROSMaster(masteruri) 392 # setup the loglevel 393 try: 394 log_level = getattr(rospy, rospy.get_param('/%s/log_level' % prog_name, "INFO")) 395 except Exception as err: 396 print("Error while set the log level: %s\n->INFO level will be used!" % err) 397 log_level = rospy.INFO 398 rospy.init_node(prog_name, anonymous=False, log_level=log_level) 399 set_terminal_name(prog_name) 400 set_process_name(prog_name) 401 from node_manager_fkie.main_window import MainWindow 402 local_master = init_globals(masteruri) 403 return MainWindow(launch_files, not local_master, launch_files)
404 405 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 406 # %%%%%%%%%%%%% MAIN %%%%%%%% 407 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 408 409
410 -def main(name):
411 ''' 412 Start the NodeManager or EchoDialog. 413 :param name: the name propagated to the rospy.init_node() 414 :type name: str 415 ''' 416 try: 417 from python_qt_binding.QtGui import QApplication 418 except: 419 try: 420 from python_qt_binding.QtWidgets import QApplication 421 except: 422 print >> sys.stderr, "please install 'python_qt_binding' package!!" 423 sys.exit(-1) 424 425 init_settings() 426 detect_version() 427 parser = init_arg_parser() 428 args = rospy.myargv(argv=sys.argv) 429 parsed_args = parser.parse_args(args[1:]) 430 if parsed_args.muri: 431 masteruri = parsed_args.muri[0] 432 hostname = NameResolution.get_ros_hostname(masteruri) 433 os.environ['ROS_MASTER_URI'] = masteruri 434 if hostname: 435 os.environ['ROS_HOSTNAME'] = hostname 436 masteruri = settings().masteruri() 437 # Initialize Qt 438 global _QAPP 439 _QAPP = QApplication(sys.argv) 440 441 # decide to show main or echo dialog 442 global _MAIN_FORM 443 try: 444 if parsed_args.echo: 445 _MAIN_FORM = init_echo_dialog(name, masteruri, parsed_args.echo[0], 446 parsed_args.echo[1], parsed_args.hz, 447 parsed_args.ssh) 448 else: 449 _MAIN_FORM = init_main_window(name, masteruri, parsed_args.file) 450 except Exception as err: 451 sys.exit("%s" % err) 452 453 exit_code = 0 454 # resize and show the qt window 455 if not rospy.is_shutdown(): 456 # change path for access to the images of descriptions 457 os.chdir(settings().PACKAGE_DIR) 458 # _MAIN_FORM.resize(1024, 720) 459 screen_size = QApplication.desktop().availableGeometry() 460 if (_MAIN_FORM.size().width() >= screen_size.width() or 461 _MAIN_FORM.size().height() >= screen_size.height() - 24): 462 _MAIN_FORM.showMaximized() 463 else: 464 _MAIN_FORM.show() 465 exit_code = -1 466 rospy.on_shutdown(finish) 467 exit_code = _QAPP.exec_() 468 return exit_code
469