--
HaraldBraeuning - 18 Mar 2009
Creating and Modifying the C++ code
Setting up after the initial design
After the initial design is finished and save in the designer, there is still no code to execute. To setup the directory structure and create stubs for the C++ code, execute the Fesa script:
Fesa Setup class-name version scratch
where
class-name is the name you gave to the class on saving and
version is the version number, usually 0 at the beginning. This will create the following directory structure beneath your current working directory:
- class-name
- v0
- COMMON
- GENERATED_CODE
- RT
- SERVER
- TEST
plus a liberal dose of directories called CVS, which we will ignore. The various directories contain various parts of the final Fesa class:
- COMMON
- This directory should contain code / libraries needed by the class but totally independent on the class design.
- GENERATED_CODE
- This directory contains the major part of code generated from your class design. It may be educational to browse it, but do not change anything. It will most probably break the class and be lost anyway after a change in the design.
- RT
- This directory contains the code for the real time (RT) actions. For each RT action defined in the design, a stub header and source file will be generated. These files can and usually must be modified to implement the desired functionality. The Fesa scripts are intelligent enough to keep your modifications when the design changes.
- SERVER
- Like with the RT directory, this directory contains all code associated with server actions.
- TEST
- In this directory the executable of the class will be created. Also instantiation files should be generated here.
Synchronizing design changes
If you changed the design in the designer tool, you have to synchronize your code with your design. To do this, change to the directory
class-name /v0 and execute the Fesa script
Fesa Synchronize class-name version
Coding the functionality
For the simple class described here, we have to provide the code for the 'Acquire' and 'AcquireStatus' real time actions.
Acquire RT action
The code for the 'Acquire' RT action resides in the Acquire.cpp file (and the Acquire.h file, which we do not need to edit however). For each call of the RT action, the methode Acquire::execute is called. Our code thus has to go into the body of this methode:
void Acquire::execute(RTEvent * pEv)
{
log << "executing RT action: HBrTrivialAcquire"<<endInfo;
MultiplexingContext *pContext = pEv->getMultiplexingContext();
for (unsigned int i=0; i < deviceCollection.size(); i++)
{
HBrTrivialDevice * pDev = deviceCollection[i];
log << "Acquire operates on device " << pDev->name.get() << endInfo;
long r = pDev->range.get(pContext);
long v = pDev->offset.get(pContext) + random() % r - r / 2;
pDev->value.set(v,pContext);
}
}
Some explanations are necessary. In contrast to server actions, real time actions are triggered by events, normally external events. A single Fesa class can handle several identical devices (instances) like for example to identical scalers in a VME crate. We want to handle all devices (instances) of the class in a RT action. The instances of the various devices are found in the deviceCollection. We therefore loop over all devices in the deviceCollection. (As an excersize create two instances of this simple class with different values for 'offset' and 'range' and observer what happens.)
The rest of the code inside the loop is fairly straight forward. We obtain the values for 'offset' and 'range' and calculate the random value 'v'. This we store in the 'value' field of the device. The parameter 'pContext' describes the virtual accellerator currently operating. It has no meaning in this class and can also be omitted, but it is always good style to provide it.
Similar, the code for the 'AcquireStatus' RT action resides in the
AcquireStatus.cpp file (and the
AcquireStatus.h).
The code for
AqcuireStatus::execute method is shown here:
void AcquireStatus::execute(RTEvent * pEv)
{
log << "executing RT action: HBrTrivialAcquireStatus"<<endInfo;
for (unsigned int i=0; i < deviceCollection.size(); i++)
{
HBrTrivialDevice * pDev = deviceCollection[i];
log << "AcquireStatus operates on device " << pDev->name.get() << endInfo;
long dt = time(NULL) - pDev->startTime.get();
pDev->runTime.set(dt);
}
}
It is similar to the code for the Acquire RT action discussed above. In the body of the loop, the current unix time is obtained, the unix time at the start of the server is subracted and the result is stored in the 'runTime' field of the device.
But: how and where is the value for the 'startTime' field set ?
Initializing data
In the RT directory, there are two files
class-name Realtime.cpp and
class-name Realtime.h which are not associated with any RT action. These files provide code called when the RT part is first instantiated. Any class specific setup like setting up VME hardware addresses etc. should go in the 'specificInit' method. Two similar files called
class-name Interface.cpp and
class-name Interface.h reside in the SERVER directory and provide a 'specificInit' method for the server part of the class.
We put the initialization of the 'startTime' field in the 'specificInit' method of the RT part:
void HBrTrivialRT::specificInit(int argc, char ** argv)
{
cout << " HBrTrivialRT::specificInit is called" << endl;
vector<HBrTrivialDevice*> *deviceCollection = HBrTrivialDevice::getDeviceCollection();
for (unsigned int i=0;i<deviceCollection->size();i++)
{
HBrTrivialDevice *pDev = (*deviceCollection)[i];
log << "Initialize device " << pDev->name.get() << endInfo;
long start = (long)time(NULL);
pDev->startTime.set(start);
}
}
Creating the executable
In principle, the following is obvious, but we mention it anyway. After creating / modfying all the code, exectue
make from the directory
class-name /v0. You have graduated the first level, when the code compiles without error.