Skip to content

EPICS Interface

Matt King edited this page Sep 17, 2019 · 13 revisions

CMakeLists

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
)
...

EPICSInterface Class Files

As mentioned in the Hardware class section of this documentation, a hardware object needs an EPICSInterface variable to monitor, set, and get EPICS PVs.

Header

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

Inheritance

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.

Member Functions

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

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 monitoring PV values

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.

Source

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.

Useful functions

You may also find it useful to create the two following functions: putValue and retrieveUpdateFunctionForRecord

putValue

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

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.

Test

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).

Example Unit Test

#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");
	}
}

Clone this wiki locally