-
Notifications
You must be signed in to change notification settings - Fork 114
Planner Design Framework
Many sampling based planners make use of similar functionalities or approaches. To make building new planners as easy as possible, we provide a modular object-oriented framework for creating new planners.
All planners have a common structure as is presented above. The core components of TrajectoryGenerators and TrajectoryEvaluators are supposed to have a flexible enough interface to allow for a large variety of planners. However, they don't need to implement every single of their virtual functions. Each of these functions has different implementations encapsulated as modules, allowing reusage of common implementations. All functions, which are not explicitely overridden, will be built from these modular building blocks.
Modules are specified and configured via the ros parameter server, where the main planner and other submodules forward their namespace plus the respective module namespace to the ModuleFactory. Every module needs to specify a "type" parameter, according to which the desired classes are instantiated. They are then individually configured through the ros parameter server.
This object-oriented modular approach has the advantage, that planners consisting of already existing modules can be built at run time via a ros parameter configuration. No more recompiling for testing new planners! The recommended way is to load a "my_config.yaml" into the planner node. An example configuration is given here. The default namespaces for modules, relative to the planner node, are as follows:
trajectory_generator:
type: "MyTrajectoryGenerator"
segment_selector:
type: "MyExpansionPolicy"
generator_updater:
type: "MyUpdateStrategy"
trajectory_evaluator:
type: "MyTrajectoryEvaluator"
cost_computer:
type: "MyCost"
value_computer:
type: "MyValueFunc"
next_selector:
type: "MyNextBestSelection"
evaluator_updater:
type: "MyUpdateStrategy"
back_tracker:
type: "DontGetStuck"
Notice that for some functionalities multiple modules can be combined using the Decorator Pattern:
evaluator_updater:
type: "Operation1"
following_updater:
type: "Operation2"
following_updater:
...
Custom implementations for all module types can easily be added to the framework. Custom modules inherit from a base module type that specifies the interface with other classes. All base types inherit from the Module
class, which contains utilities to configure and build modules through the ModuleFactory
. To create a custom module follow these steps:
- Create your module class
All module classes should be organized as follows:
class MyModule : public ModuleBase {
public:
// override virtual functions of the base class
bool moduleBasePureVirtualFunction(TrajectorySegment *root){
/* do some magic here */
return success;
}
protected:
// All modules need to be friends of the ModuleFactory to allow creation and setup
friend ModuleFactory;
// protected default constructor
MyModule() {}
// Statically register the module to the factory so that it can be constructed
static ModuleFactory::Registration<MyModule> registration;
// make the module configurable through the factory (required by Module class)
void setupFromParamMap(Module::ParamMap *param_map){
int my_param_default = 1;
// Use the utility function of Module to set params
setParam<int>(param_map, "my_param", &p_my_param_, my_param_default);
// Make sure to propagate the setup through the inheritance chain
ModuleBase::setupFromParamMap(param_map);
}
// guarantee that parameters fulfill required constraints (optional by Module class)
bool checkParamsValid(std::string *error_message) {
if (p_my_param <= 0) {
*error_message = "my_param expected > 0";
return false;
}
// Make sure to propagate the validation check through the inheritance chain
return ModuleBase::checkParamsValid(error_message);
}
// params
int p_my_param_;
};
// make sure the registration is initialized with a unique name (i.e. the name of the class)
ModuleFactory::Registration<MyModule> MyModule::registration("MyModule");
If your module uses other modules, e.g. to be used in a chain of decorators, adapt these functions as follows:
class MyDecoratorModule : public ModuleBase {
public:
bool moduleBasePureVirtualFunction(TrajectorySegment *root){
// pass the request down the chain
return following_module->moduleBasePureVirtualFunction(root);
}
protected:
void setupFromParamMap(Module::ParamMap *param_map){
// Submodules are created using this formalism
std::string args; // the module args need to be specifiable
std::string param_ns = (*param_map)["param_namespace"]; // default extends the parent namespace
setParam<std::string>(param_map, "following_module_args", &args, param_ns + "/following_module");
following_module_ = ModuleFactory::Instance()->createModuleBase(args, verbose_modules_, parent_);
}
// Modules are unique_ptrs
std::unique_ptr<ModuleBase> following_module_;
};
- Add some doc..?
For inspiration maybe look through some of the default modules.
Planner Structure
Planner Design Framework
Running and Evaluating a Simulated Experiment
Code Index