-
Notifications
You must be signed in to change notification settings - Fork 0
EPICS Interface
Firstly, create your source, header, and test files. Then you can add them to the CMakeLists.txt in CATAP/EPICSInterface/CMakeLists.txt.
# Inside EPICSInterface/CMakeLists.txt
message( "Processing EPICS Interface source files...")
set( SOURCE_FILES
source/EPICSInterface.cpp
source/EPICSMagnetInterface.cpp
source/EPICSNewHardwareInterface.cpp
)
set( INCLUDE_FILES
include/EPICSInterface.h
include/EPICSMagnetInterface.h
include/EPICSNewHardwareInterface.h
)
set( TEST_FILES
EPICSInterfaceTest.cpp
EPICSMagnetInterfaceTest.cpp
EPICSNewHardwareInterfaceTest.cpp
)
...As mentioned in the Hardware class section of this documentation, a hardware object needs an EPICSInterface variable to monitor, set, and get EPICS PVs.
We start by defining the member variables and functions for the EPICS Interface in a header file. The example of the EPICSMagnetInterface header can be found here
Every EPICS interface object that is created should be a child of the general EPICSInterface class. The general EPICSInterface class allows us to define functions and variables that are used by all EPICS interface objects that are created.
For example, every EPICS interface object will have the functions retrieveCHID(pvStruct &pvStruct) and createSubscriptions(Hardware &hardware, pvStruct &pvStruct).
These functions can then be called from any EPICS interface object that is created. These functions belong to the general EPICS interface object because, conceptually, every type of EPICS interface will need access to these functions; by defining them here, we reduce code duplication across multiple EPICS interface objects.
To inherit from EPICSInterface, your specific EPICS interface object should have a definition that looks like this and a constructor that looks like this.
The role of your EPICS interface object will be to provide functions that essentially set and update values for PVs using EPICS channel access.
Functions for setting PV values are the simplest to explain since they take 2 arguments that are intuitive: a value, and a pvStruct. The pvStruct provides information telling EPICS which PV we want to set, and the value argument is just the value we wish to set the PV to. (An example of this is setNewCurrent(value, pvStruct) in EPICSMagnetInterface.)
Functions for updating PV values are a bit more complicated. They take a const struct event_handler_args as their argument. An event_handler_args struct is an EPICS object that holds some vital information used for monitoring PV values so that we can perform actions when they change.
The event_handler_args type is defined as such (more information here):
typedef struct event_handler_args {
void *usr; /* user argument supplied with request (your Hardware object) */
chanId chid; /* channel id */
long type; /* the type of the item returned */
long count; /* the element count of the item returned */
const void *dbr; /* a pointer to the item returned (value of the PV) */
int status; /* ECA_XXX status of the requested op from the server */
} evargs;When we create the subscription using EPICSInterface::createSubscription we pass arguments to the ca_create_subscription function that are then used to define the members of the event_handler_args struct.
The 2 important members that get set are (void*)&hardware and pvStruct.updateFunction. The function that takes event_handler_args as an argument will be set to the pvStruct.updateFunction and the event_handler_args struct member variable void *usr will become our (void*)&hardware.
By setting void *usr to a hardware object, it allows us to set the values of member variables for that hardware object. For example, see this snippet of the updateFunction updateCurrent defined in the EPICSMagnetInterface class:
void EPICSMagnetInterface::updateCurrent(const struct event_handler_args args)
{
// ...
else if (args.type == DBR_DOUBLE)
{
// checking the status of monitoring is ECA_NORMAL
MY_SEVCHK(args.status);
// recast the hardware object held by args.usr as a Magnet object
Magnet* recastMagnet = static_cast<Magnet*>(args.usr);
// we now have access to Magnet function setCurrent
// and we set it to the value held by args.dbr (returned value of the PV).
// since args.dbr is a DBR_DOUBLE, we must recast it as a double to set the magnet current variable
recastMagnet->setCurrent(*(double*)(args.dbr));
messenger.printDebugMessage("GETSETI VALUE FOR " + recastMagnet->getHardwareName() + ": " + std::to_string(*(double*)(args.dbr)));
}
// ...
}This function will be assigned to the updateFunction variable for a pvStruct within the Factory setup procedure where the subscription is created like this:
/* INSIDE MAGNETFACTORY SETUP PROCEDURE */
for (auto &magnet : magnetMap)
{
for (auto &pv : magPVStructs)
{
/* setting up other pvStruct variables...*/
magnet.second.epicsInterface->retrieveUpdateFunctionForRecord(pv.second);
/* ... */
if (pv.second.monitor)
{
/* create subscription will use pvStruct.updateFunction variable */
magnet.second.epicsInterface->createSubscription(magnet.second, pv.second);
}
}
/* INSIDE MAGNET EPICS INTERFACE retrieveUpdateFunctionForRecord FUNCTION */
void EPICSMagnetInterface::retrieveUpdateFunctionForRecord(pvStruct &pvStruct) const
{
if (pvStruct.pvRecord == "GETSETI")
{
pvStruct.updateFunction = this->updateCurrent;
}
if (pvStruct.pvRecord == "RPOWER")
{
pvStruct.updateFunction = this->updatePSUState;
}
if (pvStruct.pvRecord == "READI")
{
pvStruct.updateFunction = this->updateREADI;
}
if (pvStruct.pvRecord == "RILK")
{
pvStruct.updateFunction = this->updateRILK;
}
}
You will have to create an update function for all of the PVs that you wish to monitor, in the Magnet example we have the following update functions: updateCurrent, updateREADI, updateRILK, updatePSUState. Which correspond to the PVs: GETSETI, READI, RILK, and RPOWER respectively.
Once you have defined the update and set functions in your EPICS interface header, the source (.cpp) file will essentially define how each of those methods set the corresponding hardware variables. For example, EPICSMagnetInterface::updateCurrent() will call Magnet::setCurrent(double value) which sets the value of the current variable in the Magnet class.
You may also find it useful to create the two following functions: putValue and retrieveUpdateFunctionForRecord
putValue is a template function that calls ca_put with whichever type you give it.
For example:
/*Calling putValue with a double*/
putValue<double>(pvStruct, newCurrentToSet);
/*Calling putValue with an int*/
putValue<int>(pvStruct, newPSUStateToSet);retrieveUpdateFunctionForRecord will take a pvStruct and set the updateFunction parameter based on the pvStruct record. The following code is from EPICSMagnetInterface:
void EPICSMagnetInterface::retrieveUpdateFunctionForRecord(pvStruct &pvStruct) const
{
if (pvStruct.pvRecord == "GETSETI")
{
pvStruct.updateFunction = this->updateCurrent;
}
if (pvStruct.pvRecord == "RPOWER")
{
pvStruct.updateFunction = this->updatePSUState;
}
if (pvStruct.pvRecord == "READI")
{
pvStruct.updateFunction = this->updateREADI;
}
if (pvStruct.pvRecord == "RILK")
{
pvStruct.updateFunction = this->updateRILK;
}
}This will be called from the MagnetFactory setup function which allows us to keep the EPICS-specific code out of the Factory classes.
For unit tests to run EPICS related code, you must be set to work with the Virtual Accelerator. This will involve changing your system environment variables EPICS_CA_ADDR_LIST and EPICS_CA_SERVER_PORT (Please ask a CATAP developer for help with this).
#define BOOST_TEST_MODULE EPICSMagnetInterfaceTest
// Boost includes
#include <boost/test/unit_test.hpp>
// Standard Library includes
#include <string>
// CATAP includes
#include <EPICSMagnetInterface.h>
// EPICS includes
#ifndef __CINT__
#include <cadef.h>
#endif
BOOST_AUTO_TEST_CASE(epics_magnet_interface_put_and_get_value_test)
{
// create your epicsInterface
EPICSMagnetInterface epicsInterface = EPICSMagnetInterface();
// create a dummy pvStruct for set
pvStruct setIPV;
// set parameters of pvStruct for set
setIPV.fullPVName = "VM-CLA-C2V-MAG-VCOR-01";
setIPV.pvRecord = "SETI";
epicsInterface.retrieveCHID(setIPV);
epicsInterface.retrieveCHTYPE(setIPV);
epicsInterface.retrieveCOUNT(setIPV);
// create a dummy pvStruct for get
pvStruct getSetIPV;
// set parameters of pvStruct for get
getSetIPV.fullPVName = "VM-CLA-C2V-MAG-VCOR-01";
getSetIPV.pvRecord = "GETSETI";
epicsInterface.retrieveCHID(getSetIPV);
epicsInterface.retrieveCHTYPE(getSetIPV);
epicsInterface.retrieveCOUNT(getSetIPV);
//check if we are connected to EPICS
if (ca_state(setIPV.CHID) == cs_conn)
{
// check we have read and write access to the set PV
BOOST_CHECK_EQUAL(ca_read_access(setIPV.CHID), 1);
BOOST_CHECK_EQUAL(ca_write_access(setIPV.CHID), 1);
srand(time(NULL));
// create a random value that we can set
double currentToSet = rand() % 10 + 1.0;
// set PV value from epicsInterface
epicsInterface.putValue(setIPV, currentToSet);
double returnValue;
// call ca_get directly and assign its value to returnValue variable
ca_get(getSetIPV.CHTYPE, getSetIPV.CHID, &returnValue);
ca_pend_io(CA_PEND_IO_TIMEOUT);
// check value returns from ca_get is the same as our currentToSet variable
BOOST_CHECK_EQUAL(returnValue, currentToSet);
}
else
{
TEST_LOGGER.printMessage("CANNOT CONNECT TO EPICS");
}
}