diff --git a/README.md b/README.md index 8c0b962..2752aa2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ `config_utilities` is a minimal but powerful C++ library, providing tools for automated modular software configuration. Parse, verify, modify, and print C++ config structs to build modular object-oriented software systems at run-time. +> **ℹ️ Note**
+> With the deprecation of ROS1, we no longer support parsing configs from ROS parameters. The last version of `config_utilities` to support this is available [here](https://github.com/MIT-SPARK/config_utilities/tree/archive/ros_noetic). + ## Table of contents - [Credits](#credits) @@ -14,7 +17,7 @@ ## Credits -This library was developed by [Lukas Schmid](https://schmluk.github.io/) and [Nathan Hughes](http://mit.edu/sparklab/people.html) at the [MIT-SPARK Lab](http://mit.edu/sparklab), based on functionalities in [ethz-asl/config_utilities](https://github.com/ethz-asl/config_utilities) and [Hydra](https://github.com/MIT-SPARK/Hydra), and is released under a [BSD-3-Clause License](LICENSE)! Additional contributions welcome! This work was supported in part by the Swiss National Science Foundation and Amazon. +This library was developed by [Lukas Schmid](https://schmluk.github.io/) and [Nathan Hughes](https://nathanhhughes.github.io/) at the [MIT-SPARK Lab](http://mit.edu/sparklab), based on functionalities in [ethz-asl/config_utilities](https://github.com/ethz-asl/config_utilities) and [Hydra](https://github.com/MIT-SPARK/Hydra), and is released under a [BSD-3-Clause License](LICENSE)! Additional contributions welcome! This work was supported in part by the Swiss National Science Foundation and Amazon. ## Why `config_utilities`? @@ -128,7 +131,7 @@ This package is compatible with `ROS2`/`colcon`. Just clone it into your workspa ```bash cd ~/my_ws/src git clone git@github.com:MIT-SPARK/config_utilities.git -colcon build config_utilities_ros +colcon build --packages-up-to config_utilities_ros ``` If you want to build and install without colcon/ROS, that is easy, too! Just clone this repository and build via CMake: diff --git a/config_utilities/CMakeLists.txt b/config_utilities/CMakeLists.txt index e269941..5d619de 100644 --- a/config_utilities/CMakeLists.txt +++ b/config_utilities/CMakeLists.txt @@ -117,6 +117,6 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config_utilitiesConfig.cmake # Exposes this package to ament for ros2 run and other utilities to work find_package(ament_cmake_core QUIET) -if (${ament_cmake_core_FOUND}) +if (${ament_cmake_core_FOUND}) ament_package() endif() diff --git a/docs/Parsing.md b/docs/Parsing.md index eea3d97..70d7a95 100644 --- a/docs/Parsing.md +++ b/docs/Parsing.md @@ -7,6 +7,7 @@ This tutorial explains how to create configs and other objects from source data. - [Parse from yaml](#parse-from-yaml) - [Parse from the command line](#parse-from-the-command-line) - [Parse via global context](#parse-via-global-context) +- [Parsing in ROS2](#parsing-in-ros2) ## Parse from yaml @@ -75,11 +76,11 @@ std::unique_ptr object = createFromYamlFileWithNamespace(file_na ## Parse from the command line -It is also possible to use the same interfaces as in the yaml or ROS case but via aggregate YAML read from the command line. To use it, include `parsing/command_line.h`. +It is also possible to parse aggregate yaml read from the command line. To use it, include `parsing/command_line.h`. `config_utilities` supports parsing the following command line flags: -- `--config-utilities-file SOME_FILE_PATH`: Specify a file to load YAML from. -- `--config-utilities-yaml SOME_ARBITRARY_YAML`: Specify YAML directly from the command line. +- `--config-utilities-file SOME_FILE_PATH`: Specify a file to load yaml from. +- `--config-utilities-yaml SOME_ARBITRARY_YAML`: Specify yaml directly from the command line. - `--config-utilities-var KEY=VALUE`: Specify a new variable for the substitution context. - `--disable-substitutions/--no-disable-substitutions`: Turn off resolving substitutions @@ -87,7 +88,7 @@ It is also possible to use the same interfaces as in the yaml or ROS case but vi > Note that the `--config-utilities-file` flag allows for a namespace (i.e., `some/custom/ns`) to apply to the file globally. This is specified as `--config-utilities-file SOME_FILE@some/custom/ns`. Both command line flags can be specified as many times as needed. -When aggregating the YAML from the command line, the various flags are merged left to right (where conflicting keys from the last specified flag take precedence) and any sequences are appended together. +When aggregating the yaml from the command line, the various flags are merged left to right (where conflicting keys from the last specified flag take precedence) and any sequences are appended together. For those familiar with how the ROS parameter server works, this is the same behavior. See [here](Compositing.md#controlling-compositing-behavior) for an in-depth discussion of options as to how to control this behavior. Please also note that the `--config-utilities-yaml` currently accepts multiple space-delimited tokens (because the ROS2 launch file infrastructure does not currently correctly handle escaped substitutions), so @@ -102,7 +103,7 @@ and some_command --config-utilities-yaml {my: {cool: config}} --config-utilities-file some_file.yaml ``` -will result in the same behavior (that the resulting parsed YAML will be `{my: {cool: config}}` merged with the contents of `some_file.yaml`). +will result in the same behavior (that the resulting parsed yaml will be `{my: {cool: config}}` merged with the contents of `some_file.yaml`). Parsing directly from the command line takes one of three forms: @@ -121,15 +122,15 @@ int main(int argc, char** argv) { # Parse via global context -Usually the command line arguments or parsed YAML are not globally available to every part of an executable. -Similar to the ROS1 parameter server (and access to the parameter server by `ros::NodeHandle`), we provide a global `config::internal::Context` object (included via `parsing/context.h`) that handles tracking parsed YAML. +Usually the command line arguments or parsed yaml are not globally available to every part of an executable. +Similar to the ROS1 parameter server (and access to the parameter server by `ros::NodeHandle`), we provide a global `config::internal::Context` object (included via `parsing/context.h`) that handles tracking parsed yaml. This `config::internal::Context` is not intended to be manipulated directly. Instead, you should use one of the following methods: ```cpp int main(int argc, char** argv) { - // pushes config-utilities specific flags to the end of argv and decrements argc so that it - // looks like the command was run without any config-utilities specific flags + // pushes config_utilities specific flags to the end of argv and decrements argc so that it + // looks like the command was run without any config_utilities specific flags const bool remove_config_utils_args = true; config::initContext(argc, argv, remove_config_utils_args); @@ -162,3 +163,73 @@ const auto object_2 = config::createFromContext("optional/namespace", ba > **✅ Supports**
> Parsing via the gobal context is the recommended mode, as this supports all functionalities in a simple manner. In addition, [introspection](Introspection.md) works best on the global context. + +# Parsing in ROS2 + +Certain design choices with how parameters work in ROS2 made it impossible to bring forward our original ROS1 parameters parsing code (that leveraged `XmlRPC`). +Instead, we recommend also using the global context for parsing configs in ROS2 code. +When doing this, there are two things to watch out for. +The first is that you should parse and remove `config_utilities` command-line arguments **before** calling `rclcpp::init`. +Roughly, your top-level executable code should take this general structure: + +```cpp +#include +#include +// ... + +namespace my_ros2_package { + +struct NodeSettings { + size_t robot_id = 0; + std::filesystem::path log_path; + // ... +}; + +void declare_config(NodeSettings& config) { + using namespace config; + name("NodeSettings"); + field(config.robot_id, "robot_id"); + field(config.log_path, "log_path"); + // ... +} + +} // namespace my_ros2_package + +int main(int argc, char* argv[]) { + config::initContext(argc, argv, true); + config::setConfigSettingsFromContext(); + + rclcpp::init(argc, argv); + + const auto node_info = config::fromContext(); + // ... + + rclcpp::shutdown(); + return 0; +} +``` + +The second thing to watch out for is that nodes included in a launch file will not display stdout statements. +Both the default logger (which uses stdout/stderr) and the glog-based logger will appropriately display warnings and errors, +but you may want to implement your own `config_utilities` logger that forwards messages to the `rclcpp` logging infrastructure. + +To actually specify configuration information for a node, you just need to supply the appropriate command-line information under the `args` section of the node. +Using a portion of Hydra's launch file as example, this would look like: +```yaml +launch: + - ... + - node: + pkg: hydra_ros + exec: hydra_ros_node + name: hydra + args: > + --config-utilities-file $(find-pkg-share hydra_ros)/config/sinks/mesh_segmenter_sinks.yaml@frontend/objects + --config-utilities-file $(find-pkg-share hydra_ros)/config/sinks/active_window_sinks.yaml@active_window + --config-utilities-yaml {robot_id: $(var robot_id), log_path: $(var log_path)} + - ... +``` + +> **:warning: Warning**
+> Note that you cannot escape any portion of the command-line information when you use ROS2 substitutions. +> The command-line parsing for `config_utilities` was developed for use with ROS2 launch files originally, +> so the parsed yaml should accurately reflect the specified information without required any escaping (i.e., we handle cases where inline yaml is broken over multiple strings in `argv`). diff --git a/docs/README.md b/docs/README.md index b61e222..6fff2e6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,7 @@ The following tutorials will guide you through functionalities of `config_utilit - [Parse from yaml](Parsing.md#parse-from-yaml) - [Parse from the command line](Parsing.md#parse-from-the-command-line) - [Parse via global context](Parsing.md#parse-via-global-context) + - [Parsing in ROS2](Parsing.md#parsing-in-ros2) 4. [**Handling complex configs or types**](Types.md)