Difference between revisions of "Standards for Component Functionality"
(→Component Computation Standards) |
|||
Line 150: | Line 150: | ||
===Component Parameters=== | ===Component Parameters=== | ||
− | As stated above, a component must read its key-value parameters from a <code>.ini<code> configuration file named after the component, e.g. <code>protoComponent.ini</code>. | + | As stated above, a component must read its key-value parameters from a <code>.ini</code> configuration file named after the component, e.g. <code>protoComponent.ini</code>. |
Line 214: | Line 214: | ||
− | The following code segment shows how the <code respond()</code> method in the resource finder <code>RFModule</code> class handles input from this port. | + | The following code segment shows how the <code>respond()</code> method in the resource finder <code>RFModule</code> class handles input from this port. |
Revision as of 12:35, 6 November 2014
Contents
Introduction
The standards set out in this section are concerned with adherence to the principles of the 4Cs of Configuration, Coordination, Computation, and Communication in Component-Base Software Engineering (CBSE). By their very nature, these standards are closely tied to the implementation of the software and the YARP support for that implementation. However, this is not an appropriate place to provide a detailed guide to component-based software development and that will be documented in due course on the DREAM wiki. On the other hand, we will make detailed reference to specific YARP-based implementation techniques in the following in order to make clear that the standards entail both abstract requirements and specific implementation techniques. To see how all this works together, you should refer to the complete example implementation of a prototypical component protoComponent
elsewhere on the DREAM wiki.
For the moment, please take careful note of two important issues when it comes to the implementation of a component with YARP.
First, to develop a component for DREAM you need to define two derived classes:
- The first class is derived from the
yarp::os::RFModule class
:
class ProtoComponent : public RFModule {}
- The second class is derived from either
yarp::os::Thread
oryarp::os::RateThread
:
class ProtoComponentThread : public Thread {}
The first derived class takes care of the first two Cs in the CPC model (Configuration and Coordination) and is defined in the protoComponentConfiguration.cpp
file.
The second derived class takes care of the second two Cs in the CPC model (Computation and Communication) and is defined in the protoComponentComputation.cpp
file.
Both derived classes are declared in protoComponent.h
.
The ProtoComponent
class is instantiated as a object in protoComponentMain.cpp:<code>
/* create your module */ ProtoComponent protoComponent;
The derived class methods for <code>ProtoComponent are defined in protoComponentConfiguration.cpp
.
The ProtoComponentThread
class (or the ProtoComponentRateThread
class) is instantiated as a object and processing started in the ProtoComponent::configure()
method in the configuration .cpp
file, e.g. protoComponentConfiguration.cpp
, as follows.
/* create the thread and pass pointers to the module parameters */ protoComponentThread = new ProtoComponentThread(&imageIn, &imageOut, &thresholdValue); /* now start the thread to do the work */ protoComponentThread->start();
The derived class methods for ProtoComponentThread
or ProtoComponentRateThread
are defined in protoComponentComputation.cpp
.
Second, you need to instantiate a ResourceFinder
class, e.g. ResourceFinder rf
. The rf
object is instantiated in protoComponentMain.cpp
and the object methods are used to prepare and configure the resource finder, identifying
- where the resource finder should look for the information that defines the root of the path to use when searching for the component configuration files and resources,
- the default context (i.e. the path default to be appended to the root path), and
- the default name of the configuration file.
This is explained further in below.
Component Configuration Standards
A component is configured by its parameters. These are defined either
- in a configuration file, or
- in the application file in the
<parameter></parameter>
section, or 3. by the command line arguments.
Every component must define a default configuration file and default path to search for that file (known as a context). Furthermore, every component must allow the use to override these defaults with the component parameters.
In the following, we cover the standards for handling these default and user-defined values first, before proceeding to cover standards for handling parameters in general.
Default Configuration File
YARP assumes that every component has a default configuration file. For the purposes of these standards, this configuration file must have a .ini
extension and it must be named after the component, e.g. protoComponent.ini
.
A component must set this default filename.
This is accomplished with the setDefaultConfigFile()
method in the ResourceFinder class
, as follows.
 /* can be overridden by --from parameter */
rf.setDefaultConfigFile("protoComponent.ini");
This code goes in the main .cpp
file, e.g. protoComponentMain.cpp
.
Default Configuration Context
YARP assumes that the default configuration file is located in a default path. A component must set this default path. This is accomplished with the setDefaultContext()
method in theResourceFinder
class, as follows.
/* overridden by --context parameter */ rf.setDefaultContext("protoComponent/config");
This code also goes in the main .cpp
file, e.g. protoComponentMain.cpp
.
Note, however, that this context is not the full path where YARP should search for the configuration file. The full path is formed in flexible but rather complicated manner which we will now explain.
YARP uses a policy file to determine the paths to search for configuration files. This requires you to tell YARP where to look for the information required to initialize the root of the path to use when searching for component configuration files (in fact, YARP allows for several default paths, as described below). This is accomplished with the configure()
method in the ResourceFinder
class, as follows.
rf.configure("DREAM_ROOT", argc, argv);
DREAM_ROOT
is an environment variable (which you need to define and initialize). Its value is the path where a configuration file named DREAM_ROOT.ini
is located (e.g. C:\DREAM
). This .ini
file contains the YARP policies for finding DREAM configuration files. It defines two parameters capability_directory
and default_capability
.
capability_directory
has one value associated with it: the path that is appended to the value of the DREAM_ROOT
environment variable. For example, if we had
capability_directory Release
then the default path would begin C:/DREAM/Release
(by concatenating values associated with DREAM_ROOT
and capability_directory
.
However, YARP will search several directories based on this partial path. It forms the full path to search for the configuration files by appending the context, i.e. the value(s) associated with the default_capability parameter key. For example, if we had
default_capability configuration components/config
then the paths to be searched would be
C:/DREAM/Release/config C:/DREAM/Release/components/config
YARP also searches one other path (and, in fact, it searches it first). This path is the one that is formed by appending the value of the default context provided as an argument of the rf.setDefaultContext()
method (described above) or the value of the --context
parameter in an application. For example, if a component set the default context as follows
rf.setDefaultContext("components/protoComponent/config");
or the application specified
--context components/protoComponent/config
then YARP would first search for the configuration file in
C:/DREAM/Release/components/protoComponent/config
before then searching
C:/DREAM/Release/config C:/DREAM/Release/components/config
as dictated by the YARP policies in the DREAM_ROOT.ini
configuration file.
User-defined Configuration File
A component must allow the default name of the configuration file to be overridden by the configuration time. This is accomplished with a the --from
parameter. You don’t have to do anything to implement this functionality as the ResourceFinder class does it automatically.
User-defined Context
A component must allow the default context of the configuration file to be overridden by the configuration time. This is accomplished with a the --context
parameter. You don’t have to do anything to implement this functionality as the ResourceFinder class does it automatically.
Component Parameters
As stated above, a component must read its key-value parameters from a .ini
configuration file named after the component, e.g. protoComponent.ini
.
A component must also read its key-value parameters from the list of command line arguments.It should do t his using the check()
method in the ResourceFinder
class to do this. For exam-
ple, the C++ <code>ResourceFinder
code to parse an example parameter is shown in the segment below.
thresholdValue = rf.check("threshold", // parameter key Value(8), // default value "Key value (int)").asInt(); // key value type
This code is to be inserted in the configure() method in the configuration .cpp
file, e.g. protoComponentConfiguration.cpp
.
Component Port Names
A component must allow the port names to be set and overridden. This is treated in the same way to the parameter value above using the port name key-value parameters in the .ini configuration file. For example, in the following code segment, the parameter keys protoInputPort
and protoOutputPort
are used to read the user-defined port names, defaulting to /image:i
and /image:o
if they are not specified.
/* get the name of the input and output ports, automatically prefixing */ /* the module name by using getName() */ inputPortName = "/"; inputPortName += getName( rf.check("protoInputPort", Value("/image:i"), "Input image port (string)").asString() ); outputPortName = "/"; outputPortName += getName( rf.check("protoOutputPort", Value("/image:o"), "Output image port (string)").asString() );
Note the convention to have :i
and :o
suffixes on the input and output port names, respectively. Note also the leading /
on all port names.
Component Name
A component must allow the default name of the component to be set and overridden. This is accomplished with the --name
parameter. It should do this using the check()
method in the ResourceFinde
r class to do this, as shown below.
/* get the module name which will form the stem of all module port names */ moduleName = rf.check("name", Value("protoComponent"), "module name (string)").asString(); /* * before continuing, set the module name before getting any other parameters, * specifically the port names which are dependent on the module name */ setName(moduleName.c_str());
Component Coordination Standards
A component must provide a for runtime configuration, i.e. coordination, of the behaviour of the module by allowing commands to be issued on a special port with the same name as the component. These commands will typically alter the parameter values while the module is executing.
As already noted, the name of this port mirrors whatever is provided by the --name
parameter value. The port is attached to the terminal so that you can type in commands and receive replies. The port can be used by other modules but also interactively by a user through the YARP rpc
directive, viz.:
yarp rpc /protoComponent
This opens a connection from a terminal to the port and allows the user to then type in commands and receive replies.
The following code segment shows how the respond()
method in the resource finder RFModule
class handles input from this port.
This code is to be included in the configuration .cpp
file, e.g. protoComponentConfiguration.cpp
.
bool protoComponent::respond(const Bottle& command, Bottle& reply) { string helpMessage = string(getName().c_str()) + " commands are: \n" + "help \n" + "quit \n" + "set thr <n> ... set the threshold \n" + "(where <n> is an integer number) \n"; reply.clear(); if (command.get(0).asString()=="quit") { reply.addString("quitting"); return false; } else if (command.get(0).asString()=="help") { cout << helpMessage; reply.addString("command is: set thr <n>"); } else if (command.get(0).asString()=="set") { if (command.get(1).asString()=="thr") { thresholdValue = command.get(2).asInt(); // set parameter value reply.addString("ok"); } } return true; }
Component Computation Standards
A component must implement the functional aspect of the code using either a YARP yarp::os::Thread
class or yarp::os::RateThread
class. As already stated at the beginning of this section:
- The derived class is declared in the
.h
file.
- It is instantiated as an object and processing started in the overloaded
RFModule
methodconfigure()
in the configuration file, e.g.protoComponentConfiguration.cpp
, viz.
protoComponentThread = new ProtoComponentThread(&imageIn, &imageOut, &thresholdValue); protoComponentThread->start(); // this calls threadInit() and // it if returns true, it then calls run()
- The derived class methods are defined in
protoComponentComputation.cpp<code>.
All parameters read and initialized in the <code>configure() method of the yarp::os::RFModule
class must be passed explicitly when instantiating the derived yarp::os::Thread
or yarp::os::RateThread
object. For example, imageIn
, imageOut
, and thresholdValue
arguments above are all initialized with the rf.check()
method.
Component Communication Standards
A component must effect all communication with other component using YARP ports. Examples of port usage are provided in the protoComponent
example.