diff --git a/CMakeLists.txt b/CMakeLists.txt
index cfba2ac..bb75223 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.3)
+cmake_minimum_required(VERSION 3.0.2)
project(roslaunch2)
find_package(catkin REQUIRED)
diff --git a/README.md b/README.md
index cdf9509..8330dec 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
* [License & Citing](#cite)
# Overview
-roslaunch2 is a (pure Python based) ROS package that facilitates writing **versatile, flexible and dynamic launch configurations for the Robot Operating System (ROS 1) using Python**, both for simulation and real hardware setups, as contrasted with the existing XML based launch file system of ROS, namely [roslaunch](http://wiki.ros.org/roslaunch). Note that roslaunch2 is not (yet) designed and developed for ROS 2 but for ROS 1 only although it may also inspire the development (of the launch system) of ROS 2. It is **compatible with all ROS versions providing roslaunch** which is used as its backend. roslaunch2 has been tested and heavily used on ROS Indigo, Jade, Kinetic, and Lunar; it also supports a “dry-mode” to generate launch files without ROS being installed at all. The **key features** of roslaunch2 are
+roslaunch2 is a (pure Python based) ROS package that facilitates writing **versatile, flexible and dynamic launch configurations for the Robot Operating System (ROS 1) using Python**, both for simulation and real hardware setups, as contrasted with the existing XML based launch file system of ROS, namely [roslaunch](http://wiki.ros.org/roslaunch). Note that roslaunch2 is not (yet) designed and developed for ROS 2 but for ROS 1 only although it may also inspire the development (of the launch system) of ROS 2. It is **compatible with all ROS versions providing roslaunch** which is used as its backend. roslaunch2 has been tested and used on ROS Indigo, Jade, Kinetic, Lunar, Melodic, and Noetic; it also supports a “dry-mode” to generate launch files without ROS being installed at all. The **key features** of roslaunch2 are
- versatile control structures (conditionals, loops),
- extended support for launching and querying information remotely,
- an easy-to-use API for also launching from Python based ROS nodes dynamically, as well as
@@ -106,6 +106,6 @@ The entire code is **BSD 3-Clause licenced**, see [here](https://github.com/Code
}
```
-Copyright (c) 2017-2019, Adrian Böckenkamp, Department of Computer Science VII, TU Dortmund University.
+Copyright (c) 2017-2020, Adrian Böckenkamp, Department of Computer Science VII, TU Dortmund University.
All rights reserved.
diff --git a/package.xml b/package.xml
index 6d514df..fe30775 100644
--- a/package.xml
+++ b/package.xml
@@ -1,5 +1,5 @@
-
+
roslaunch2
0.0.1
@@ -18,9 +18,12 @@
catkin
python-catkin-pkg
+ python3-setuptools
+ python-setuptools
python-enum34-pip
pyro4
python-rospkg
roslaunch
+ rosbash
diff --git a/setup.py b/setup.py
index f6a23b6..83da1d8 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from distutils.core import setup
+from setuptools import setup
from catkin_pkg.python_setup import generate_distutils_setup
d = generate_distutils_setup(
diff --git a/src/roslaunch2/__init__.py b/src/roslaunch2/__init__.py
index 4356cd5..8386089 100644
--- a/src/roslaunch2/__init__.py
+++ b/src/roslaunch2/__init__.py
@@ -3,21 +3,21 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 12/11/2019
+# Date: 08/06/2020
# Import all submodules typically used in launch modules:
-from group import *
-from parameter import *
-from machine import *
-from package import *
-from logger import *
-from utils import *
-from remote import *
-from launch import *
-from node import *
-from environment import *
-from test import *
-from helpers import *
+from .group import *
+from .parameter import *
+from .machine import *
+from .package import *
+from .logger import *
+from .utils import *
+from .remote import *
+from .launch import *
+from .node import *
+from .environment import *
+from .test import *
+from .helpers import *
import argparse
@@ -172,7 +172,8 @@ def terminate(instance):
def main(command_line_args=None):
"""
Defines the core logic (= Python based dynamic launch files) of roslaunch2. It does NOT create any
- launch modules or the like.
+ launch modules or the like. This function is not meant to be called directly. See `start()` and
+ `start_async()` for more details.
:param command_line_args: List with command line arguments as strings
:return: None
diff --git a/src/roslaunch2/environment.py b/src/roslaunch2/environment.py
index 54719db..aa23b19 100644
--- a/src/roslaunch2/environment.py
+++ b/src/roslaunch2/environment.py
@@ -3,13 +3,13 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import lxml.etree
import warnings
-import interfaces
-import machine
+from . import interfaces
+from . import machine
class EnvironmentVariable(interfaces.GeneratorBase, interfaces.Composable):
diff --git a/src/roslaunch2/group.py b/src/roslaunch2/group.py
index 3797678..c30b3dc 100644
--- a/src/roslaunch2/group.py
+++ b/src/roslaunch2/group.py
@@ -3,15 +3,15 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import lxml.etree
import warnings
-import interfaces
-import remapable
-import node
-import launch
+from . import interfaces
+from . import remapable
+from . import node
+from . import launch
class Group(remapable.Remapable, interfaces.Composer, interfaces.Composable):
diff --git a/src/roslaunch2/helpers.py b/src/roslaunch2/helpers.py
index 1c005b6..8db2556 100644
--- a/src/roslaunch2/helpers.py
+++ b/src/roslaunch2/helpers.py
@@ -3,10 +3,10 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 12/11/2019
+# Date: 08/06/2020
-import logger
-import node
+from . import logger
+from . import node
class Helpers:
diff --git a/src/roslaunch2/interfaces.py b/src/roslaunch2/interfaces.py
index cf71ff5..9a553ea 100644
--- a/src/roslaunch2/interfaces.py
+++ b/src/roslaunch2/interfaces.py
@@ -3,7 +3,7 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 26/01/2018
+# Date: 08/06/2020
import copy
import lxml.etree
@@ -126,9 +126,9 @@ def add_env_variables_to_nodes(self, environment_variable_dict=None):
"""
if environment_variable_dict is None:
environment_variable_dict = {}
- from environment import EnvironmentVariable
- from group import Group
- from node import Node
+ from .environment import EnvironmentVariable
+ from .group import Group
+ from .node import Node
# Copy dict to not change the argument in higher recursion levels:
tmp_env_dict = dict(environment_variable_dict)
diff --git a/src/roslaunch2/launch.py b/src/roslaunch2/launch.py
index 8406c49..2ba41e6 100644
--- a/src/roslaunch2/launch.py
+++ b/src/roslaunch2/launch.py
@@ -3,16 +3,16 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import warnings
import lxml.etree
-import interfaces
-import machine
-import remapable
-import node
-import group
+from . import interfaces
+from . import machine
+from . import remapable
+from . import node
+from . import group
class Launch(interfaces.Composable, interfaces.Composer, remapable.Remapable):
diff --git a/src/roslaunch2/machine.py b/src/roslaunch2/machine.py
index 985da49..b160e83 100644
--- a/src/roslaunch2/machine.py
+++ b/src/roslaunch2/machine.py
@@ -3,7 +3,7 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import lxml.etree
import getpass
@@ -12,9 +12,9 @@
import socket
import enum
-import interfaces
-import utils
-import remote
+from . import interfaces
+from . import utils
+from . import remote
class Machine(interfaces.GeneratorBase):
diff --git a/src/roslaunch2/node.py b/src/roslaunch2/node.py
index 62c320f..4e68310 100644
--- a/src/roslaunch2/node.py
+++ b/src/roslaunch2/node.py
@@ -3,21 +3,21 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import warnings
import lxml.etree
import enum
-
-import remapable
-import interfaces
-import package
-import machine
-import parameter
-import environment
import random
import string
+from . import remapable
+from . import interfaces
+from . import package
+from . import machine
+from . import parameter
+from . import environment
+
class Output(enum.IntEnum):
"""
@@ -151,7 +151,7 @@ def __del__(self):
warnings.warn('{} has been created but never add()ed.'.format(str(self)), Warning, 2)
def __str__(self):
- return '{:s}@{:s}: {:s}'.format(self.node, self.pkg, self.name)
+ return '{:s}@{:s}: {:s}'.format(str(self.node), str(self.pkg), str(self.name))
def add(self, param):
"""
diff --git a/src/roslaunch2/package.py b/src/roslaunch2/package.py
index 1c666dc..f723e8b 100644
--- a/src/roslaunch2/package.py
+++ b/src/roslaunch2/package.py
@@ -3,14 +3,14 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import rospkg
import os
import sys
import Pyro4
-import logger
+from . import logger
class Package:
@@ -79,9 +79,8 @@ def __init__(self, name=None, silent=False):
:param name: Name of ROS package
:param silent: True if no exceptions should be thrown if the package was not found
"""
- self.name = name
try:
- self.path = Package.__get_pkg_path_cached(name)
+ self.set_name(name)
except rospkg.ResourceNotFound:
if not silent:
raise
@@ -93,7 +92,7 @@ def get_name(self):
:return: ROS package name
"""
- return self.name
+ return self.__name
@Pyro4.expose
def set_name(self, name):
@@ -102,8 +101,8 @@ def set_name(self, name):
:param name: ROS package name
"""
- self.name = name
- self.path = Package.__get_pkg_path_cached(name)
+ self.__name = name
+ self.__path = Package.__get_pkg_path_cached(name)
name = property(get_name, set_name)
@@ -114,16 +113,16 @@ def get_path(self):
:return: ROS package path
"""
- return self.path
+ return self.__path
def _set_path(self, pkg_path): # not exposed to Pyro!
- if self.name:
- self.path = pkg_path
+ if self.__name:
+ self.__path = pkg_path
else:
- self.path = None
+ self.__path = None
def __nonzero__(self):
- return bool(self.path) # for Python 2.x
+ return bool(self.__path) # for Python 2.x
def __bool__(self):
return self.__nonzero__() # for Python 3.x
@@ -131,7 +130,7 @@ def __bool__(self):
path = property(get_path, _set_path)
def __str__(self):
- return self.name
+ return self.__name
@staticmethod
def valid(pkg):
@@ -162,7 +161,7 @@ def has_node(self, node_name, warn=True):
:param warn: True if a warning about the missing node should be emitted
:return: True if node exists, False otherwise
"""
- pkg = os.path.join(self.path, '../..')
+ pkg = os.path.join(self.__path, '../..')
# Just consider files that are executable:
if [f for f in Package.get_paths_to_file(pkg, node_name) if os.access(f, os.X_OK)]:
# if len(res) > 1:
@@ -171,7 +170,7 @@ def has_node(self, node_name, warn=True):
return True
else:
if warn:
- logger.warning("Node '{}' in package '{}' not found.".format(node_name, self.name))
+ logger.warning("Node '{}' in package '{}' not found.".format(node_name, self.__name))
return False
@staticmethod
@@ -201,7 +200,7 @@ def use(self, path_comp, **kwargs):
path_comp += '.pyl'
mod_path = self.find(path_comp, True)
if not mod_path:
- raise ValueError("Launch module '{:s}' in package '{:s}' not found.".format(path_comp, self.name))
+ raise ValueError("Launch module '{:s}' in package '{:s}' not found.".format(path_comp, self.__name))
m = Package.import_launch_module(mod_path)
return m.main(**kwargs)
@@ -223,7 +222,7 @@ def import_launch_module(full_module_path):
search_path = os.path.dirname(os.path.abspath(module_name))
if search_path not in sys.path:
sys.path.append(search_path)
- if sys.version_info < (3, 3): # Python 2.x and 3.x where x < 3
+ if sys.version_info < (3, 3): # Python 2.x and 3.y where x >= 4 and y < 3
import imp
return imp.load_source(module_name, full_module_path)
elif sys.version_info < (3, 4): # Python 3.3 and 3.4
@@ -231,6 +230,9 @@ def import_launch_module(full_module_path):
return importlib.machinery.SourceFileLoader(module_name, full_module_path).load_module()
elif sys.version_info >= (3, 5): # Python 3.5+
import importlib.util
+ import importlib.machinery
+ # Allow any extenstions (not only .py and .so, and .pyl in particular):
+ importlib.machinery.SOURCE_SUFFIXES.append('')
spec = importlib.util.spec_from_file_location(module_name, full_module_path)
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
@@ -246,21 +248,21 @@ def find(self, path_comp, silent=False):
case of failure
:return: first found file (full path) or None if silent==True and nothing found
"""
- key = ''.join([self.name, path_comp])
+ key = ''.join([self.__name, path_comp])
if key in Package.__find_cache:
return Package.__find_cache[key]
if not path_comp:
- return self.path
- dir_path = os.path.join(self.path, path_comp if not path_comp.startswith(os.path.sep) else path_comp[1:])
+ return self.__path
+ dir_path = os.path.join(self.__path, path_comp if not path_comp.startswith(os.path.sep) else path_comp[1:])
if os.path.isdir(dir_path):
Package.__find_cache[key] = dir_path
return dir_path
- f = Package.get_paths_to_file(self.path, path_comp)
+ f = Package.get_paths_to_file(self.__path, path_comp)
if len(f) > 1:
logger.log("Found {} files, unique selection impossible (using first).".format(', '.join(f)))
if not f:
if not silent:
- raise IOError("No files like '{}' found in '{}'.".format(path_comp, self.name))
+ raise IOError("No files like '{}' found in '{}'.".format(path_comp, self.__name))
else:
return None
Package.__find_cache[key] = f[0]
@@ -285,6 +287,6 @@ def selective_find(self, path_comp_options, path_comp_prefix='', silent=False):
return path
# Nothing found
if not silent:
- raise IOError("None of the queried files found in '{}'.".format(self.name))
+ raise IOError("None of the queried files found in '{}'.".format(self.__name))
else:
return None
diff --git a/src/roslaunch2/parameter.py b/src/roslaunch2/parameter.py
index ff689ed..268c32e 100644
--- a/src/roslaunch2/parameter.py
+++ b/src/roslaunch2/parameter.py
@@ -3,19 +3,19 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import lxml.etree
import warnings
import argparse
import os.path
import yaml
-
-import interfaces
-import machine
-import logger
import enum
+from . import interfaces
+from . import machine
+from . import logger
+
def load_from_file(path, only_parse_known_args):
"""
@@ -35,7 +35,7 @@ def __call__(self, parser, namespace, values, option_string=None):
filename = "{:s}.yaml".format(values[0])
filepath = os.path.join(path, filename)
try:
- f = yaml.load(file(filepath, 'r'))
+ f = yaml.safe_load(file(filepath, 'r'))
for key, value in f.iteritems():
if value is not None:
if only_parse_known_args:
@@ -52,17 +52,24 @@ class LaunchParameter(argparse.ArgumentParser):
"""
Represents a parameter for a launch module. For example, this can influence whether to select simulator A or B.
These parameters are NOT consumed by ROS nodes (refer to ``ServerParameter`` and ``FileParameter`` in such cases).
+ Such parameters are passed by command line, for example: roslaunch2 my_pkg my_launch.pyl --param foo
+
+ Whats special about this class is that all command line parameters from all (possibly included) launch modules are
+ added so that calling roslaunch2 with the special command line flag "--ros-args" prints all available parameters
+ for the given launch file along with a description of it (like roslaunch does). In order to make this work, you
+ must finally call 'get_args()'.
"""
launch_parameter_list = [] # Static list collection all LaunchParameter instances.
- def __init__(self, prog=None, description=None, epilog=None, version=None,
+ def __init__(self, prog=None, description=None, epilog=None,
parents=None, formatter_class=argparse.HelpFormatter, prefix_chars='-',
fromfile_prefix_chars=None, argument_default=None, conflict_handler='resolve'):
if parents is None:
parents = []
- argparse.ArgumentParser.__init__(self, prog, str(), description, epilog, version, parents,
- formatter_class, prefix_chars, fromfile_prefix_chars,
- argument_default, conflict_handler, add_help=False)
+ argparse.ArgumentParser.__init__(self, prog=prog, usage=None, description=description, epilog=epilog,
+ parents=parents, formatter_class=formatter_class, prefix_chars=prefix_chars,
+ fromfile_prefix_chars=fromfile_prefix_chars, argument_default=argument_default,
+ conflict_handler=conflict_handler, add_help=False)
self.ros_argument_group = self.add_argument_group(title='ROS launch module arguments', description=None)
def add(self, name, help_text, default, short_name=None, **kwargs):
diff --git a/src/roslaunch2/remapable.py b/src/roslaunch2/remapable.py
index d241154..3716b29 100644
--- a/src/roslaunch2/remapable.py
+++ b/src/roslaunch2/remapable.py
@@ -3,11 +3,11 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
from lxml import etree
-import interfaces
+from . import interfaces
class Remapable(interfaces.GeneratorBase):
diff --git a/src/roslaunch2/remote.py b/src/roslaunch2/remote.py
index b826453..0cb3f23 100644
--- a/src/roslaunch2/remote.py
+++ b/src/roslaunch2/remote.py
@@ -3,14 +3,14 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
import os
import Pyro4
-import utils
import tempfile
-import package
+from . import utils
+from . import package
__all__ = ["API", "Resolvable", "Path", "Variable"]
diff --git a/src/roslaunch2/test.py b/src/roslaunch2/test.py
index cc88b1b..67790fc 100644
--- a/src/roslaunch2/test.py
+++ b/src/roslaunch2/test.py
@@ -3,10 +3,10 @@
#
# Author: Adrian Böckenkamp
# License: BSD (https://opensource.org/licenses/BSD-3-Clause)
-# Date: 13/03/2018
+# Date: 08/06/2020
-import interfaces
-import node
+from . import interfaces
+from . import node
class Test(node.Runnable):