diff --git a/src/Makefile b/src/Makefile index 357c7a9..3871918 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,6 +16,7 @@ INC += ntmultiChannel.h INC += ntscalarMultiChannel.h INC += ntndarray.h INC += ntmatrix.h +INC += ntenum.h LIBSRCS += ntutils.cpp LIBSRCS += ntfield.cpp @@ -27,6 +28,7 @@ LIBSRCS += ntmultiChannel.cpp LIBSRCS += ntscalarMultiChannel.cpp LIBSRCS += ntndarray.cpp LIBSRCS += ntmatrix.cpp +LIBSRCS += ntenum.cpp LIBRARY = nt diff --git a/src/nt/nt.h b/src/nt/nt.h index 2403c62..8dc4f52 100644 --- a/src/nt/nt.h +++ b/src/nt/nt.h @@ -19,6 +19,7 @@ #include #include #include +#include #endif /* NT_H */ diff --git a/src/nt/ntenum.cpp b/src/nt/ntenum.cpp new file mode 100644 index 0000000..213f444 --- /dev/null +++ b/src/nt/ntenum.cpp @@ -0,0 +1,190 @@ +/* ntenum.cpp */ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * EPICS pvDataCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#define epicsExportSharedSymbols +#include +#include + +using namespace std; +using namespace epics::pvData; + +namespace epics { namespace nt { + +static NTFieldPtr ntField = NTField::get(); + +namespace detail { + +static NTFieldPtr ntField = NTField::get(); + + +StructureConstPtr NTEnumBuilder::createStructure() +{ + FieldBuilderPtr builder = + getFieldCreate()->createFieldBuilder()-> + setId(NTEnum::URI)-> + add("value", ntField->createEnumerated()); + + if (descriptor) + builder->add("descriptor", pvString); + + if (alarm) + builder->add("alarm", ntField->createAlarm()); + + if (timeStamp) + builder->add("timeStamp", ntField->createTimeStamp()); + + size_t extraCount = extraFieldNames.size(); + for (size_t i = 0; i< extraCount; i++) + builder->add(extraFieldNames[i], extraFields[i]); + + + StructureConstPtr s = builder->createStructure(); + + reset(); + return s; +} + +NTEnumBuilder::shared_pointer NTEnumBuilder::addDescriptor() +{ + descriptor = true; + return shared_from_this(); +} + +NTEnumBuilder::shared_pointer NTEnumBuilder::addAlarm() +{ + alarm = true; + return shared_from_this(); +} + +NTEnumBuilder::shared_pointer NTEnumBuilder::addTimeStamp() +{ + timeStamp = true; + return shared_from_this(); +} + +PVStructurePtr NTEnumBuilder::createPVStructure() +{ + return getPVDataCreate()->createPVStructure(createStructure()); +} + +NTEnumPtr NTEnumBuilder::create() +{ + return NTEnumPtr(new NTEnum(createPVStructure())); +} + +NTEnumBuilder::NTEnumBuilder() +{ + reset(); +} + +void NTEnumBuilder::reset() +{ + descriptor = false; + alarm = false; + timeStamp = false; + extraFieldNames.clear(); + extraFields.clear(); +} + +NTEnumBuilder::shared_pointer NTEnumBuilder::add(string const & name, FieldConstPtr const & field) +{ + extraFields.push_back(field); extraFieldNames.push_back(name); + return shared_from_this(); +} + + +} + +const std::string NTEnum::URI("epics:nt/NTEnum:1.0"); + +NTEnum::shared_pointer NTEnum::wrap(PVStructurePtr const & structure) +{ + if(!isCompatible(structure)) return shared_pointer(); + return wrapUnsafe(structure); +} + +NTEnum::shared_pointer NTEnum::wrapUnsafe(PVStructurePtr const & structure) +{ + return shared_pointer(new NTEnum(structure)); +} + +bool NTEnum::is_a(StructureConstPtr const & structure) +{ + return NTUtils::is_a(structure->getID(), URI); +} + +bool NTEnum::isCompatible(PVStructurePtr const & pvStructure) +{ + if(!pvStructure) return false; + PVStructurePtr pvValue = pvStructure->getSubField("value"); + if(!pvValue) return false; + if (!ntField->isEnumerated(pvValue->getField())) return false; + + PVFieldPtr pvField = pvStructure->getSubField("descriptor"); + if(pvField && !pvStructure->getSubField("descriptor")) return false; + pvField = pvStructure->getSubField("alarm"); + if(pvField && !ntField->isAlarm(pvField->getField())) return false; + pvField = pvStructure->getSubField("timeStamp"); + if(pvField && !ntField->isTimeStamp(pvField->getField())) return false; + + return true; +} + +NTEnumBuilderPtr NTEnum::createBuilder() +{ + return NTEnumBuilderPtr(new detail::NTEnumBuilder()); +} + +bool NTEnum::attachTimeStamp(PVTimeStamp &pvTimeStamp) const +{ + PVStructurePtr ts = getTimeStamp(); + if (ts) + return pvTimeStamp.attach(ts); + else + return false; +} + +bool NTEnum::attachAlarm(PVAlarm &pvAlarm) const +{ + PVStructurePtr al = getAlarm(); + if (al) + return pvAlarm.attach(al); + else + return false; +} + +PVStructurePtr NTEnum::getPVStructure() const +{ + return pvNTEnum; +} + +PVStringPtr NTEnum::getDescriptor() const +{ + return pvNTEnum->getSubField("descriptor"); +} + +PVStructurePtr NTEnum::getTimeStamp() const +{ + return pvNTEnum->getSubField("timeStamp"); +} + +PVStructurePtr NTEnum::getAlarm() const +{ + return pvNTEnum->getSubField("alarm"); +} + +PVStructurePtr NTEnum::getValue() const +{ + return pvValue; +} + +NTEnum::NTEnum(PVStructurePtr const & pvStructure) : + pvNTEnum(pvStructure), pvValue(pvNTEnum->getSubField("value")) +{} + + +}} diff --git a/src/nt/ntenum.h b/src/nt/ntenum.h new file mode 100644 index 0000000..4e7fad8 --- /dev/null +++ b/src/nt/ntenum.h @@ -0,0 +1,221 @@ +/* ntenum.h */ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * EPICS pvDataCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +#ifndef NTENUM_H +#define NTENUM_H + +#ifdef epicsExportSharedSymbols +# define ntenumEpicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#ifdef ntenumEpicsExportSharedSymbols +# define epicsExportSharedSymbols +# undef ntenumEpicsExportSharedSymbols +#endif + +#include + +#include + +namespace epics { namespace nt { + +class NTEnum; +typedef std::tr1::shared_ptr NTEnumPtr; + +namespace detail { + + /** + * @brief Interface for in-line creating of NTEnum. + * + * One instance can be used to create multiple instances. + * An instance of this object must not be used concurrently (an object has a state). + * @author dgh + */ + class epicsShareClass NTEnumBuilder : + public std::tr1::enable_shared_from_this + { + public: + POINTER_DEFINITIONS(NTEnumBuilder); + + /** + * Add descriptor field to the NTEnum. + * @return this instance of NTEnumBuilder. + */ + shared_pointer addDescriptor(); + + /** + * Add alarm structure to the NTEnum. + * @return this instance of NTEnumBuilder. + */ + shared_pointer addAlarm(); + + /** + * Add timeStamp structure to the NTEnum. + * @return this instance of NTEnumBuilder. + */ + shared_pointer addTimeStamp(); + + /** + * Create a Structure that represents NTEnum. + * This resets this instance state and allows new instance to be created. + * @return a new instance of a Structure. + */ + epics::pvData::StructureConstPtr createStructure(); + + /** + * Create a PVStructure that represents NTEnum. + * This resets this instance state and allows new instance to be created. + * @return a new instance of a PVStructure. + */ + epics::pvData::PVStructurePtr createPVStructure(); + + /** + * Create a NTEnum instance. + * This resets this instance state and allows new instance to be created. + * @return a new instance of a NTEnum. + */ + NTEnumPtr create(); + /** + * Add extra Field to the type. + * @param name name of the field. + * @param field a field to add. + * @return this instance of NTEnumBuilder. + */ + shared_pointer add(std::string const & name, epics::pvData::FieldConstPtr const & field); + + private: + NTEnumBuilder(); + + void reset(); + + bool descriptor; + bool alarm; + bool timeStamp; + + // NOTE: this preserves order, however it does not handle duplicates + epics::pvData::StringArray extraFieldNames; + epics::pvData::FieldConstPtrArray extraFields; + + friend class ::epics::nt::NTEnum; + }; + +} + +typedef std::tr1::shared_ptr NTEnumBuilderPtr; + + + +/** + * @brief Convenience Class for NTEnum + * + * @author dgh + */ +class epicsShareClass NTEnum +{ +public: + POINTER_DEFINITIONS(NTEnum); + + static const std::string URI; + + /** + * Wrap (aka dynamic cast, or wrap) the structure to NTEnum. + * First isCompatible is called. + * This method will nullptr if the structure is is not compatible. + * This method will nullptr if the structure is nullptr. + * @param structure The structure to wrap-ed (dynamic cast, wrapped) to NTEnum. + * @return NTEnum instance on success, nullptr otherwise. + */ + static shared_pointer wrap(epics::pvData::PVStructurePtr const & structure); + + /** + * Wrap (aka dynamic cast, or wrap) the structure to NTMultiChannel without checking for isCompatible + * @param structure The structure to wrap-ed (dynamic cast, wrapped) to NTEnum. + * @return NTEnum instance. + */ + static shared_pointer wrapUnsafe(epics::pvData::PVStructurePtr const & structure); + + /** + * Is the structure an NTEnum. + * @param structure The structure to test. + * @return (false,true) if (is not, is) an NTEnum. + */ + static bool is_a(epics::pvData::StructureConstPtr const & structure); + /** + * Is the pvStructure compatible with NTEnum. + * This method introspects the fields to see if they are compatible. + * @param pvStructure The pvStructure to test. + * @return (false,true) if (is not, is) an NTMultiChannel. + */ + static bool isCompatible( + epics::pvData::PVStructurePtr const &pvStructure); + /** + * Create a NTEnum builder instance. + * @return builder instance. + */ + static NTEnumBuilderPtr createBuilder(); + + /** + * Destructor. + */ + ~NTEnum() {} + + /** + * Attach a pvTimeStamp. + * @param pvTimeStamp The pvTimeStamp that will be attached. + * Does nothing if no timeStamp. + * @return true if the operation was successfull (i.e. this instance has a timeStamp field), otherwise false. + */ + bool attachTimeStamp(epics::pvData::PVTimeStamp &pvTimeStamp) const; + + /** + * Attach an pvAlarm. + * @param pvAlarm The pvAlarm that will be attached. + * Does nothing if no alarm. + * @return true if the operation was successfull (i.e. this instance has a timeStamp field), otherwise false. + */ + bool attachAlarm(epics::pvData::PVAlarm &pvAlarm) const; + + /** + * Get the pvStructure. + * @return PVStructurePtr. + */ + epics::pvData::PVStructurePtr getPVStructure() const; + + /** + * Get the descriptor field. + * @return The pvString or null if no function field. + */ + epics::pvData::PVStringPtr getDescriptor() const; + + /** + * Get the timeStamp. + * @return PVStructurePtr which may be null. + */ + epics::pvData::PVStructurePtr getTimeStamp() const; + + /** + * Get the alarm. + * @return PVStructurePtr which may be null. + */ + epics::pvData::PVStructurePtr getAlarm() const; + + /** + * Get the value field. + * @return The PVStructure for the values. + */ + epics::pvData::PVStructurePtr getValue() const; + +private: + NTEnum(epics::pvData::PVStructurePtr const & pvStructure); + epics::pvData::PVStructurePtr pvNTEnum; + epics::pvData::PVStructurePtr pvValue; + + friend class detail::NTEnumBuilder; +}; + +}} +#endif /* NTENUM_H */ diff --git a/test/nt/Makefile b/test/nt/Makefile index 3334e0e..828e311 100644 --- a/test/nt/Makefile +++ b/test/nt/Makefile @@ -40,6 +40,10 @@ TESTPROD_HOST += ntmatrixTest ntmatrixTest_SRCS = ntmatrixTest.cpp TESTS += ntmatrixTest +TESTPROD_HOST += ntenumTest +ntenumTest_SRCS = ntenumTest.cpp +TESTS += ntenumTest + TESTPROD_HOST += ntutilsTest ntutilsTest_SRCS = ntutilsTest.cpp TESTS += ntutilsTest diff --git a/test/nt/ntenumTest.cpp b/test/nt/ntenumTest.cpp new file mode 100644 index 0000000..2b36205 --- /dev/null +++ b/test/nt/ntenumTest.cpp @@ -0,0 +1,188 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * EPICS pvDataCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#include +#include + +#include + + +using namespace epics::nt; +using namespace epics::pvData; +using std::tr1::dynamic_pointer_cast; + +static FieldCreatePtr fieldCreate = getFieldCreate(); +static StandardFieldPtr standardField = getStandardField(); +static NTFieldPtr ntField = NTField::get(); + +void test_builder() +{ + testDiag("test_builder"); + + NTEnumBuilderPtr builder = NTEnum::createBuilder(); + testOk(builder.get() != 0, "Got builder"); + + StructureConstPtr structure = builder-> + addDescriptor()-> + addAlarm()-> + addTimeStamp()-> + add("valueAlarm",standardField->doubleAlarm()) -> + add("extra",fieldCreate->createScalarArray(pvString)) -> + createStructure(); + testOk1(structure.get() != 0); + if (!structure) + return; + + testOk1(NTEnum::is_a(structure)); + testOk1(structure->getID() == NTEnum::URI); + testOk1(structure->getNumberFields() == 6); + testOk1(structure->getField("value").get() != 0); + testOk1(structure->getField("descriptor").get() != 0); + testOk1(structure->getField("alarm").get() != 0); + testOk1(structure->getField("timeStamp").get() != 0); + + FieldConstPtr valueField = structure->getField("value"); + testOk(valueField.get() != 0 && + ntField->isEnumerated(valueField), "value is enum"); + + std::cout << *structure << std::endl; + +} + +void test_ntenum() +{ + testDiag("test_ntenum"); + + NTEnumBuilderPtr builder = NTEnum::createBuilder(); + testOk(builder.get() != 0, "Got builder"); + + NTEnumPtr ntEnum = builder-> + addDescriptor()-> + addAlarm()-> + addTimeStamp()-> + add("valueAlarm",standardField->intAlarm()) -> + create(); + testOk1(ntEnum.get() != 0); + + testOk1(ntEnum->getPVStructure().get() != 0); + testOk1(ntEnum->getValue().get() != 0); + testOk1(ntEnum->getDescriptor().get() != 0); + testOk1(ntEnum->getAlarm().get() != 0); + testOk1(ntEnum->getTimeStamp().get() != 0); + + // + // example how to set a value + // + PVStructurePtr pvValue = ntEnum->getValue(); + //PVStringArray pvChoices = pvValue->getSubField("choices"); + PVStringArray::svector choices(2); + choices[0] = "Off"; + choices[1] = "On"; + pvValue->getSubField("choices")->replace(freeze(choices)); + pvValue->getSubField("index")->put(1); + + // + // example how to get a value + // + int32 value = ntEnum->getValue()->getSubField("index")->get(); + testOk1(value == 1); + + PVStringArrayPtr pvChoices = ntEnum->getValue()->getSubField("choices"); + std::string choice0 = pvChoices->view()[0]; + std::string choice1 = pvChoices->view()[1]; + testOk1(choice0 == "Off"); + testOk1(choice1 == "On"); + + // + // timeStamp ops + // + PVTimeStamp pvTimeStamp; + if (ntEnum->attachTimeStamp(pvTimeStamp)) + { + testPass("timeStamp attach"); + + // example how to set current time + TimeStamp ts; + ts.getCurrent(); + pvTimeStamp.set(ts); + + // example how to get EPICS time + TimeStamp ts2; + pvTimeStamp.get(ts2); + testOk1(ts2.getEpicsSecondsPastEpoch() != 0); + } + else + testFail("timeStamp attach fail"); + + // + // alarm ops + // + PVAlarm pvAlarm; + if (ntEnum->attachAlarm(pvAlarm)) + { + testPass("alarm attach"); + + // example how to set an alarm + Alarm alarm; + alarm.setStatus(deviceStatus); + alarm.setSeverity(minorAlarm); + alarm.setMessage("simulation alarm"); + pvAlarm.set(alarm); + } + else + testFail("alarm attach fail"); + + // + // set descriptor + // + ntEnum->getDescriptor()->put("This is a test NTEnum"); + + // dump ntEnum + std::cout << *ntEnum->getPVStructure() << std::endl; + +} + +void test_wrap() +{ + testDiag("test_wrap"); + + NTEnumPtr nullPtr = NTEnum::wrap(PVStructurePtr()); + testOk(nullPtr.get() == 0, "nullptr wrap"); + + nullPtr = NTEnum::wrap( + getPVDataCreate()->createPVStructure( + NTField::get()->createTimeStamp() + ) + ); + testOk(nullPtr.get() == 0, "wrong type wrap"); + + + NTEnumBuilderPtr builder = NTEnum::createBuilder(); + testOk(builder.get() != 0, "Got builder"); + + PVStructurePtr pvStructure = builder-> + createPVStructure(); + testOk1(pvStructure.get() != 0); + if (!pvStructure) + return; + + testOk1(NTEnum::isCompatible(pvStructure)==true); + NTEnumPtr ptr = NTEnum::wrap(pvStructure); + testOk(ptr.get() != 0, "wrap OK"); + + ptr = NTEnum::wrapUnsafe(pvStructure); + testOk(ptr.get() != 0, "wrapUnsafe OK"); +} + +MAIN(testNTEnum) { + testPlan(30); + test_builder(); + test_ntenum(); + test_wrap(); + return testDone(); +} + + diff --git a/test/nt/ntunionTest.cpp b/test/nt/ntunionTest.cpp new file mode 100644 index 0000000..8224fe4 --- /dev/null +++ b/test/nt/ntunionTest.cpp @@ -0,0 +1,167 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * EPICS pvDataCPP is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#include +#include + +#include + + +using namespace epics::nt; +using namespace epics::pvData; +using std::tr1::dynamic_pointer_cast; + +static FieldCreatePtr fieldCreate = getFieldCreate(); +static StandardFieldPtr standardField = getStandardField(); +static NTFieldPtr ntField = NTField::get(); + +void test_builder() +{ + testDiag("test_builder"); + + NTUnionBuilderPtr builder = NTUnion::createBuilder(); + testOk(builder.get() != 0, "Got builder"); + + StructureConstPtr structure = builder-> + addDescriptor()-> + addAlarm()-> + addTimeStamp()-> + add("valueAlarm",standardField->doubleAlarm()) -> + add("extra",fieldCreate->createScalarArray(pvString)) -> + createStructure(); + testOk1(structure.get() != 0); + if (!structure) + return; + + testOk1(NTUnion::is_a(structure)); + testOk1(structure->getID() == NTUnion::URI); + testOk1(structure->getNumberFields() == 6); + testOk1(structure->getField("value").get() != 0); + testOk1(structure->getField("descriptor").get() != 0); + testOk1(structure->getField("alarm").get() != 0); + testOk1(structure->getField("timeStamp").get() != 0); + + UnionConstPtr valueField = structure->getField("value"); + testOk(valueField.get() != 0, "value is enum"); + + std::cout << *structure << std::endl; + +} + +void test_ntunion() +{ + testDiag("test_ntunion"); + + NTUnionBuilderPtr builder = NTUnion::createBuilder(); + testOk(builder.get() != 0, "Got builder"); + + NTUnionPtr ntUnion = builder-> + addDescriptor()-> + addAlarm()-> + addTimeStamp()-> + create(); + testOk1(ntUnion.get() != 0); + + testOk1(ntUnion->getPVStructure().get() != 0); + testOk1(ntUnion->getValue().get() != 0); + testOk1(ntUnion->getDescriptor().get() != 0); + testOk1(ntUnion->getAlarm().get() != 0); + testOk1(ntUnion->getTimeStamp().get() != 0); + + // TODO + // 1. Variant union example. + // 2. set the union value. + + // + // timeStamp ops + // + PVTimeStamp pvTimeStamp; + if (ntUnion->attachTimeStamp(pvTimeStamp)) + { + testPass("timeStamp attach"); + + // example how to set current time + TimeStamp ts; + ts.getCurrent(); + pvTimeStamp.set(ts); + + // example how to get EPICS time + TimeStamp ts2; + pvTimeStamp.get(ts2); + testOk1(ts2.getEpicsSecondsPastEpoch() != 0); + } + else + testFail("timeStamp attach fail"); + + // + // alarm ops + // + PVAlarm pvAlarm; + if (ntUnion->attachAlarm(pvAlarm)) + { + testPass("alarm attach"); + + // example how to set an alarm + Alarm alarm; + alarm.setStatus(deviceStatus); + alarm.setSeverity(minorAlarm); + alarm.setMessage("simulation alarm"); + pvAlarm.set(alarm); + } + else + testFail("alarm attach fail"); + + // + // set descriptor + // + ntUnion->getDescriptor()->put("This is a test NTUnion"); + + // dump ntUnion + std::cout << *ntUnion->getPVStructure() << std::endl; + +} + +void test_wrap() +{ + testDiag("test_wrap"); + + NTUnionPtr nullPtr = NTUnion::wrap(PVStructurePtr()); + testOk(nullPtr.get() == 0, "nullptr wrap"); + + nullPtr = NTUnion::wrap( + getPVDataCreate()->createPVStructure( + NTField::get()->createTimeStamp() + ) + ); + testOk(nullPtr.get() == 0, "wrong type wrap"); + + + NTUnionBuilderPtr builder = NTUnion::createBuilder(); + testOk(builder.get() != 0, "Got builder"); + + PVStructurePtr pvStructure = builder-> + createPVStructure(); + testOk1(pvStructure.get() != 0); + if (!pvStructure) + return; + + testOk1(NTUnion::isCompatible(pvStructure)==true); + NTUnionPtr ptr = NTUnion::wrap(pvStructure); + testOk(ptr.get() != 0, "wrap OK"); + + ptr = NTUnion::wrapUnsafe(pvStructure); + testOk(ptr.get() != 0, "wrapUnsafe OK"); +} + +MAIN(testNTUnion) { + testPlan(27); + test_builder(); + test_ntunion(); + test_wrap(); + return testDone(); +} + +