Reconfiguration

About

Q) What to do when you need to share dynamic_reconfigure variables amongst a collection of nodes?

A good example is sharing navigation motion constraints amongst a collection of nodes that control the motion of the base (general navigation, parking, docking, ...) This standalone node volunteers for the responsibility of loading and managing dynamic reconfigure servers from a central location. All you need do is feed it with a yaml/rosparam configuration that will define the dynamic_reconfigure servers you wish to fire up along with any initial parameter overrides to use when instantiating them.

It also manages these for free. You can start with an initial collection of default dynamic reconfigure servers with their respective configurations. You can then also dynamically bring up, update or tear down servers as you wish at a later time so that the reconfiguration syncs with the starting up and tearing down of your higher level applications.

Reconfigure your Reconfiguration!

Server

You will need to prepare the following (no coding necessary!):

Reconfiguration Node

  • (Optional) A yaml defining the default dyn-reconf servers to run on startup
  • A launcher for starting the reconfiguration server

Feeders (Optional)

  • A yaml defining new dyn-reconf servers to manage, or supercede default servers
  • A launcher for starting the feeder with your yaml configuration

If you’re familiar with nodelet managers and nodelets, the feeders work similarly. When the feeder node launches it sends a request to the server to fire up or update the specified list of dynamic reconfigure servers. When it terminates, it will send one last service call off to the reconfiguration server to shutdown or reset the previously started reconfigure servers.

Some policies/conventions that apply:

Initialisation of Parameters : all parameters for new dyn-reconf servers are initialised by values in the overrides section in the reconfiguration node/feeder yamls, otherwise falling back to defaults from the .cfg files. Do not initialise these parameters directly as is the norm (e.g. via rosparam in roslaunch files).

Dynamic Loading on Top of Default Profiles : having a default dyn-reconf server started by the reconfiguration node and then later feeding the same dyn-reconf server with an updated set of overrides is a great means of toggling parameter profiles for a dyn-reconf server. It is important to note here that this implementation merges the incoming overrides with the currently saved parameters and that it restores these when the feeder shuts down.

_images/superceding.png

Examples - Reconfiguration

An example set of files (also available as a demo within this package):

Reconfiguration Node - Launch
<launch>
  <arg name="debug" default="true" doc="enable/disable verbose loading/unloading/updating information"/>

  <node pkg="feed_the_troll" type="reconfiguration_server.py" name="reconfiguration" output="screen">
    <rosparam command="load" file="$(find feed_the_troll)/parameters/demo_reconfiguration_server.yaml"/>
    <param name="debug" value="$(arg debug)"/>
  </node>
</launch>
Reconfiguration Node - Yaml
servers:
  # a dyn-recfg server definition, this will be started in the reconfiguration server's namespace
  #   note: the server name is simply a unique key enabling dyn-recfg server management,
  #         it does not have to related to the dyn-recfg server namespace!
  bob:
    module: feed_the_troll.cfg.DemoConfig
    # namespace is optional....if not set, it will use the key ('bob') and create the dyn-recfg server in the
    #                          reconfiguration node's private namespace (/reconfiguration/bob)
    # overrides are optional...if not set, they will default to those in the .cfg files

  # optionally direct the reconfigure server to be somewhere other than the reconfiguration server's private namespace
  dudette:
    module: feed_the_troll.cfg.DemoConfig
    namespace: /foo/troll/dudette
    # overrides are optional...and can be partial
    overrides:
      # int_param: 50
      double_param: 0.6
      str_param: Dudette
      bool_param: False
      size: 2

Feed it using this package’s parameter feeder:

Feeder - Launch
<launch>
  <node pkg="feed_the_troll" type="param_feeder.py" name="$(anon feeder)">
      <rosparam command="load" file="$(find feed_the_troll)/parameters/demo_reconfiguration_feeder.yaml"/>
  </node>
</launch>
Feeder - Yaml
# Point this feeder at the reconfiguration server - this is where the load/unload
# services can be found. It follws the usual ros namespacing conventions - i.e.
# it can be an absolute global name (e.g. /reconfiguration) or it can be
# representative of a sibling namespace as is the case here
server_namespace: reconfiguration

# Define new or override already started dyn-recfg servers here
parameters:
  # Load up a brand new reconfigure server (this wasn't already started by the reconfiguration node)
  dude:
    # The dyn-recfg type (it is actually a python module) - don't forget the 'Config' suffix!
    module: feed_the_troll.cfg.DemoConfig
    # Load some initialisations to the param server that override those in the .cfg files
    overrides:
      int_param: 5
      #double_param: 0.6
      str_param: Dude
      bool_param: False
      size: 2

  # Override a default server's settings
  dudette:
    module: feed_the_troll.cfg.DemoConfig
    # optionally direct the reconfigure server to be somewhere other than the reconfiguration server's private namespace
    namespace: /foo/troll/dudette
    overrides:
      int_param: 1
      #double_param: 0.5
      #str_param: Dudette
      #bool_param: False
      #size: 2

A snapshot of the rosparam server is useful to illustrate where the various parameters get sourced and eventually used for the reconfigure server. Note dude and dudette get placed in two places - dude is inside the reconfiguration server, while dudette has been explicitly instructed to start its dynamic reconfigure server elsewhere.

Param List
$ rosparam list
/feeder_snorriwork_18767_3776865491990798862/parameters/dude/module
/feeder_snorriwork_18767_3776865491990798862/parameters/dude/overrides/bool_param
/feeder_snorriwork_18767_3776865491990798862/parameters/dude/overrides/double_param
/feeder_snorriwork_18767_3776865491990798862/parameters/dude/overrides/int_param
/feeder_snorriwork_18767_3776865491990798862/parameters/dude/overrides/size
/feeder_snorriwork_18767_3776865491990798862/parameters/dude/overrides/str_param
/feeder_snorriwork_18767_3776865491990798862/parameters/dudette/module
/feeder_snorriwork_18767_3776865491990798862/parameters/dudette/namespace
/feeder_snorriwork_18767_3776865491990798862/parameters/dudette/overrides/int_param
/feeder_snorriwork_18767_3776865491990798862/server_namespace
/foo/troll/dudette/bool_param
/foo/troll/dudette/double_param
/foo/troll/dudette/int_param
/foo/troll/dudette/size
/foo/troll/dudette/str_param
/reconfiguration/bob/bool_param
/reconfiguration/bob/double_param
/reconfiguration/bob/int_param
/reconfiguration/bob/size
/reconfiguration/bob/str_param
/reconfiguration/debug
/reconfiguration/dude/bool_param
/reconfiguration/dude/double_param
/reconfiguration/dude/int_param
/reconfiguration/dude/size
/reconfiguration/dude/str_param
/reconfiguration/servers/bob/module
/reconfiguration/servers/dudette/module
/reconfiguration/servers/dudette/namespace
/reconfiguration/servers/dudette/overrides/bool_param
/reconfiguration/servers/dudette/overrides/double_param
/reconfiguration/servers/dudette/overrides/size
/reconfiguration/servers/dudette/overrides/str_param
/reconfiguration_client/name

Clients

Client programs that need to tune into the dynamic reconfigure servers simply need to instantiate a dynamic reconfigure client, or more simply, a subscriber listening to the dynamic reconfigure server’s private parameter_updates topic.

Example - Python Client

from dynamic_reconfigure.client import Client
import functools
import rospy
import termcolor


def config_callback(config, namespace):
    print("")
    termcolor.cprint("Reconfiguration Client Callback", 'yellow', attrs=['bold'])
    print("")
    termcolor.cprint("  Reconfigure Client", "green")
    print("    " + termcolor.colored("{0: <23}".format("Name"), 'cyan') + ": " + termcolor.colored("{0}".format("dudette"), 'yellow'))
    print("    " + termcolor.colored("{0: <23}".format("Namespace"), 'cyan') + ": " + termcolor.colored("{0}".format(namespace), 'yellow'))
    termcolor.cprint("    Parameters", "cyan")
    for k, v in config.iteritems():
        if k != "groups":
            print("      " + termcolor.colored("{0: <21}".format(k), 'cyan') + ": " + termcolor.colored("{0}".format(v), 'yellow'))
    print("")

Example - CPP Client

There is no official C++ client, but it’s easy to subscribe to the parameter_updates topic and use the config_->__fromMessage__ method. Such a callback might look similar to the following code:

typedef std::shared_ptr<feed_the_troll::DemoConfig> config_ptr DemoConfigPtr;

void configCB(dynamic_reconfigure::ConfigPtr config_update_msg)
{
   DemoConfigPtr config = DemoConfigPtr(new feed_the_troll::DemoConfig());
   config->__fromMessage__(*config_update_msg);
}