Source code for hbp_nrp_cleserver.server.ROSLaunch

# ---LICENSE-BEGIN - DO NOT CHANGE OR MOVE THIS HEADER
# This file is part of the Neurorobotics Platform software
# Copyright (C) 2014,2015,2016,2017 Human Brain Project
# https://www.humanbrainproject.eu
#
# The Human Brain Project is a European Commission funded project
# in the frame of the Horizon2020 FET Flagship plan.
# http://ec.europa.eu/programmes/horizon2020/en/h2020-section/fet-flagships
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# ---LICENSE-END
"""
Wrapper for roslaunch functionality, which must be run on a "main" thread.
"""

from builtins import object

# nosetests has issues importing roslaunch since it includes some "from . import ..."
# statements that seem to break the strict PYTHONPATH set by ContinuousIntegration.
# There's no mechanism to exclude this file during test discovery (we can mock it in tests),
# detect if an ImportError occurs when being run by nosetests discovery and pass.
try:
    import roslaunch
except ImportError:
    import sys
    if 'nose' not in list(sys.modules.keys()):
        raise
    from unittest.mock import Mock
    roslaunch = Mock()

from multiprocessing import Process, Manager

import ctypes
import os
import time


[docs]class ROSLaunch(object): """ Use the roslaunch API within its own process to prevent threading issues and minimize re-implementation within the NRP. Provides clean startup/shutdown/exception handling. """ def __init__(self, launch_file): """ Launches the specified ROS .launch file in a separate subprocess. :param launch_file: The ROS .launch file to spawn. """ # shared multi-process variables for running state and errors, use a Manager since we are # sharing strings and the underlying pointers will not be handled properly otherwise # our pylint version has issues introspecting multiprocessing, disable member checks # pylint: disable=no-member self._manager = Manager() self._running = self._manager.Value(ctypes.c_bool, False) self._error = self._manager.Value(ctypes.c_char_p, None) # launch the process self._process = Process(target=self.__launch, args=(launch_file, self._running, self._error)) self._process.start() # wait until the subprocess marks itself as started, dies, or we timeout waiting start = time.time() while not self._running.value and self._process.is_alive() and not self._error.value: time.sleep(0.1) if (time.time() - start) > 10.0: self._error.value = 'roslaunch failed, timed out waiting for launch.' # handle failure results, either graceful where we set the reason or unexpected if self._error.value: self._process.terminate() # should already be the case, but no harm raise Exception(self._error.value) if not self._process.is_alive: raise Exception('roslaunch failed, unexpected termination.')
[docs] def shutdown(self): """ Terminate the roslaunch process and any child nodes. """ # make sure we're actually running if not self._running.value or not self._process.is_alive(): return # notify the subprocess to shutdown self._running.value = False # wait for termination of the subprocess while not self._error.value and self._process.is_alive(): time.sleep(0.1) # check to see if we had a shutdown error/failure and propagate it if self._error.value: self._process.terminate() # should already be the case, but no harm raise Exception(self._error.value)
@staticmethod def __launch(launch_file, running, error): """ Perform the actual validation, launch, and shutdown tasks. Runs and locks until notified to shutdown through a shared multi-process variable. :param launch_file: The ROS .launch file to spawn. :param running: Shared multi-process variable to indicate running state. :param error: Shared multi-process variable to propagate error message. """ try: # abort if the specified launch file does not exist / is inaccessible if not os.path.isfile(launch_file): raise Exception('ROS launch file does not exist: %s' % launch_file) if not os.access(launch_file, os.R_OK): raise Exception('ROS launch file is not readable: %s' % launch_file) # configure roslaunch, force all output to stdout for our logger # based on example: http://wiki.ros.org/roslaunch/API%20Usage uuid = roslaunch.rlutil.get_or_generate_uuid(None, False) roslaunch.configure_logging(uuid) launch = roslaunch.parent.ROSLaunchParent(uuid, [launch_file], force_screen=True) # start the nodes and notify the waiting launch process launch.start() running.value = True # wait until notified to shutdown while running.value: time.sleep(0.1) # shutdown the launched nodes launch.shutdown() # pylint: disable=broad-except except Exception as e: error.value = str(e)