-
Notifications
You must be signed in to change notification settings - Fork 0
Factory
Firstly, create your source, header, and test files. Then you can add them to the CMakeLists.txt in CATAP/Factories/CMakeLists.txt.
# Inside EPICSInterface/CMakeLists.txt
message( "Processing EPICS Interface source files...")
set( SOURCE_FILES
source/HardwareFactory.cpp
source/MagnetFactory.cpp
source/NewHardwareFactory.cpp
)
set( INCLUDE_FILES
include/HardwareFactory.h
include/MagnetFactory.h
include/NewHardwareFactory.h
)
set( TEST_FILES
HardwareFactoryTest.cpp
MagnetFactoryTest.cpp
NewHardwareFactoryTest.cpp
)
...A Factory class will be responsible for creating, connecting (to EPICS), and storing the hardware objects you want to create. It will also be able to access all of the hardware objects that are created. This makes the Factory an ideal class for performing functions on multiple hardware objects at once!
There are a few member variables worth noting in the Factory class: isVirtual, configReader, and the map to store your hardware objects hardwareMap.
This is a boolean variable that is given to the constructor of the Factory class. It allows the configReader to determine which name to use from the Master Lattice YAML file. The below snippet demonstrates what happens when the configReader parses the YAML file:
/*Passing isVirtual when constructing the configReader member of Factory*/
/*In Factory Constructor*/
void MagnetFactory::MagnetFactory(/*isVirtual=*/true)
{
/*...*/
isVirtual = isVirtual;
// call to configReader constructor
reader = ConfigReader("Magnet", isVirtual);
}
/*Inside configReader constructor*/
ConfigReader::ConfigReader(const std::string &hardwareType, const bool &isVirtual) :
/*initialise configReader.isVirtual to isVirtual value from magnetFactory*/ isVirtual(isVirtual),
hardwareFolder(hardwareType)
{
yamlFileDestination = MASTER_LATTICE_FILE_LOCATION + SEPARATOR + hardwareFolder;
initialiseFilenameAndParsedStatusMap();
}
/*Inside ConfigReader.cpp extractControlsInformationIntoPair function*/
if (controlsInformation["PV"].as<bool>())
{
std::string controlRecords = controlsInformation["records"].as<std::string>();
boost::trim_left(controlRecords);
std::pair<std::string, std::string> pvAndRecordPair;
/*decide which name to use when constructing pvStructs*/
if (this->isVirtual)
{
pvAndRecordPair = std::make_pair(configInformationNode["properties"]["virtual_name"].as<std::string>(), controlRecords);
}
else
{
pvAndRecordPair = std::make_pair(configInformationNode["properties"]["name"].as<std::string>(), controlRecords);
}
return pvAndRecordPair;
}By creating PVs that use the virtual_name of the hardware, we are only able to communicate with the Virtual Accelerator. If isVirtual is set to False, then we will only be able to access PVs that are on the real VELA/CLARA Accelerator.
In the context of a hardwareFactory, the configReader member reader will set up all hardware objects that are defined by Master Lattice YAML files. It will fill the hardwareMap (passed by reference) with constructed objects, ready to be used throughout the Factory class.
The hardwareMap is the holder of all the hardware objects created from the Master Lattice YAML files within your hardware directory. Once you have defined your Hardware class you will be able to create the hardwareMap std::map<std::string, Hardware> hardwareMap.
It contains key,value pairs where the key is the PV name (CLA-C2V-MAG-HCOR-01) and the value being the hardware object associated with that name (Magnet).
The hardwareMap will be used to access individual/multiple hardware objects to perform tasks on them via the factory member functions. See the snippet of MagnetFactory::getCurrents(std::vector<std::string> magNames) below:
std::map<std::string, double> MagnetFactory::getCurrents(const std::vector<std::string>& names)
{
std::map<std::string, double> currents;
for (auto name : names)
{
// searching the keys of magnetMap for the names in the vector.
double current = magnetMap.find(name)->second.getCurrent();
currents[name] = current;
}
return currents;
}We will define 2 types of functions in the Factory class: setup functions, and Hardware related functions. The example of a MagnetFactory header file can be found here.
All hardware objects created by the factory will be stored in the hardware map. These will be setup by the ConfigReader which parses the Master Lattice YAML configuration files. The populate hardware map function will look like this:
void MagnetFactory::populateMagnetMap()
{
if (!reader.hasMoreFilesToParse())
{
throw std::runtime_error("Did not receive configuration parameters from ConfigReader, please contact support");
}
// while we have more files to parse from the hardware directory
while (reader.hasMoreFilesToParse())
{
//pass in the member variable magnetMap by reference,
// so magnets created in ConfigReader stay alive.
reader.parseNextYamlFile(magnetMap);
}
}This will be the first function called from within the hardware factory setup function.
Once our hardware map has been populated with hardware objects, the only thing left to do is setup their PVStructs with the appropriate values from EPICS. This will involve retrieiving PV related parameters from the epics interface, and setting up any subscriptions for the monitoring PVs.
The setup function for the magnetFactory is as follows:
populateMagnetMap();
// loop through all the magnet objects
for (auto &magnet : magnetMap)
{
//get the pvStruct vector for each magnet
std::map<std::string, pvStruct>& magPVStructs = magnet.second.getPVStructs();
for (auto &pv : magPVStructs)
{
// setting up PVStruct information for each PV
std::string pvAndRecordName = pv.second.fullPVName + ":" + pv.first;
retrieveMonitorStatus(pv.second);
magnet.second.epicsInterface->retrieveCHID(pv.second);
magnet.second.epicsInterface->retrieveCHTYPE(pv.second);
magnet.second.epicsInterface->retrieveCOUNT(pv.second);
magnet.second.epicsInterface->retrieveUpdateFunctionForRecord(pv.second);
// not sure how to set the mask from EPICS yet.
pv.second.MASK = DBE_VALUE;
// when the PV is a monitor PV, we will set up a subscription to the PV.
if (pv.second.monitor)
{
magnet.second.epicsInterface->createSubscription(magnet.second, pv.second);
}
}
}The hardware factory will be the main way of interacting with the hardware objects via Python. So we will want to define functions that act on single, multiple, and all hardware objects present in the factory's hardware map. An example of the 3 implementations for the getCurrent function are shown below:
double MagnetFactory::getCurrent(const std::string& name)
{
if (!hasBeenSetup)
{
messenger.printDebugMessage("Please call MagnetFactory.setup(VERSION)");
}
else
{
return magnetMap.find(name)->second.getCurrent();
}
return std::numeric_limits<double>::min();
}
std::map<std::string, double> MagnetFactory::getCurrents(const std::vector<std::string>& names)
{
std::map<std::string, double> currents;
for (auto name : names)
{
double current = magnetMap.find(name)->second.getCurrent();
currents[name] = current;
}
return currents;
}
std::map<std::string, double> MagnetFactory::getAllCurrents()
{
std::map<std::string, double> currents;
for (auto magnet : magnetMap)
{
double current = magnet.second.getCurrent();
currents[magnet.first] = current;
}
return currents;
}
For the above 3 functions to work in Python, we must take python-type arguments and return python-type results.
We can use the Boost.Python type classes for our getCurrents_Py function. All functions that relate to Python exposure benefit from having the suffix _Py.
Generally, any function that uses a std::vector will need to use boost::python::list and any function that uses a std::map will need to use a boost::python::dict. See the example of getCurrents below.
boost::python::dict MagnetFactory::getCurrents_Py(boost::python::list magNames)
{
std::map<std::string, double> currents;
std::vector<std::string> magNamesVector = to_std_vector<std::string>(magNames);
currents = getCurrents(magNamesVector);
boost::python::dict newPyDict = to_py_dict(currents);
return newPyDict;
}The unit test cases for a hardware factory should involve 3 things:
- create/setup a hardware factory
- use the setter function to set a property
- use the getter function to check that property has changed
Below is a test example for magnetFactory, in this test case we are changing the magnet PSU state and checking the result is as expected:
BOOST_AUTO_TEST_CASE(magnet_factory_turn_on_magnet_test)
{
std::string testMagnetName = "VM-CLA-C2V-MAG-HCOR-01";
// create/setup magnetFactory
MagnetFactory magFac = MagnetFactory(true);
magFac.setup("nominal");
// use the setter function to set PSU State to ON
magFac.turnOn(testMagnetName);
std::this_thread::sleep_for(std::chrono::seconds(1));
// use the getter function to check that PSU state has changed
BOOST_CHECK_EQUAL(magFac.getPSUState(testMagnetName), 1);
}For the full unit test suite of the magnetFactory, see here