From 3746a1644bf3b7603f66dd7bec9a3c3edcfe74c6 Mon Sep 17 00:00:00 2001 From: Carl Delsey Date: Thu, 30 May 2019 13:39:10 -0700 Subject: [PATCH] Added a custom launch substitution to modify YAML parameters based on launch params - lifecycle version (#771) * Initial version copied from old code * Code now installs You can `import nav2_common.launch.rewritten_yaml` now. * Bring RewrittenYaml class into launch scope like other launch packages do This lets you say: `from nav2_common.launch import RewrittenYaml` instead of `from nav2_common.launch.rewritten_yaml import RewrittenYaml` * Add support for converting strings to non-string values in YAML By the time RewrittenYaml gets the rewrite values, they've already been resolved to strings. This is a problem for use_sim_time because the code only accepts bools. To work around this RewrritenYaml has an option to convert all values to an actual type. * Add dependency in nav2_bringup package so it doesn't get forgotten. --- nav2_bringup/package.xml | 1 + nav2_common/CMakeLists.txt | 3 + nav2_common/nav2_common/__init__.py | 0 nav2_common/nav2_common/launch/__init__.py | 1 + .../nav2_common/launch/rewritten_yaml.py | 98 +++++++++++++++++++ nav2_common/package.xml | 2 + 6 files changed, 105 insertions(+) create mode 100644 nav2_common/nav2_common/__init__.py create mode 100644 nav2_common/nav2_common/launch/__init__.py create mode 100644 nav2_common/nav2_common/launch/rewritten_yaml.py diff --git a/nav2_bringup/package.xml b/nav2_bringup/package.xml index 3c1d66e2339..5f82c3353ce 100644 --- a/nav2_bringup/package.xml +++ b/nav2_bringup/package.xml @@ -16,6 +16,7 @@ launch_ros navigation2 + nav2_common ament_lint_common ament_lint_auto diff --git a/nav2_common/CMakeLists.txt b/nav2_common/CMakeLists.txt index b6d32f6614f..139835a896b 100644 --- a/nav2_common/CMakeLists.txt +++ b/nav2_common/CMakeLists.txt @@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.5) project(nav2_common NONE) find_package(ament_cmake_core REQUIRED) +find_package(ament_cmake_python REQUIRED) + +ament_python_install_package(nav2_common) ament_package( CONFIG_EXTRAS "nav2_common-extras.cmake" diff --git a/nav2_common/nav2_common/__init__.py b/nav2_common/nav2_common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/nav2_common/nav2_common/launch/__init__.py b/nav2_common/nav2_common/launch/__init__.py new file mode 100644 index 00000000000..96f936ec9ef --- /dev/null +++ b/nav2_common/nav2_common/launch/__init__.py @@ -0,0 +1 @@ +from .rewritten_yaml import RewrittenYaml diff --git a/nav2_common/nav2_common/launch/rewritten_yaml.py b/nav2_common/nav2_common/launch/rewritten_yaml.py new file mode 100644 index 00000000000..86025f4af6e --- /dev/null +++ b/nav2_common/nav2_common/launch/rewritten_yaml.py @@ -0,0 +1,98 @@ +from typing import Dict +from typing import List +from typing import Text +import yaml +import tempfile +import launch + +class DictItemReference: + def __init__(self, dictionary, key): + self.dictionary = dictionary + self.dictKey = key + + def key(self): + return self.dictKey + + def setValue(self, value): + self.dictionary[self.dictKey] = value + +class RewrittenYaml(launch.Substitution): + """ + Substitution that modifies the given Yaml file. + """ + + def __init__(self, + source_file: launch.SomeSubstitutionsType, + rewrites: Dict, + convert_types = False) -> None: + super().__init__() + + from launch.utilities import normalize_to_list_of_substitutions # import here to avoid loop + self.__source_file = normalize_to_list_of_substitutions(source_file) + self.__rewrites = {} + self.__convert_types = convert_types + for key in rewrites: + self.__rewrites[key] = normalize_to_list_of_substitutions(rewrites[key]) + + @property + def name(self) -> List[launch.Substitution]: + """Getter for name.""" + return self.__source_file + + def describe(self) -> Text: + """Return a description of this substitution as a string.""" + return '' + + def perform(self, context: launch.LaunchContext) -> Text: + yaml_filename = launch.utilities.perform_substitutions(context, self.name) + rewritten_yaml = tempfile.NamedTemporaryFile(mode='w', delete=False) + resolved_rewrites = self.resolve_rewrites(context) + data = yaml.load(open(yaml_filename, 'r')) + self.substitute_values(data, resolved_rewrites) + yaml.dump(data, rewritten_yaml) + rewritten_yaml.close() + return rewritten_yaml.name + + def resolve_rewrites(self, context): + resolved = {} + for key in self.__rewrites: + resolved[key] = launch.utilities.perform_substitutions(context, self.__rewrites[key]) + return resolved + + def substitute_values(self, yaml, rewrites): + for key in self.getYamlKeys(yaml): + if key.key() in rewrites: + raw_value = rewrites[key.key()] + key.setValue(self.convert(raw_value)) + + def getYamlKeys(self, yamlData): + try: + for key in yamlData.keys(): + for k in self.getYamlKeys(yamlData[key]): + yield k + yield DictItemReference(yamlData, key) + except AttributeError: + return + + def convert(self, text_value): + if self.__convert_types: + # try converting to float + try: + return float(text_value) + except ValueError: + pass + + # try converting to int + try: + return int(text_value) + except ValueError: + pass + + # try converting to bool + if text_value.lower() == "true": + return True + if text_value.lower() == "false": + return False + + #nothing else worked so fall through and return text + return text_value diff --git a/nav2_common/package.xml b/nav2_common/package.xml index c019558b18a..934310f6f17 100644 --- a/nav2_common/package.xml +++ b/nav2_common/package.xml @@ -9,6 +9,8 @@ ament_cmake_core + ament_cmake_python + ament_cmake_core