diff --git a/ADApp/Makefile b/ADApp/Makefile index 0b2f54e2b..32f440b0a 100644 --- a/ADApp/Makefile +++ b/ADApp/Makefile @@ -12,7 +12,8 @@ pluginSrc_DEPEND_DIRS += ADSrc DIRS += pluginTests pluginTests_DEPEND_DIRS += pluginSrc -ifeq ($(WITH_PVA), YES) +# if WITH_PVA or WITH_PVA = YES +ifeq ($(findstring YES,$(WITH_PVA) $(WITH_PVXS)), YES) DIRS += ntndArrayConverterSrc ntndArrayConverterSrc_DEPEND_DIRS += ADSrc pluginSrc_DEPEND_DIRS += ntndArrayConverterSrc diff --git a/ADApp/commonDriverMakefile b/ADApp/commonDriverMakefile index 1347f01e5..872aa2edd 100644 --- a/ADApp/commonDriverMakefile +++ b/ADApp/commonDriverMakefile @@ -37,6 +37,12 @@ ifeq ($(WITH_PVA),YES) endif endif +ifeq ($(WITH_PVXS),YES) + $(DBD_NAME)_DBD += NDPluginPvxs.dbd + PROD_LIBS += pvxs + PROD_LIBS += ntndArrayConverterPvxs +endif + ifeq ($(WITH_NETCDF),YES) $(DBD_NAME)_DBD += NDFileNetCDF.dbd ifeq ($(NETCDF_EXTERNAL),NO) diff --git a/ADApp/commonLibraryMakefile b/ADApp/commonLibraryMakefile index a159db671..99b99a74c 100644 --- a/ADApp/commonLibraryMakefile +++ b/ADApp/commonLibraryMakefile @@ -11,6 +11,12 @@ ifeq ($(WITH_PVA),YES) LIB_LIBS += pvData endif +ifeq ($(WITH_PVXS),YES) + LIB_LIBS += ntndArrayConverterPvxs + LIB_LIBS += pvxs + LIB_LIBS += pvxsIoc +endif + ifeq ($(WITH_NETCDF),YES) ifeq ($(NETCDF_EXTERNAL),NO) LIB_LIBS += netCDF diff --git a/ADApp/ntndArrayConverterSrc/Makefile b/ADApp/ntndArrayConverterSrc/Makefile index 662520807..07695450d 100644 --- a/ADApp/ntndArrayConverterSrc/Makefile +++ b/ADApp/ntndArrayConverterSrc/Makefile @@ -4,19 +4,26 @@ include $(TOP)/configure/CONFIG # ADD MACRO DEFINITIONS AFTER THIS LINE #============================= -LIBRARY_IOC += ntndArrayConverter -INC += ntndArrayConverterAPI.h -INC += ntndArrayConverter.h -LIB_SRCS += ntndArrayConverter.cpp +ifeq ($(WITH_PVA), YES) + LIBRARY_IOC += ntndArrayConverter + INC += ntndArrayConverter.h + LIB_SRCS += ntndArrayConverter.cpp + LIB_LIBS += pvData + LIB_LIBS += nt +endif -USR_CPPFLAGS += -DBUILDING_ntndArrayConverter_API +ifeq ($(WITH_PVXS), YES) + LIBRARY_IOC += ntndArrayConverterPvxs + INC += ntndArrayConverterPvxs.h + LIB_SRCS += ntndArrayConverterPvxs.cpp + LIB_LIBS += pvxs +endif +INC += ntndArrayConverterAPI.h +USR_CPPFLAGS += -DBUILDING_ntndArrayConverter_API LIB_LIBS += ADBase -LIB_LIBS += pvData -LIB_LIBS += nt LIB_LIBS += asyn LIB_LIBS += $(EPICS_BASE_IOC_LIBS) - #============================= include $(TOP)/configure/RULES diff --git a/ADApp/ntndArrayConverterSrc/ntndArrayConverterPvxs.cpp b/ADApp/ntndArrayConverterSrc/ntndArrayConverterPvxs.cpp new file mode 100644 index 000000000..37a33921a --- /dev/null +++ b/ADApp/ntndArrayConverterSrc/ntndArrayConverterPvxs.cpp @@ -0,0 +1,452 @@ +#include "ntndArrayConverterPvxs.h" +#include <stdio.h> +#include <string.h> +#include <iostream> +using namespace std; + +NTNDArrayConverterPvxs::NTNDArrayConverterPvxs (pvxs::Value value) : m_value(value) { + m_typeMap = { + {typeid(int8_t), NDAttrDataType_t::NDAttrInt8}, + {typeid(uint8_t), NDAttrDataType_t::NDAttrUInt8}, + {typeid(int16_t), NDAttrDataType_t::NDAttrInt16}, + {typeid(uint16_t), NDAttrDataType_t::NDAttrUInt16}, + {typeid(int32_t), NDAttrDataType_t::NDAttrInt32}, + {typeid(uint32_t), NDAttrDataType_t::NDAttrUInt32}, + {typeid(int64_t), NDAttrDataType_t::NDAttrInt64}, + {typeid(uint64_t), NDAttrDataType_t::NDAttrUInt64}, + {typeid(float_t), NDAttrDataType_t::NDAttrFloat32}, + {typeid(double_t), NDAttrDataType_t::NDAttrFloat64} + }; + + m_fieldNameMap = { + {typeid(int8_t), "value->byteValue"}, + {typeid(uint8_t), "value->ubyteValue"}, + {typeid(int16_t), "value->shortValue"}, + {typeid(uint16_t), "value->ushortValue"}, + {typeid(int32_t), "value->intValue"}, + {typeid(uint32_t), "value->uintValue"}, + {typeid(int64_t), "value->longValue"}, + {typeid(uint64_t), "value->ulongValue"}, + {typeid(float_t), "value->floatValue"}, + {typeid(double_t), "value->doubleValue"} + }; +} + +NDColorMode_t NTNDArrayConverterPvxs::getColorMode (void) +{ + auto attributes = m_value["attribute"].as<pvxs::shared_array<const pvxs::Value>>(); + NDColorMode_t colorMode = NDColorMode_t::NDColorModeMono; + for (auto &attribute : attributes) { + if (attribute["name"].as<std::string>() == "ColorMode") { + colorMode = (NDColorMode_t) attribute["value"].as<int32_t>(); + break; + } + } + return colorMode; +} + +NTNDArrayInfo_t NTNDArrayConverterPvxs::getInfo (void) +{ + NTNDArrayInfo_t info = {0}; + + auto dims = m_value["dimension"].as<pvxs::shared_array<const pvxs::Value>>(); + info.ndims = (int) dims.size(); + info.nElements = 1; + + for(int i = 0; i < info.ndims; ++i) + { + info.dims[i] = dims[i]["size"].as<size_t>(); + info.nElements *= info.dims[i]; + } + + // does not update info.codec if codec.name not found in Value + m_value["codec.name"].as<std::string>(info.codec); + + NDDataType_t dt; + int bpe; + + if (info.codec.empty()) { + switch (m_value["value->"].type().code) { + case pvxs::TypeCode::Int8A: {dt = NDInt8; break;} + case pvxs::TypeCode::UInt8A: {dt = NDUInt8; break;} + case pvxs::TypeCode::Int16A: {dt = NDInt16; break;} + case pvxs::TypeCode::UInt16A: {dt = NDUInt16; break;} + case pvxs::TypeCode::Int32A: {dt = NDInt32; break;} + case pvxs::TypeCode::UInt32A: {dt = NDUInt32; break;} + case pvxs::TypeCode::Int64A: {dt = NDInt64; break;} + case pvxs::TypeCode::UInt64A: {dt = NDUInt64; break;} + case pvxs::TypeCode::Float32A: {dt = NDFloat32; break;} + case pvxs::TypeCode::Float64A: {dt = NDFloat64; break;} + default: throw std::runtime_error("invalid value data type"); + } + } else dt = (NDDataType_t) m_value["codec.parameters"].as<int32_t>(); + switch (dt) { + case NDInt8: {bpe = sizeof(int8_t); break;} + case NDUInt8: {bpe = sizeof(uint8_t); break;} + case NDInt16: {bpe = sizeof(int16_t); break;} + case NDUInt16: {bpe = sizeof(uint16_t); break;} + case NDInt32: {bpe = sizeof(int32_t); break;} + case NDUInt32: {bpe = sizeof(uint32_t); break;} + case NDInt64: {bpe = sizeof(int64_t); break;} + case NDUInt64: {bpe = sizeof(uint64_t); break;} + case NDFloat32: {bpe = sizeof(float_t); break;} + case NDFloat64: {bpe = sizeof(double_t); break;} + default: throw std::runtime_error("Could not determine element size."); + } + + info.dataType = dt; + info.bytesPerElement = bpe; + info.totalBytes = info.nElements*info.bytesPerElement; + info.colorMode = getColorMode(); + + if(info.ndims > 0) + { + info.x.dim = 0; + info.x.stride = 1; + info.x.size = info.dims[0]; + } + + if(info.ndims > 1) + { + info.y.dim = 1; + info.y.stride = 1; + info.y.size = info.dims[1]; + } + + if(info.ndims == 3) + { + switch(info.colorMode) + { + case NDColorModeRGB1: + info.x.dim = 1; + info.y.dim = 2; + info.color.dim = 0; + info.x.stride = info.dims[0]; + info.y.stride = info.dims[0]*info.dims[1]; + info.color.stride = 1; + break; + + case NDColorModeRGB2: + info.x.dim = 0; + info.y.dim = 2; + info.color.dim = 1; + info.x.stride = 1; + info.y.stride = info.dims[0]*info.dims[1]; + info.color.stride = info.dims[0]; + break; + + case NDColorModeRGB3: + info.x.dim = 1; + info.y.dim = 2; + info.color.dim = 0; + info.x.stride = info.dims[0]; + info.y.stride = info.dims[0]*info.dims[1]; + info.color.stride = 1; + break; + + default: + info.x.dim = 0; + info.y.dim = 1; + info.color.dim = 2; + info.x.stride = 1; + info.y.stride = info.dims[0]; + info.color.stride = info.dims[0]*info.dims[1]; + break; + } + + info.x.size = info.dims[info.x.dim]; + info.y.size = info.dims[info.y.dim]; + info.color.size = info.dims[info.color.dim]; + } + + return info; +} + +void NTNDArrayConverterPvxs::toArray (NDArray *dest) +{ + toValue(dest); + toDimensions(dest); + toTimeStamp(dest); + toDataTimeStamp(dest); + toAttributes(dest); + + dest->uniqueId = m_value["uniqueId"].as<int32_t>(); +} + +void NTNDArrayConverterPvxs::fromArray (NDArray *src) +{ + fromValue(src); + fromDimensions(src); + fromTimeStamp(src); + fromDataTimeStamp(src); + fromAttributes(src); + + m_value["uniqueId"] = src->uniqueId; +} + +template <typename arrayType> +void NTNDArrayConverterPvxs::toValue (NDArray *dest) +{ + NTNDArrayInfo_t info = getInfo(); + dest->codec.name = info.codec; + dest->dataType = info.dataType; + + + std::string fieldName = m_fieldNameMap[typeid(arrayType)]; + + auto value = m_value[fieldName].as<pvxs::shared_array<const arrayType>>(); + memcpy(dest->pData, value.data(), info.totalBytes); + + if (!info.codec.empty()) + dest->compressedSize = info.totalBytes; +} + +void NTNDArrayConverterPvxs::toValue (NDArray *dest) +{ + switch (m_value["value->"].type().code) { + case pvxs::TypeCode::Int8A: {toValue<int8_t>(dest); break;} + case pvxs::TypeCode::UInt8A: {toValue<uint8_t>(dest); break;} + case pvxs::TypeCode::Int16A: {toValue<int16_t>(dest); break;} + case pvxs::TypeCode::UInt16A: {toValue<uint16_t>(dest); break;} + case pvxs::TypeCode::Int32A: {toValue<int32_t>(dest); break;} + case pvxs::TypeCode::UInt32A: {toValue<uint32_t>(dest); break;} + case pvxs::TypeCode::Int64A: {toValue<int64_t>(dest); break;} + case pvxs::TypeCode::UInt64A: {toValue<uint64_t>(dest); break;} + case pvxs::TypeCode::Float32A: {toValue<float_t>(dest); break;} + case pvxs::TypeCode::Float64A: {toValue<double_t>(dest); break;} + default: throw std::runtime_error("invalid value data type"); + } +} + +void NTNDArrayConverterPvxs::toDimensions (NDArray *dest) +{ + auto dims = m_value["dimension"].as<pvxs::shared_array<const pvxs::Value>>(); + dest->ndims = (int)dims.size(); + + for(int i = 0; i < dest->ndims; ++i) + { + NDDimension_t *d = &dest->dims[i]; + d->size = dims[i]["size"].as<int32_t>(); + d->offset = dims[i]["offset"].as<int32_t>(); + d->binning = dims[i]["binning"].as<int32_t>(); + d->reverse = dims[i]["reverse"].as<bool>(); + } +} + +void NTNDArrayConverterPvxs::toTimeStamp (NDArray *dest) +{ + // NDArray uses EPICS time, pvAccess uses Posix time, need to convert + dest->epicsTS.secPastEpoch = (epicsUInt32) + m_value["timeStamp.secondsPastEpoch"].as<uint32_t>() - POSIX_TIME_AT_EPICS_EPOCH; + dest->epicsTS.nsec = (epicsUInt32) + m_value["timeStamp.nanoseconds"].as<uint32_t>(); +} + +void NTNDArrayConverterPvxs::toDataTimeStamp (NDArray *dest) +{ + // NDArray uses EPICS time, pvAccess uses Posix time, need to convert + dest->timeStamp = (epicsFloat64) + (m_value["dataTimeStamp.nanoseconds"].as<double_t>() / 1e9) + + m_value["dataTimeStamp.secondsPastEpoch"].as<uint32_t>() + - POSIX_TIME_AT_EPICS_EPOCH; +} + +template <typename valueType> +void NTNDArrayConverterPvxs::toAttribute (NDArray *dest, pvxs::Value attribute) +{ + auto name = attribute["name"].as<std::string>(); + auto desc = attribute["descriptor"].as<std::string>(); + auto source = attribute["source"].as<std::string>(); + NDAttrSource_t sourceType = (NDAttrSource_t) attribute["sourceType"].as<int32_t>(); + valueType value = attribute["value"].as<valueType>(); + NDAttrDataType_t dataType = m_typeMap[typeid(valueType)]; + + NDAttribute *attr = new NDAttribute(name.c_str(), desc.c_str(), sourceType, source.c_str(), dataType, (void*)&value); + dest->pAttributeList->add(attr); +} + +void NTNDArrayConverterPvxs::toStringAttribute (NDArray *dest, pvxs::Value attribute) +{ + auto name = attribute["name"].as<std::string>(); + auto desc = attribute["descriptor"].as<std::string>(); + auto source = attribute["source"].as<std::string>(); + NDAttrSource_t sourceType = (NDAttrSource_t) attribute["sourceType"].as<int32_t>(); + auto value = attribute["value"].as<std::string>(); + + NDAttribute *attr = new NDAttribute(name.c_str(), desc.c_str(), sourceType, source.c_str(), NDAttrDataType_t::NDAttrString, (void*)value.c_str()); + dest->pAttributeList->add(attr); +} + +void NTNDArrayConverterPvxs::toUndefinedAttribute (NDArray *dest, pvxs::Value attribute) +{ + auto name = attribute["name"].as<std::string>(); + auto desc = attribute["descriptor"].as<std::string>(); + auto source = attribute["source"].as<std::string>(); + NDAttrSource_t sourceType = (NDAttrSource_t) attribute["sourceType"].as<int32_t>(); + + NDAttribute *attr = new NDAttribute(name.c_str(), desc.c_str(), sourceType, source.c_str(), NDAttrDataType_t::NDAttrUndefined, NULL); + dest->pAttributeList->add(attr); +} + +void NTNDArrayConverterPvxs::toAttributes (NDArray *dest) +{ + auto attributes = m_value["attribute"].as<pvxs::shared_array<const pvxs::Value>>(); + for (size_t i=0; i < attributes.size(); i++) { + pvxs::Value value = attributes[i]["value"]; + switch (attributes[i]["value->"].type().code) { + // use indirection on Any container to get specified type + case pvxs::TypeCode::Int8: toAttribute<int8_t> (dest, attributes[i]); break; + case pvxs::TypeCode::UInt8: toAttribute<uint8_t> (dest, attributes[i]); break; + case pvxs::TypeCode::Int16: toAttribute<int16_t> (dest, attributes[i]); break; + case pvxs::TypeCode::UInt16: toAttribute<uint16_t> (dest, attributes[i]); break; + case pvxs::TypeCode::Int32: toAttribute<int32_t> (dest, attributes[i]); break; + case pvxs::TypeCode::UInt32: toAttribute<uint32_t> (dest, attributes[i]); break; + case pvxs::TypeCode::Int64: toAttribute<int64_t> (dest, attributes[i]); break; + case pvxs::TypeCode::UInt64: toAttribute<uint64_t> (dest, attributes[i]); break; + case pvxs::TypeCode::Float32: toAttribute<float_t> (dest, attributes[i]); break; + case pvxs::TypeCode::Float64: toAttribute<double_t> (dest, attributes[i]); break; + case pvxs::TypeCode::String: toStringAttribute (dest, attributes[i]); break; + case pvxs::TypeCode::Null: toUndefinedAttribute (dest, attributes[i]); break; + default: throw std::runtime_error("invalid value data type"); + } + } +} + +template <typename dataType> +struct freeNDArray { + NDArray *array; + // increase reference count of array, release on destructor + freeNDArray(NDArray *array) : array(array) { array->reserve(); } + void operator()(dataType *data) { + assert (array->pData == data); + array->release(); + } +}; + +template <typename dataType> +void NTNDArrayConverterPvxs::fromValue(NDArray *src) { + NDArrayInfo_t arrayInfo; + src->getInfo(&arrayInfo); + + m_value["compressedSize"] = src->compressedSize; + m_value["uncompressedSize"] = arrayInfo.totalBytes; + + std::string fieldName = m_fieldNameMap[typeid(dataType)]; + auto val = pvxs::shared_array<dataType>( + (dataType*)src->pData, + // custom deletor + freeNDArray<dataType>(src), + arrayInfo.nElements); + m_value[fieldName] = val.freeze(); + + m_value["codec.name"] = src->codec.name; // compression codec + // The uncompressed data type would be lost when converting to NTNDArray, + // so we must store it somewhere. codec.parameters seems like a good place. + m_value["codec.parameters"] = (int32_t) src->dataType; +} + +void NTNDArrayConverterPvxs::fromValue (NDArray *src) { + switch(src->dataType) { + case NDInt8: {fromValue<int8_t>(src); break;}; + case NDUInt8: {fromValue<uint8_t>(src); break;}; + case NDInt16: {fromValue<int16_t>(src); break;}; + case NDUInt16: {fromValue<uint16_t>(src); break;}; + case NDInt32: {fromValue<int32_t>(src); break;}; + case NDUInt32: {fromValue<uint32_t>(src); break;}; + case NDInt64: {fromValue<int64_t>(src); break;}; + case NDUInt64: {fromValue<uint64_t>(src); break;}; + case NDFloat32: {fromValue<float_t>(src); break;}; + case NDFloat64: {fromValue<double_t>(src); break;}; + default: { + throw std::runtime_error("invalid value data type"); + break; + } + } +} + +void NTNDArrayConverterPvxs::fromDimensions (NDArray *src) { + pvxs::shared_array<pvxs::Value> dims; + dims.resize(src->ndims); + + for (int i = 0; i < src->ndims; i++) { + dims[i] = m_value["dimension"].allocMember() + .update("size", src->dims[i].size) + .update("offset", src->dims[i].offset) + .update("fullSize", src->dims[i].size) + .update("binning", src->dims[i].binning) + .update("reverse", src->dims[i].reverse); + } + m_value["dimension"] = dims.freeze(); +} + +void NTNDArrayConverterPvxs::fromDataTimeStamp (NDArray *src) { + double seconds = floor(src->timeStamp); + double nanoseconds = (src->timeStamp - seconds)*1e9; + // pvAccess uses Posix time, NDArray uses EPICS time, need to convert + seconds += POSIX_TIME_AT_EPICS_EPOCH; + m_value["dataTimeStamp.secondsPastEpoch"] = seconds; + m_value["dataTimeStamp.nanoseconds"] = nanoseconds; +} + +void NTNDArrayConverterPvxs::fromTimeStamp (NDArray *src) { + // pvAccess uses Posix time, NDArray uses EPICS time, need to convert + m_value["timeStamp.secondsPastEpoch"] = src->epicsTS.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH; + m_value["timeStamp.nanoseconds"] = src->epicsTS.nsec; +} + +template <typename valueType> +void NTNDArrayConverterPvxs::fromAttribute (pvxs::Value destValue, NDAttribute *src) +{ + valueType value; + src->getValue(src->getDataType(), (void*)&value); + destValue["value"] = value; +} + +void NTNDArrayConverterPvxs::fromStringAttribute (pvxs::Value destValue, NDAttribute *src) +{ + const char *value; + src->getValue(src->getDataType(), (void*)&value); + destValue["value"] = std::string(value); +} + +void NTNDArrayConverterPvxs::fromAttributes (NDArray *src) +{ + NDAttributeList *srcList = src->pAttributeList; + NDAttribute *attr = NULL; + size_t i = 0; + pvxs::shared_array<pvxs::Value> attrs; + attrs.resize(src->pAttributeList->count()); + while((attr = srcList->next(attr))) + { + NDAttrSource_t sourceType; + attr->getSourceInfo(&sourceType); + attrs[i] = m_value["attribute"].allocMember() + .update("name", attr->getName()) + .update("descriptor", attr->getDescription()) + .update("source", attr->getSource()) + .update("sourceType", sourceType); + + switch(attr->getDataType()) + { + case NDAttrInt8: fromAttribute<int8_t>(attrs[i], attr); break; + case NDAttrUInt8: fromAttribute<uint8_t>(attrs[i], attr); break; + case NDAttrInt16: fromAttribute<int16_t>(attrs[i], attr); break; + case NDAttrUInt16: fromAttribute<uint16_t>(attrs[i], attr); break; + case NDAttrInt32: fromAttribute<int32_t>(attrs[i], attr); break; + case NDAttrUInt32: fromAttribute<uint32_t>(attrs[i], attr); break; + case NDAttrInt64: fromAttribute<int64_t>(attrs[i], attr); break; + case NDAttrUInt64: fromAttribute<uint64_t>(attrs[i], attr); break; + case NDAttrFloat32: fromAttribute<float>(attrs[i], attr); break; + case NDAttrFloat64: fromAttribute<double>(attrs[i], attr); break; + case NDAttrString: fromStringAttribute(attrs[i], attr); break; + case NDAttrUndefined: break; // No need to assign value, leave as undefined + default: throw std::runtime_error("invalid attribute data type"); + } + ++i; + } + m_value["attribute"] = attrs.freeze(); +} + + + + diff --git a/ADApp/ntndArrayConverterSrc/ntndArrayConverterPvxs.h b/ADApp/ntndArrayConverterSrc/ntndArrayConverterPvxs.h new file mode 100644 index 000000000..a69dded5a --- /dev/null +++ b/ADApp/ntndArrayConverterSrc/ntndArrayConverterPvxs.h @@ -0,0 +1,69 @@ +#include <math.h> + +#include <ntndArrayConverterAPI.h> +#include <NDArray.h> +#include <pvxs/data.h> +#include <typeindex> +#include <typeinfo> +#include <unordered_map> + +typedef struct NTNDArrayInfo +{ + int ndims; + size_t dims[ND_ARRAY_MAX_DIMS]; + size_t nElements, totalBytes; + int bytesPerElement; + NDColorMode_t colorMode; + NDDataType_t dataType; + std::string codec; + + struct + { + int dim; + size_t size, stride; + }x, y, color; +}NTNDArrayInfo_t; + +class NTNDARRAYCONVERTER_API NTNDArrayConverterPvxs +{ +public: + NTNDArrayConverterPvxs(pvxs::Value value); + NTNDArrayInfo_t getInfo (void); + void toArray (NDArray *dest); + void fromArray (NDArray *src); + +private: + pvxs::Value m_value; + std::unordered_map<std::type_index, NDAttrDataType_t> m_typeMap; + std::unordered_map<std::type_index, std::string> m_fieldNameMap; + NDColorMode_t getColorMode (void); + + template <typename arrayType> + void toValue (NDArray *dest); + void toValue (NDArray *dest); + + void toDimensions (NDArray *dest); + void toTimeStamp (NDArray *dest); + void toDataTimeStamp (NDArray *dest); + + template <typename valueType> + void toAttribute (NDArray *dest, pvxs::Value attribute); + void toStringAttribute (NDArray *dest, pvxs::Value attribute); + void toUndefinedAttribute (NDArray *dest, pvxs::Value attribute); + void toAttributes (NDArray *dest); + + template <typename arrayType> + void fromValue (NDArray *src); + void fromValue (NDArray *src); + + void fromDimensions (NDArray *src); + void fromTimeStamp (NDArray *src); + void fromDataTimeStamp (NDArray *src); + + template <typename valueType> + void fromAttribute (pvxs::Value destValue, NDAttribute *src); + void fromStringAttribute (pvxs::Value destValue, NDAttribute *src); + void fromAttributes (NDArray *src); +}; + +typedef std::shared_ptr<NTNDArrayConverterPvxs> NTNDArrayConverterPvxsPtr; diff --git a/ADApp/pluginSrc/Makefile b/ADApp/pluginSrc/Makefile index a6d95710e..a00a81d00 100644 --- a/ADApp/pluginSrc/Makefile +++ b/ADApp/pluginSrc/Makefile @@ -196,6 +196,12 @@ ifeq ($(WITH_PVA), YES) LIB_SRCS += NDPluginPva.cpp endif +ifeq ($(WITH_PVXS), YES) + DBD += NDPluginPvxs.dbd + INC += NDPluginPvxs.h + LIB_SRCS += NDPluginPvxs.cpp +endif + ifeq ($(WITH_BLOSC), YES) USR_CXXFLAGS += -DHAVE_BLOSC endif diff --git a/ADApp/pluginSrc/NDPluginPvxs.cpp b/ADApp/pluginSrc/NDPluginPvxs.cpp new file mode 100644 index 000000000..36b3f3bc8 --- /dev/null +++ b/ADApp/pluginSrc/NDPluginPvxs.cpp @@ -0,0 +1,195 @@ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <iostream> + +#include <pvxs/server.h> +#include <pvxs/sharedpv.h> +#include <pvxs/nt.h> +#include <pvxs/iochooks.h> + +#include <iocsh.h> + +#include <ntndArrayConverterPvxs.h> + +#include "NDPluginPvxs.h" + +#include <epicsExport.h> + +static const char *driverName="NDPluginPvxs"; + +using namespace std; + +class NDPLUGIN_API NTNDArrayRecordPvxs { + +private: + NTNDArrayRecordPvxs(string const & name, pvxs::Value value) : m_name(name), m_value(value) {}; + NTNDArrayConverterPvxsPtr m_converter; + string m_name; + pvxs::Value m_value; + pvxs::server::SharedPV m_pv; + +public: + virtual ~NTNDArrayRecordPvxs () {}; + static NTNDArrayRecordPvxsPtr create (string const & name); + virtual bool init (); + virtual void process () {} + void update (NDArray *pArray); +}; + +NTNDArrayRecordPvxsPtr NTNDArrayRecordPvxs::create (string const & name) +{ + pvxs::Value value = pvxs::nt::NTNDArray{}.build().create(); + NTNDArrayRecordPvxsPtr pvRecord(new NTNDArrayRecordPvxs(name, value)); + + if(!pvRecord->init()) + pvRecord.reset(); + + return pvRecord; +} + +bool NTNDArrayRecordPvxs::init () +{ + m_pv = pvxs::server::SharedPV(pvxs::server::SharedPV::buildReadonly()); + m_pv.open(m_value); + // use singleton pvxs server + pvxs::ioc::server().addPV(m_name, m_pv); + m_converter.reset(new NTNDArrayConverterPvxs(m_value)); + return true; +} + +void NTNDArrayRecordPvxs::update(NDArray *pArray) +{ + m_converter->fromArray(pArray); + m_pv.post(m_value); +} + +/** Callback function that is called by the NDArray driver with new NDArray + * data. + * \param[in] pArray The NDArray from the callback. + */ +void NDPluginPvxs::processCallbacks(NDArray *pArray) +{ + static const char *functionName = "processCallbacks"; + + NDPluginDriver::beginProcessCallbacks(pArray); // Base class method + + // Most plugins can rely on endProcessCallbacks() to check for throttling, but this one cannot + // because the output is not an NDArray but a pvAccess server. Need to check here. + if (throttled(pArray)) { + int droppedOutputArrays; + int arrayCounter; + getIntegerParam(NDPluginDriverDroppedOutputArrays, &droppedOutputArrays); + asynPrint(pasynUserSelf, ASYN_TRACE_WARNING, + "%s::%s maximum byte rate exceeded, dropped array uniqueId=%d\n", + driverName, functionName, pArray->uniqueId); + droppedOutputArrays++; + setIntegerParam(NDPluginDriverDroppedOutputArrays, droppedOutputArrays); + // Since this plugin has done no useful work we also decrement ArrayCounter + getIntegerParam(NDArrayCounter, &arrayCounter); + arrayCounter--; + setIntegerParam(NDArrayCounter, arrayCounter); + } + m_record->update(pArray); + + // Do NDArray callbacks. We need to copy the array and get the attributes + NDPluginDriver::endProcessCallbacks(pArray, true, true); + + callParamCallbacks(); +} + +/** Constructor for NDPluginPvxs + * This plugin cannot block (ASYN_CANBLOCK=0) and is not multi-device (ASYN_MULTIDEVICE=0). + * \param[in] portName The name of the asyn port driver to be created. + * \param[in] queueSize The number of NDArrays that the input queue for this + * plugin can hold when NDPluginDriverBlockingCallbacks=0. + * Larger queues can decrease the number of dropped arrays, at the + * expense of more NDArray buffers being allocated from the + * underlying driver's NDArrayPool. + * \param[in] blockingCallbacks Initial setting for the + * NDPluginDriverBlockingCallbacks flag. 0=callbacks are queued and + * executed by the callback thread; 1 callbacks execute in the + * thread of the driver doing the callbacks. + * \param[in] NDArrayPort Name of asyn port driver for initial source of + * NDArray callbacks. + * \param[in] NDArrayAddr asyn port driver address for initial source of + * NDArray callbacks. + * \param[in] pvName Name of the PV that will be served by the EPICSv4 server. + * \param[in] maxBuffers The maximum number of NDArray buffers that the NDArrayPool for this driver is + * allowed to allocate. Set this to 0 to allow an unlimited number of buffers. + * \param[in] maxMemory The maximum amount of memory that the NDArrayPool for this driver is + * allowed to allocate. Set this to 0 to allow an unlimited amount of memory. + * \param[in] priority The thread priority for the asyn port driver thread if ASYN_CANBLOCK is set in asynFlags. + * This value should also be used for any other threads this object creates. + * \param[in] stackSize The stack size for the asyn port driver thread if ASYN_CANBLOCK is set in asynFlags. + * This value should also be used for any other threads this object creates. + */ +NDPluginPvxs::NDPluginPvxs(const char *portName, int queueSize, + int blockingCallbacks, const char *NDArrayPort, int NDArrayAddr, + const char *pvName, int maxBuffers, size_t maxMemory, int priority, int stackSize) + /* Invoke the base class constructor */ + : NDPluginDriver(portName, queueSize, blockingCallbacks, + NDArrayPort, NDArrayAddr, 1, maxBuffers, maxMemory, 0, 0, + 0, 1, priority, stackSize, 1, true), + m_record(NTNDArrayRecordPvxs::create(pvName)) +{ + createParam(NDPluginPvxsPvNameString, asynParamOctet, &NDPluginPvxsPvName); + + /* Set the plugin type string */ + setStringParam(NDPluginDriverPluginType, "NDPluginPvxs"); + + /* Set PvName */ + setStringParam(NDPluginPvxsPvName, pvName); + + /* Try to connect to the NDArray port */ + connectToArrayPort(); +} + +/* Configuration routine. Called directly, or from the iocsh function */ +extern "C" int NDPvxsConfigure(const char *portName, int queueSize, + int blockingCallbacks, const char *NDArrayPort, int NDArrayAddr, + const char *pvName, int maxBuffers, size_t maxMemory, int priority, int stackSize) +{ + NDPluginPvxs *pPlugin = new NDPluginPvxs(portName, queueSize, blockingCallbacks, NDArrayPort, + NDArrayAddr, pvName, maxBuffers, maxMemory, priority, stackSize); + return pPlugin->start(); +} + +/* EPICS iocsh shell commands */ +static const iocshArg initArg0 = { "portName",iocshArgString}; +static const iocshArg initArg1 = { "frame queue size",iocshArgInt}; +static const iocshArg initArg2 = { "blocking callbacks",iocshArgInt}; +static const iocshArg initArg3 = { "NDArrayPort",iocshArgString}; +static const iocshArg initArg4 = { "NDArrayAddr",iocshArgInt}; +static const iocshArg initArg5 = { "pvName",iocshArgString}; +static const iocshArg initArg6 = { "maxBuffers",iocshArgInt}; +static const iocshArg initArg7 = { "maxMemory",iocshArgInt}; +static const iocshArg initArg8 = { "priority",iocshArgInt}; +static const iocshArg initArg9 = { "stack size",iocshArgInt}; +static const iocshArg * const initArgs[] = {&initArg0, + &initArg1, + &initArg2, + &initArg3, + &initArg4, + &initArg5, + &initArg6, + &initArg7, + &initArg8, + &initArg9,}; +static const iocshFuncDef initFuncDef = {"NDPvxsConfigure",10,initArgs}; +static void initCallFunc(const iocshArgBuf *args) +{ + NDPvxsConfigure(args[0].sval, args[1].ival, args[2].ival, + args[3].sval, args[4].ival, args[5].sval, + args[6].ival, args[7].ival, args[8].ival, + args[9].ival); +} + +extern "C" void NDPvxsRegister(void) +{ + iocshRegister(&initFuncDef,initCallFunc); +} + +extern "C" { +epicsExportRegistrar(NDPvxsRegister); +} diff --git a/ADApp/pluginSrc/NDPluginPvxs.dbd b/ADApp/pluginSrc/NDPluginPvxs.dbd new file mode 100644 index 000000000..8fe934c1b --- /dev/null +++ b/ADApp/pluginSrc/NDPluginPvxs.dbd @@ -0,0 +1 @@ +registrar("NDPvxsRegister") diff --git a/ADApp/pluginSrc/NDPluginPvxs.h b/ADApp/pluginSrc/NDPluginPvxs.h new file mode 100644 index 000000000..a03e35973 --- /dev/null +++ b/ADApp/pluginSrc/NDPluginPvxs.h @@ -0,0 +1,33 @@ +#ifndef NDPluginPvxs_H +#define NDPluginPvxs_H + + +#include "NDPluginDriver.h" +#include <vector> + +#define NDPluginPvxsPvNameString "PV_NAME" + +class NTNDArrayRecordPvxs; +typedef std::shared_ptr<NTNDArrayRecordPvxs> NTNDArrayRecordPvxsPtr; + +/** Converts NDArray callback data into EPICS V4 NTNDArray data and exposes it + * as an EPICS V4 PV */ +class NDPLUGIN_API NDPluginPvxs : public NDPluginDriver, + public std::enable_shared_from_this<NDPluginPvxs> +{ +public: + NDPluginPvxs(const char *portName, int queueSize, int blockingCallbacks, + const char *NDArrayPort, int NDArrayAddr, const char *pvName, + int maxBuffers, size_t maxMemory, int priority, int stackSize); + + /* These methods override the virtual methods in the base class */ + void processCallbacks(NDArray *pArray); + +protected: + int NDPluginPvxsPvName; + +private: + NTNDArrayRecordPvxsPtr m_record; +}; + +#endif diff --git a/docs/ADCore/NDPluginPvxs.rst b/docs/ADCore/NDPluginPvxs.rst new file mode 100644 index 000000000..1f9cba869 --- /dev/null +++ b/docs/ADCore/NDPluginPvxs.rst @@ -0,0 +1,66 @@ +NDPluginPvxs +=========== +:author: James Souter, Diamond Light Source + +.. contents:: Contents + +Overview +-------- + +This plugin reimplements the functionality of :ref:`NDPluginPva`, but requires the linking +of the pvxs support module, with no requirement for the pvAccessCPP, pvDatabaseCPP, pvDataCPP or +normativeTypesCPP support modules. The plugin wraps a pvxs SharedPV, which is added to +the singleton PVXS server that is started when the IOC starts. + +NDPluginPvxs defines the following parameters. + +.. cssclass:: table-bordered table-striped table-hover +.. flat-table:: + :header-rows: 2 + :widths: 5 5 5 70 5 5 5 + + * - + - + - **Parameter Definitions in NDPluginPvxs.h and EPICS Record Definitions in NDPvxs.template** + * - Parameter index variable + - asyn interface + - Access + - Description + - drvInfo string + - EPICS record name + - EPICS record type + * - NDPluginPvxsPvName + - asynOctet + - r/o + - Name of the EPICSv4 PV being served + - PV_NAME + - $(P)$(R)PvName_RBV + - waveform + + +Configuration +------------- + +The NDPluginPvxs plugin is created with the ``NDPvxsConfigure`` command, +either from C/C++ or from the EPICS IOC shell. + +:: + + NDPvxsConfigure (const char *portName, int queueSize, int blockingCallbacks, + const char *NDArrayPort, int NDArrayAddr, const char *pvName, + size_t maxMemory, int priority, int stackSize) + + +For details on the meaning of the parameters to this function refer to +the detailed documentation on the NDPvxsConfigure function in the +`NDPluginPvxs.cpp +documentation <../areaDetectorDoxygenHTML/_n_d_plugin_pvxs_8cpp.html>`__ and +in the documentation for the constructor for the `NDPluginPvxs +class <../areaDetectorDoxygenHTML/class_n_d_plugin_pvxs.html>`__. + +Starting the pvxs server +---------------------------- + +Unlike NDPluginPva, the EPICSv4 PV will be automatically served in an IOC if the plugin +is configured, however the +`IOC must be built with pvxsIoc.dbd and the pvxs and pvxsIoc libraries. <https://epics-base.github.io/pvxs/building.html#including-pvxs-in-your-application>`