Modules and Representations¶
A robot control program usually consists of several modules, each performing a certain task, e.g. image processing, self-localization, or walking. Modules require a certain input and produce a certain output (so-called representations). Therefore, they have to be executed in a specific order to make the whole system work. The module framework introduced by the GermanTeam1 simplifies the definition of the interfaces of modules and automatically determines the sequence in which the modules must be executed. It consists of the blackboard, the module definition, and a visualization component.
Blackboard¶
The blackboard2 is the central storage for information, i.e. for the representations. Each thread is associated with its own instance of the blackboard. Representations are transmitted through inter-thread communication if a module in one thread requires a representation that is provided by a module in another thread. The blackboard itself is a map that associates names of representations to reference-counted instances of these representations. It only contains entries for the representations that at least one of the modules in its associated thread actually requires or provides.
Module Definition¶
The definition of a module consists of three parts: the module interface, its actual implementation, and a statement that allows instantiating the module. Here is an example:
MODULE(SimpleBallLocator,
{,
REQUIRES(BallPercept),
REQUIRES(FrameInfo),
PROVIDES(BallModel),
DEFINES_PARAMETERS(
{,
(Vector2f)(5.f, 0.f) offset,
(float)(1.1f) scale,
}),
});
class SimpleBallLocator : public SimpleBallLocatorBase
{
void update(BallModel& ballModel) override
{
if(theBallPercept.wasSeen)
{
ballModel.position = theBallPercept.position * scale + offset;
ballModel.wasLastSeen = theFrameInfo.time;
}
}
}
MAKE_MODULE(SimpleBallLocator);
The module interface defines the name of the module (e.g. SimpleBallLocator
), the representations that are required to perform its task, the representations provided by the module, and the parameters of the module, the values of which can either be defined in place or loaded from a file. The interface basically creates a base class for the actual module following the naming scheme <ModuleName>Base
. The actual implementation of the module is a class that is derived from that base class. The following statements are available in a module definition:
REQUIRES
declares that this module has read-only access to this representation in the blackboard (and only to this) viathe<representationName>
. As is described in this section, modules can expect that all their required representations have been updated before any of their provider methods is called. If a required representation is provided by multiple threads, a so-called alias must be used to specify which representation it uses. An alias is a representation that has a thread name prefix before the actual name, e.g.<thread><representation>
. The representation must be derived from the actual representation without adding members to it.USES
declares that this module accesses a representation, which does not necessarily have to be updated before the module is run. This should only be used to resolve conflicts in the configuration. Note thatUSES
is not considered when exchanging data between threads.PROVIDES
declares that this module will update this representation. It must define anupdate
method for each representation that is provided. For each representation the execution time can be determined (cf. this section) and it can be sent to a host PC or even altered by it.PROVIDES_WITHOUT_MODIFY
does exactly the same asPROVIDES
, except that the representation can not be inspected or altered by the host PC.DEFINES_PARAMETERS
andLOADS_PARAMETERS
allow the modifiable parameterization of modules, as described in this section. It is recommended to use this for all parameters.
Finally, the MAKE_MODULE
statement allows the module to be instantiated. The optional second parameter can override the static function that delivers the list of required and provided representations to the system (cf. this section). This can be used to add requirements to a module that can not be part of the module definition, as is needed by the behavior framework. While the module interface is usually part of the header file, the MAKE_MODULE
statement has to be part of the implementation file.
MODULE
is a macro that gets all the information about the module as parameters, i.e. they are all separated by commas. The macro ignores its second and its last parameter, because by convention, these are used for opening and closing curly brackets. These let some source code formatting tools indent the definitions as a block. Currently, MODULE
is limited to up to 90 definitions between the curly brackets. When the macro is expanded, it creates a lot of hidden functionality. Each entry that references a representation makes sure that it is created in the blackboard when the module is constructed and freed when the module is destroyed. The information that a module has certain requirements and provides certain representations is not only used to generate a base class for that module, but is also available for sorting the providers, and can be requested by a host PC. On a host PC, the information can be used to change the configuration and for visualization. If a MessageID
id<representation>
exists, the representation can also be logged.
If a representation provided defines a parameterless method draw
, that method will be called after the representation was updated. The method is intended to visualize the representation using the techniques described in this section. If the representation defines a parameterless method verify
, that method will be called in Debug and Develop builds after the representation was updated as well. A verify
method should contain ASSERT
s that check whether the contents of the representation are plausible. Both methods are only called if they are defined in the representation itself and not if they are inherited from its base class.
Configuring Providers and Threads¶
Since modules can provide more than a single representation, the configuration has to be performed on the level of providers. For each representation, it can be selected which module provides it or that it is not provided at all. Normally, the configuration is read from the file Config/Scenarios/<scenario>/threads.cfg
during the start-up of the robot control program, but it can also be changed interactively when the robot has a debug connection to a host PC using the command mr
. This file defines which threads exist at all and which providers run in them. This allows for completely different configurations in different scenarios.
The configuration does not specify the sequence in which the providers are executed. This sequence is automatically determined at runtime based on the rule that all representations required by a provider must already have been provided by other providers before, i.e. those providers have to be executed earlier. This is calculated in the thread Debug
. Only valid configurations are then sent to all other threads.
In some situations, it is required that a certain representation is provided by a module before any other representation is provided by the same module, e.g., when the main task of the module is performed in the update
method of that representation, and the other update
methods rely on results computed in the first one. Such a case can be implemented by both requiring and providing a representation in the same module.
Default Representations¶
During the development of the robot control software, it is sometimes desirable to simply deactivate a certain provider or module. As mentioned above, it can always be decided not to provide a certain representation, i.e. all providers generating the representation are switched off. However, not providing a representation typically makes the set of providers inconsistent, because other providers rely on that representation, so they would have to be deactivated as well. This has a cascading effect. In many situations, it would be better to be able to deactivate a provider without any effect on the dependencies between the modules. In this case it is possible to enter representations to a separate list in the thread configuration file. These default representations never change their value – so they basically remain in their initial state – but still exist in the blackboard, and thereby, all dependencies can be resolved. However, in terms of functionality, a configuration using that list is not complete.
Parameterizing Modules¶
Modules usually need some parameters to function properly. Those parameters can also be defined in the module's interface description. Parameters behave like protected class members and can be accessed in the same way. Additionally, they can be manipulated from the console using the commands get/set parameters:<ModuleName>
.
There are two different parameter initialization methods. In the hard-coded approach, the initialization values are specified as part of the module definition. They are defined using the DEFINES_PARAMETERS
macro. This macro is intended for parameters that may change during development but will never change again afterwards. In contrast, loadable parameters are initialized to values that are loaded from a configuration file upon module creation, i.e. the initialization values are not specified in the source file. These parameters are defined using the LOADS_PARAMETERS
macro. By default, parameters are loaded from a file with the same base name as the module, but starting with a lowercase letter3 and the extension .cfg
. For instance if a module is named SimpleBallLocator
, its configuration file is simpleBallLocator.cfg
. This file can be placed anywhere in the usual configuration file search path. It is also possible to assign a custom name to a module's configuration file by passing the name as a parameter to the constructor of the module's base class.
Only either DEFINES_PARAMETERS
or LOADS_PARAMETERS
can be used in a module definition. They can both only be used once. Their syntax follows the definition of generated streamable classes. Parameters may have any data type as long as it is streamable.
-
Thomas Röfer, Jörg Brose, Daniel Göhring, Matthias Jüngel, Tim Laue, and Max Risler. GermanTeam 2007. In Ubbo Visser, Fernando Ribeiro, Takeshi Ohashi, and Frank Dellaert, editors, RoboCup 2007: Robot Soccer World Cup XI Preproceedings, Atlanta, GA, USA, 2007. RoboCup Federation. ↩
-
V. Jagannathan, Rajendra Dodhiawala, and Lawrence S. Baum, editors. Blackboard Architectures and Applications. Academic Press, Boston, 1989. ↩
-
Actually, if a module name begins with more than one uppercase letter, all initial uppercase letters but the last one are transformed to lowercase, e.g. the module
LEDHandler
would read the fileledHandler.cfg
if it would read its parameters from a file. ↩