Parameter Database Design Pattern

Intent

Decouple the use of database parameters from the physical representation of the on-board database and allow access to the database parameters using symbolic identifiers.

Based On

This pattern is based on the principle of abstract coupling.

Motivation

The on-board database contains all the configuration parameters for an on-board system. On-board systems usually have a requirement that all configuration parameters should be easily over-writable by the ground via telecommands. Dedicated telecommands are often defined for this purpose. The telecommands usually refer to the database parameters via identifiers. This allows the telecommand to be independent of the physical location of the parameter in the on-board memory. In the absence of such independence, the telecommand content would have to be updated every time the on-board software is re-linked which would create significant configuration management problems.

In a typical situation, a "parameter change" telecommand is foreseen that can be used for updating an on-board parameter. The main argument of the telecommand is the pair: [identifier, newValue]. The first field in this pair symbolically identifies the parameter to be updated. The translation from the identifier to the specific physical location where the parameter value is stored on board is done by the OBS invisibly to the ground station and to the telecommand.

In practice, implementation of the on-board database is often as a table of sequential locations in on-board memory. The parameter identifier is simply the offset of the parameter in the table. Note that this type of table structure for storing parameters is incompatible with an object-oriented (OO) approach. In an OO approach, parameters are localized in objects and each object defines only the parameters that are specific to itself. Thus, it becomes very difficult to access parameters symbolically without making reference to the objects which encapsulate them.

Parameter databases normally exist in duplicate in a running application. The master copy of the parameter database is stored in ROM. During application initialization, a RAM version of the database is created that is initialized with the parameter values read from the ROM master copy. The application code uses the RAM version of the database and the ground station can affect its behaviour by patching its values. Obviously, every time the application is reset, the RAM database is re-loaded with the ROM values.

The parameter database pattern proposes a reusable solution that decouples the users of the on-board parameters from the data structure that holds these parameters. This makes it possible to have a physical structure that is compatible with easy manipulation on the part of the ground segment while at the same time giving on-board components the illusion of having private copies of the database parameters.

Dictionary Entries

The following abstractions or domain-wide concepts are defined to support the implementation of this design pattern:

Structure

The on-board database pattern represents the parameter database abstraction as an abstract interface ParameterDatabase that defines the way the parameter database can be accessed by users. Concrete database classes implement this interface and are free to choose any convenient physical representation for the data structure holding the parameters.

Components that need to access parameter values do so through getter and setter methods that take as arguments a symbolic identifier of the parameter. The translation from the identifier to the physical variable holding the parameter value is done internally to the concrete parameter database component.

The parameter database offers a reset method that causes all the parameter values to be re-initialized with some default value.

The parameter database component is a plug-in component for its clients that see it exclusively through the abstract ParameterDatabase interface.

Participants

Collaborations

Typical operational scenarios for this design pattern are:

Consequences

Applicability

It is necessary to have one (or a small number of) centralized parameter databases and it is desired to decouple their internal structure from their users

Implementation Issues

The most straightforward implementation of the design parameter consists in having a database organized (at the conceptual level) as two arrays dbDefault and dbOperational representing the default and operational versions the database. The default version might correspond to a ROM version that holds the fixed initial values of the database parameters. The operational version might correspond to a RAM version that holds the (possibly updated) values of the database parameters. The parameter identifier is the array indey and each element of the array contains one single datum that is the value of the parameter. This implementation is easy but it forces client components to access the database every time they need to use a database parameter. It also constrains the values of the identifiers to be sequential integers.

The latter problem can be solved by making the elements of the arrays dbDefault and dbOperational hold two values: the parameter identifier and the parameter value. This gives complete freedom to specifying the parameter identifiers but it makes getting and setting a parameter value more onerous because it becomes necessary to search the arrays to find the desired parameters.

The frequency of the accesses to the database can be reduced by organizing the operational version of the database to hold pointers to the parameters rather than their values. Note that the default version of the database cannot be made to hold pointers because this would make it dependent on the physical location of the application components thus violating the basic requirement for the database. To exemplify, consider the case of a component Comp that internally uses a variable par (assumed to be of type integer):

	class Comp {
		int par;
		. . .
	}  
Let us assume that it is desired to link par to the database parameter with identifier PAR_ID. For the sake of simplicity, let us also assume that the parameter identifiers are the indeces of the dbDefault and dbOperational arrays. Then, the following holds:
	dbDefault[PAR_ID] = . . .  // default value of the parameter
	dbOperational[PAR_ID] = &comp.par;	
where comp is one instance of class Comp. With this implementation, the getter methods of the ParameterDatabase interface no longer make sense: the users of the parameter database do not need to get the parameter values from the database. It is the (operational version of the) database that is directly linked to the variables that must be mapped to the database. The setter method is instead implemented as follows:
	void setParameter(int parId, int newValue) {
		dbOperational[parId] = newValue;
	}	
The reset operation could be implemented as follows:
	void reset() {
		for (all parameters in database)
			*dbOperational[parId] = dbDefault[parId];
	}	
Perhaps the most difficult problem with this type of implementation is how to load the operational version of the database with the pointers of the database variables. If one wants to preserve the encapsulation of these variables within their owner components, then the only solution is to make the owner components do the loading. With reference to the above example, one possibility would be to modify the interface of the Comp component as follows:
	
    class Comp {

        int par;
        . . .
        void linkParameter(int PAR_ID, DbOperational* dbOperational);

    }  
The new method should be called during the application initialization phase and is responsible for loading the entry in the operational database holding the address of the parameter par. One possible implementation would be:
	void linkParameter(int PAR_ID, DbOperational* dbOperational) {
		dbOperational[PAR_ID] = ∥
	}	
Thus, this implementation has the advantage of avoiding direct and frequent accesses to database but it requires an extension of the external interface of the components that need to map some of their internal variables to the database.

In a variant to the previous approach, the component holds the pointer to the parameter and the parameter itself is stored in the database. The component accesses the parameter value by linking to it during configuration phase. With reference to the previous example, the component Comp is now defined as follows:

	class Comp {

		int* par;
		. . .

	}  
The default value of the parameter is held as before in dbDefault[PAR_ID]. The operational value is now held in dbOperational[PAR_ID]. During the configuration phase, the following link must somehow be set up:
	Comp.par = &dbOperational[PAR_ID]; 
One way to achieve this purpose is to extend the interface of the Comp class as before:
    class Comp {

        int par;
        . . .
        void linkParameter(int PAR_ID, DbOperational* dbOperational);

    }  
The implementation of the link method however is now different:
	void linkParameter(int PAR_ID, DbOperational* dbOperational) {
		par = &dbOperational[PAR_ID];
	}	
The advantage of this approach is that now the operational database holds all the values of the application parameters (rather than just their pointers). It is therefore easier to perform a blanket update of the application parameters by simply overwriting its operational database.

How can the parameter identifiers be implemented? In a C/C++ context, one simple solution is to have one include file that defines a set of #define constants that associates integers to symbolic names. The application can then use the symbolic names as the identifiers for the database parameters.

The pattern class diagram shows only one setter and one getter method. In practice, multiple setter and getter methods will be required, one for each possible type of the parameters. Thus, for instance, there might be a getter/setter pair for integer parameters, one for float parameters, one for short parameters, etc.

The use of a centralized database introduces the possibility of errors that are similar to those arising when global variables are used. One way to remove this source of errors is to have an access control in the getter and setter methods: when the database component receives a request to return a parameter value or to update its value, it checks whether the component making the request is authorized to have access to that parameter. This could for instance be done by changing the signature of the getter method as follows:

   void getParameter(void* requester, int paramterId)
and by stipulating that client components call the getter method by passing themselves as the first argument to the getParameter method as follows:
   value = database.getParameter(this, paramterId);

The database component can then be implemented to use the identity of the requester to verify that the request is legitimate. Note that this type of access control relies on programmer discipline to call the getter method as shown above and results in run-time errors in case of illegal accesses. Access control to internal object variables is instead enforced by the compiler and results in compile-time errors.

Should the concrete database component be implemented as a singleton? Many on-board applications have one single parameter database. There is however no reason why this should be so. In principle, one could have several databases gathering together related configuration data. Thus, the database component will not always be implemented as a singleton.

OBS Framework Mapping

The implementation of this design pattern in the OBS Framework is supported by the following classes:

Sample Code

As a first example, consider a component implementing a PID controller. This component needs three parameter values representing the three gains of the controller (the proportional, the integral, and the derivative gains usually represented by symbols kp, ki and kd). Such parameters would typically be stored in a centralized database. The method implementing the PID control law would then be implemented as follows:


    ParameterDatabase* database;
    int KP, KI, KD;        // parameter identifiers
    float kp, ki, kd;

    . . .



    void loadParameterDatabase(ParameterDatabase* pd) {
        database = pd;
    }



    void computeControl() {

        kp = database->getParameter(KP);
        ki = database->getParameter(KI);
        kd = database->getParameter(KD);

        . . . // use kp, ki and kd to implement PID control law

    }  

The loadParameterDatabase is a method to load the parameter database. This method would typically be used in the application initialization phase to configure the controller component with the database plug-in component. When it is necessary to compute the PID controller output, the component reads the current values of the controller gains from the database by using the getParameter methods.

Note that the sample code above is using integer variables to store the parameter identifiers. In a C implementation, the parameter identifiers could be compiler constants (#define variables). In a C++ implementations, they could be marked as const to clearly indicate the fact that they must not be modified.

As a second example, consider the example of a component that encapsulates a generic recovery action (see one of the examples in the recovery action design pattern description) that is capable of distinguishing between sporadic and permanent faults. The distinction is made on the basis of the number of consecutive times that the fault is detected. If this number exceeds a certain threshold, then the fault is declared to be permanent. Otherwise it is treated as sporadic. The threshold would typically be a database parameter. This would result in a modification of the implementation of the RecoveryAction class of the following kind:

    class RecoveryAction {

              int LIMIT;
              int counter=0;
              bool isSporadic=false;
              ParameterDatabase* database;
              . . .

              void loadParameterDatabase(ParameterDatabase* pd) {
                   database = pd;
              }

              . . .

              bool isSporadic() {
                   return isSporadic;
              }

              void doRecovery() {
                   counter++;
                   if ( limitCounter > datbase->getParameter(LIMIT) )
                   {   
                       isSporadic=true;
                       limitCounter=0;
                   }
              }

              . . .

   }
In the version of section 9.13, the class had an internal variable limit that stored the threshold for discriminating between sporadic and permanent faults. In the new version, instead, the component only holds an identifier that is used as argument to the getter method in the database to retrieve the value of the limit.

Remarks

None

Author

A. Pasetti (P&P Software)

Last Modified

2002-05-22

Contact Us | The OBS Framework Project