Standards for Component Functionality

From David Vernon's Wiki
Revision as of 04:24, 17 February 2015 by Dvernon (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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 CINDY 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 CINDY 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 CINDY 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 or yarp::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("CINDY_ROOT", argc, argv);

CINDY_ROOT is an environment variable (which you need to define and initialize). Its value is the path where a configuration file named CINDY_ROOT.ini is located (e.g. C:\CINDY). This .ini file contains the YARP policies for finding CINDY 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 CINDY_ROOT environment variable. For example, if we had

capability_directory Release

then the default path would begin C:/CINDY/Release (by concatenating values associated with CINDY_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:/CINDY/Release/config
C:/CINDY/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:/CINDY/Release/components/protoComponent/config

before then searching

C:/CINDY/Release/config
C:/CINDY/Release/components/config

as dictated by the YARP policies in the CINDY_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 ResourceFinderclass 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 ResourceFinder 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 method configure() 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.



Return to The CINDY Cognitive Architecture main page.