Utility tools to make working with config structs for ROS (and non-ROS) C++ libraries more uniform, readable, and convenient.
Important Note (Nov 2023):
A newer version of config_utilities has been released, that supports everything config_utilities can do and more with cleaner interfaces and tools! You can find the new release at https://github.com/MIT-SPARK/config_utilities. This version of config_utilities will no longer be actively maintained.
- Author: Lukas Schmid schmluk@ethz.ch.
- Affiliation: Autonomous Systems Lab (ASL), ETH Zürich.
- Version: 1.3.1
- License: BSD-3-Clause.
This library was developed to make working with config structs for object-oriented C++ libraries as simple as possible. Using config_utilities-based configs has the following advantages:
- Having all parameters in a config struct, rather than with other variables, makes code clearer and more readable:
if (x_ < config_.x_max) { doMagic(); }
- Configs can be easily checked for validity with verbose warnings to avoid runtime issues:
MyClass::MyClass(const Config& config) : config_(config.checkValid()) {}
- For projects consisting of a library and a ROS-package, the configs don't have any ROS dependency and can be used in the library.
In the ROS-package, configs can be created from NodeHandles without requiring additional code:
MyConfig c = config_utilities::getConfigFromRos<MyConfig>(nh_private);
- Verbose and clear printing for debugging or verification can be setup for the entire project:
config_utilities::Global::Settings().default_print_width = 80; std::cout << config.toString() << std::endl; std::ofstream(log_file) << config_utilities::Global::printAllConfigs();
- Everything related to a config is located at its definition/implementation, making all its properties clear and easy to change.
No need for additional code in other files where changes could be overlooked.
my_class_using_configs.h / my_class_using_configs.cpp { // Contains *all* variables, defaults, valid values, printing, ROS-creation, factory registration, ... }
- Easy registration and factory creation for arbitrary classes with and without configs:
static config_utilities::Factory::Registration<Base, Derived> registration("MyDerivedKey"); std::shared_ptr<Base> object = config_utilities::Factory::create<Base>("MyDerivedKey");
-
Header-Only
This mini-library can be used as a header only library by simply copying
config_utilities.hpp
into your project. Requires glog and xmlrpc++.- Dependencies:
# As System Install: sudo apt update sudo apt install libxmlrpc-c++8-dev sudo apt-get install libgoogle-glog-dev
# Alternatively, as Catkin Package with prior ROS installation: cd ~/catkin_ws/src git clone git@github.com:ethz-asl/glog_catkin.git catkin build glog_catkin
- Dependencies:
-
Demos Package
To run the demos, the ROS package can be conveniently installed via catkin:
cd ~/catkin_ws/src git clone https://github.com/ethz-asl/config_utilities.git # Install all dependencies via rosinstall wstool init . ./config_utilities/config_utilities.rosinstall # new workspace wstool merge -t . ./config_utilities/config_utilities.rosinstall # existing workspace wstool update cd config_utilities catkin build --this
Briefly describes the interfaces available and how to use them.
Set default settings for the entire project. Set these before instantiating a config.
config_utilities::Global::Settings().default_print_width = 80;
config_utilities::Global::Settings().default_print_indent = 30;
Define configs by inheriting from the provided config_utilities::Config
and templating itself.
All following interfaces are part of such a Config
.
struct MyConfig : public config_utilities::Config<MyConfig> {
double x_max = 1.0;
};
Use these to interact with a Config
.
bool isValid(bool print_warnings=false) const; // Validity information.
Config checkValid() const; // Enforce validity.
Config& checkValid();
string toString() const; // Printing.
Override these functions to implement the corresponding behavior.
virtual void initializeDependentVariableDefaults(); // Initialization.
virtual void checkParams() const; // Param validity checks.
virtual void printFields() const; // Printing behavior.
virtual void fromRosParam(); // ROS-creation behavior.
virtual void setupParamsAndPrinting(); // Combines fromRosParam() and printFields() in a single call. Precedes but does not exclude these functions if implemented.
Use these tools within the virtual functions to create the desired behavior.
// General settings.
void setConfigName(const std::string& name);
void setPrintWidth(int width);
void setPrintIndent(int indent);
// Set these values in the constructor.
MyConfig::MyConfig() {
setConfigName("MyConfig");
...
}
// Parameter validity constraints.
void checkParamGT<T>(const T& param, const T& value, const std::string& name) const;
void checkParamGE<T>(const T& param, const T& value, const std::string& name) const;
void checkParamLT<T>(const T& param, const T& value, const std::string& name) const;
void checkParamLE<T>(const T& param, const T& value, const std::string& name) const;
void checkParamEq<T>(const T& param, const T& value, const std::string& name) const;
void checkParamNE<T>(const T& param, const T& value, const std::string& name) const;
// Any condition can be checked using checkParamCond().
void checkParamCond(bool condition, const std::string &warning) const;
// Validity of member configs can be checked using checkParamConfig().
void checkParamConfig(const Config& config) const;
// Use these checks within checkParams().
MyConfig::checkParams() const {
checkParamGT(x_max, 0.0, "x_max");
...
}
// Printing.
void printField<T>(const std::string& name, const T& field, const std::string& unit="") const;
void printText(const std::string& text) const;
// Use these tools within printFields().
MyConfig::printFields() const {
printField("x_max", x_max);
...
}
// Creation from ROS params.
void rosParam<T>(const std::string& name, T* param);
// Also works for configs, these don't require a name but an optional sub_namespace.
void rosParam(Config* config, const std::string& sub_namespace = "");
// The namespace of the creating nodehandle can be queried via rosParamNameSpace().
string rosParamNameSpace();
// Use these tools within fromRosParam(). Defaults should be set at variable declaration.
MyConfig::fromRosParam() {
rosParam("x_max", &x_max);
...
}
// Merged param and printing setup. Internally uses the same tools as printField() and rosParam() to avoid code duplication.
void setupParam<T>(const std::string& name, T* param, const std::string& unit);
// Use these tools within setupParamsAndPrinting().
MyConfig::setupParamsAndPrinting() {
setupParam("x_max", &x_max, "m");
...
}
Use these tools to let derived classes register themselves to the factory and create them based on a string or from the ROS parameter server.
// Register any class to the factory using a static struct.
static config_utilities::Factory::Registration<BaseT, DerivedT, ConstructorArgs...> registration("IdentifierString");
// Register a class that has a Config struct as a member to enable ROS creation.
static config_utilities::Factory::RegistrationRos<BaseT, DerivedT, ConstructorArgs...> registration("IdentifierString");
// Create any class registered to the factory.
std::unique_ptr<BaseT> config_utilities::Factory::create<BaseT>("IdentifierString", constructor_args);
// Create a that uses a Config from ros params. The param 'type' is expected to provide the identifier string.
// The constructors of each DerivedT is expected to take as first argument a DerivedT::Config.
std::unique_ptr<BaseT> config_utilities::FactoryRos::create<BaseT>(const ros::NodeHandle& nh, constructor_args);
Use these configs like regular sub-configs as a member of a config. These can hold varying configs to create components downstream. The contained objects need to be registered via the ROS factory. Variable configs can be filled in via the getConfigFromRos()
function. Variable configs need to be templated on the base type they create. Additional functionalities include:
// Check whether the config is setup.
bool isSetup() const;
// Get the string identifier for the type to be created (ROS Factory).
std::string getType() const;
// Create the downstream object using this config. Args are additional constructor args.
std::unique_ptr<BaseT> create(Args... args) const;
Verbose examples of the most important functionalities are given in the demos folder. They can easily be run after building the config_utilities ROS-package.
This demo describes how to use the ConfigChecker
class to verify non-config_utilities configs in a readable way:
rosrun config_utilities demo_config_checker
Runs a validity check and prints all warnings to console:
============================== IndependentConfig ===============================
Warning: Param 'a' is expected >= '0' (is: '-1').
Warning: Param 'c' is expected to be 'this is c' (is: 'test').
================================================================================
This demo describes how to define custom classes that utilize a Config
struct:
rosrun config_utilities demo_config
This will setup a class using a valid config and print it to console, as well as a creation attempt with an invalid config:
================================ MyClass-Config ================================
a: 1 (default)
b: 2.34 (default)
b_half: 1.17 (default)
c: this is c (default)
An_extremely_unecessarily_and_unreasonably_long_param_name:
A_similarly_unreasonably_long_param_value. (defaul
t)
And a custom message.
================================================================================
================================ MyClass-Config ================================
Warning: Param 'a' is expected >= '0' (is: '-1').
Warning: Param 'c' is expected to be 'this is c' (is: 'test').
Warning: b is expected > a.
================================================================================
This demo describes how to use the config_utilities::getConfigFromRos<Config>()
function to setup configs via the ROS parameter server:
roscore & rosrun config_utilities demo_ros_param
Sets config params from ros and prints them to console:
================= Config (from ROS params) =================
a: 123
b: 45.6
c: seven-eight-nine
vec: [1, 2, 3]
map: {m1: 1, m2: 2}
T: t: [0, 0, 0] RPY°: [-0, 0, -0]
namespace: /demo_ros_param
============================================================
This demo describes how to use nested configs, which can be used to setup derived and base classes:
roscore & rosrun config_utilities demo_inheritance
Sets up a derived class from ROS, prints its nested config, and check for validity:
===================== MyDerivedConfig ======================
e: Bananas are yellow.
f: 6
other_config:
a: 11.1
b: 222
base_config:
c: False
d: 3.45
other_config:
a: 1
b: 2
============================================================
======================= OtherConfig ========================
Warning: Param 'a' is expected >= '0' (is: '-1').
============================================================
========================== MyBase ==========================
Warning: Member config 'OtherConfig' is not valid.
============================================================
===================== MyDerivedConfig ======================
Warning: Member config 'MyBase' is not valid.
============================================================
This demo describes how to use the config_utilities::Factory::Registration()
and config_utilities::Factory::create()
tools to instantiate various objects.
rosrun config_utilities demo_factory
Defines two derived classes and registers them statically to the factory, which can then be created using a string identifier:
This is a DerivedA with i=0, f=0.
This is a DerivedB with i=1, f=2.
E1104 20:45:29.080973 6629 config_utilities.hpp:1152] No module with name 'DerivedC' registered to the factory for base '4Base' and constructor arguments 'i, f'. Registered are: DerivedB, DerivedA.
'object' is invalid.
This demo describes how to use the config_utilities::Factory::RegistrationRos()
and config_utilities::FactoryRos::create()
tools to create different objects that use varying custom configs from the parameter server.
roscore & rosrun config_utilities demo_ros_factory
Defines two derived classes that use different configs and creates them from a ROS nodehandle:
This is a DerivedA with i=1, f=2.345, and info 'How to create a DerivedA'.
This is a DerivedB with info 'Now the type param has changed'.
=============================== DerivedB Config ================================
s: param text.
f: 2.345
================================================================================
This demo describes how to use the config_utilities::Factory::RegistrationRos()
and config_utilities::VariableConfig
parameter struct to adaptively create downstream objects without direct access to a ROS nodehandle.
roscore & rosrun config_utilities demo_variable_config
Creates a component of the primary object downstream using a Variable Config:
================================ Object Config =================================
i: 5 (default)
base_config: Uninitialized Variable Config.
================================================================================
Config is valid: false
================================ Object Config =================================
i: 10
base_config (Variable Config: DerivedB):
s: text for derived B.
================================================================================
Config is valid: true
This is a DerivedB with s='text for derived B.' and base data='10'.
This demo describes how to use the config_utilities::Global
tools to dynamically change settings and get information on all configs.
rosrun config_utilities demo_global_settings
Creates three confings A,B, and C, prints C with two different printing layouts defined by global settings, and then summarizes all existing configs.
=================================== ConfigC ====================================
config_a:
a [m]: 123 (default)
aa: config a text (default)
config_b:
b [Hz]: 45
bb: varied config b text
================================================================================
=============== ConfigC ================
config_a:
a: 123
aa: config a text
config_b:
b: 45
bb: varied config b text
========================================
Value of all existing params:
=============== ConfigA ================
a: 123
aa: config a text
=============== ConfigB ================
b: 30
bb: config b text
=============== ConfigC ================
config_a:
a: 123
aa: config a text
config_b:
b: 45
bb: varied config b text
========================================