From 55c18501f61ad101c9ab3352370ed459c32f677c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 19 May 2020 16:46:49 -0700 Subject: [PATCH 01/19] add svectorinRecord --- pdbApp/Makefile | 22 ++- pdbApp/devSVInSoft.cpp | 110 ++++++++++++++ pdbApp/qsrv-new.dbd | 2 + pdbApp/qsrv-old.dbd | 2 + pdbApp/svectorinRecord.cpp | 295 +++++++++++++++++++++++++++++++++++++ pdbApp/svectorinRecord.dbd | 74 ++++++++++ 6 files changed, 501 insertions(+), 4 deletions(-) create mode 100644 pdbApp/devSVInSoft.cpp create mode 100644 pdbApp/svectorinRecord.cpp create mode 100644 pdbApp/svectorinRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index a2e9c09..ed7c210 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -22,6 +22,8 @@ USR_CPPFLAGS += -I$(TOP)/common -I$(TOP)/p2pApp INC += pv/qsrv.h INC += pv/qsrvVersionNum.h +DBDINC += svectorinRecord + EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION EXPANDVARS += EPICS_QSRV_MAINTENANCE_VERSION @@ -55,6 +57,9 @@ else qsrv_SRCS += pvalink_null.cpp endif +qsrv_SRCS += svectorinRecord.c +qsrv_SRCS += devSVInSoft.c + qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -82,6 +87,7 @@ DB_INSTALLS += $(EPICS_BASE)/db/softIocExit.db endif softIocPVA_DBD += softIoc.dbd +softIocPVA_DBD += svectorinRecord.dbd softIocPVA_DBD += PVAServerRegister.dbd softIocPVA_DBD += qsrv.dbd @@ -91,6 +97,10 @@ include $(TOP)/configure/RULES #---------------------------------------- # ADD RULES AFTER THIS LINE +# why is this not automatic! +$(COMMON_DIR)/softIocPVA.dbd: ../svectorinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd + # Can't use EXPAND as generated headers must appear # in O.Common, but EXPAND emits rules for O.$(T_A) ../O.Common/pv/qsrvVersionNum.h: ../pv/qsrvVersionNum.h@ @@ -101,12 +111,16 @@ qsrv$(DEP): ../O.Common/pv/qsrvVersionNum.h pvalink$(DEP): ../O.Common/pv/qsrvVersionNum.h ifdef BASE_3_16 -../O.Common/qsrv.dbd: ../qsrv-new.dbd - $(CP) $< $@ +QSRVDBD = ../qsrv-new.dbd else -../O.Common/qsrv.dbd: ../qsrv-old.dbd - $(CP) $< $@ +QSRVDBD = ../qsrv-old.dbd endif + +$(QSRVDBD) : ../svectorinRecord.dbd + +../O.Common/qsrv.dbd: $(QSRVDBD) + $(CP) $< $@ + qsrv.dbd$(DEP): @$(RM) $@ @echo "$(COMMONDEP_TARGET): ../Makefile" > $@ diff --git a/pdbApp/devSVInSoft.cpp b/pdbApp/devSVInSoft.cpp new file mode 100644 index 0000000..7e92486 --- /dev/null +++ b/pdbApp/devSVInSoft.cpp @@ -0,0 +1,110 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include + +// include by svectorinRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#include "svectorinRecord.h" +#include + +namespace { + +namespace pvd = epics::pvData; + +long readLocked(struct link *pinp, void *raw) +{ + const bool* doload = static_cast(raw); + svectorinRecord *prec = (svectorinRecord *) pinp->precord; + + VSharedVector ival; + ival.vtype = &vfSharedVector; + ival.value = &prec->val; + + long status = *doload ? dbLoadLink(pinp, DBR_VFIELD, &ival) : dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status==S_db_badDbrtype) { + status=0; + + size_t esize = dbValueSize(prec->ftvl); + pvd::shared_vector temp(prec->nelm*esize); + long nReq = prec->nelm; + + if(!status && !(status = *doload ? dbLoadLinkArray(pinp, prec->ftvl, temp.data(), &nReq) : dbGetLink(pinp, prec->ftvl, temp.data(), 0, &nReq)) && nReq>=0) { + if(prec->ftvl==DBR_STRING) { + // translate char[] to std::string + pvd::shared_vector scratch(nReq); + for(size_t i=0u, N=scratch.size(); ival = pvd::static_shared_vector_cast(pvd::freeze(scratch)); + } else { + temp.resize(nReq*esize); + prec->val = pvd::static_shared_vector_cast(pvd::freeze(temp)); + prec->val.set_original_type(prec->stvl); + } + + } else if(!status) { + status = -1; + } + } + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +long init_record(struct dbCommon *pcommon) +{ + svectorinRecord *prec = (svectorinRecord *)pcommon; + bool doload = true; + + if (readLocked(&prec->inp, &doload)) { + prec->udf = FALSE; + } + + return 0; +} + +long read_svi(svectorinRecord* prec) +{ + bool doload = false; + long status = dbLinkDoLocked(&prec->inp, readLocked, &doload); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &doload); + + if (!status && !dbLinkIsConstant(&prec->inp)) + prec->udf = FALSE; + + return status; +} + +svectorindset devSVISoft = { + {5, NULL, NULL, &init_record, NULL}, + &read_svi +}; + +} +extern "C" { +epicsExportAddress(dset, devSVISoft); +} diff --git a/pdbApp/qsrv-new.dbd b/pdbApp/qsrv-new.dbd index 7ac8602..6264efc 100644 --- a/pdbApp/qsrv-new.dbd +++ b/pdbApp/qsrv-new.dbd @@ -16,3 +16,5 @@ variable(PDBProviderDebug, int) # Number of worker threads for handling monitor updates. # Default: 1 variable(pvaLinkNWorkers, int) + +include "svectorinRecord.dbd" diff --git a/pdbApp/qsrv-old.dbd b/pdbApp/qsrv-old.dbd index 05b9edd..bc77b01 100644 --- a/pdbApp/qsrv-old.dbd +++ b/pdbApp/qsrv-old.dbd @@ -15,3 +15,5 @@ variable(PDBProviderDebug, int) # Number of worker threads for handling monitor updates. # Default: 1 variable(pvaLinkNWorkers, int) + +include "svectorinRecord.dbd" diff --git a/pdbApp/svectorinRecord.cpp b/pdbApp/svectorinRecord.cpp new file mode 100644 index 0000000..71a0341 --- /dev/null +++ b/pdbApp/svectorinRecord.cpp @@ -0,0 +1,295 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include +#include +#include +#include +#include + +// include by svectorinRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +extern "C" { +const VFieldType vfSharedVector = {"epics::pvData::shared_vector"}; +} + +namespace { + +namespace pvd = epics::pvData; + +ELLLIST vfSharedVectorList = ELLLIST_INIT; +VFieldTypeNode vfSharedVectorNode; + +long initialize() +{ + vfSharedVectorNode.vtype = &vfSharedVector; + ellAdd(&vfSharedVectorList, &vfSharedVectorNode.node); + return 0; +} + +long type_check(svectorinRecord *prec) +{ + if(prec->val.empty() || prec->val.original_type()==prec->stvl) + return 0; + + errlogPrintf("%s error: device support attempts type change. Clear VAL\n", prec->name); + (void)recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "STVL type mismatch"); + + prec->val.clear(); + + return S_db_badDbrtype; +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + svectorinRecord *prec = (svectorinRecord*)pcommon; + svectorindset *pdset = (svectorindset *)(prec->dset); + + if(pass==0) { + new (&prec->val) pvd::shared_vector(); + new (&prec->oval) pvd::shared_vector(); + + if (!pdset) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no DSET"); + recGblRecordError(S_dev_noDSET, prec, "svectorin: no DSET"); + return S_dev_noDSET; + } + + switch(prec->ftvl) { + #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBF_##DBFTYPE: prec->stvl = pvd::pv##PVACODE; break; + #define CASE_SKIP_BOOL + #define CASE_REAL_INT64 + #include + #undef CASE_SKIP_BOOL + #undef CASE_REAL_INT64 + #undef CASE + // not supporting ENUM or STRING + default: + prec->stvl = (pvd::ScalarType)-1; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad FTVL"); + recGblRecordError(S_db_badDbrtype, prec, "svectorin: bad FTVL"); + return S_db_badDbrtype; + } + + } else { // pass==1 + + if(pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(!ret) + ret = type_check(prec); + if(ret) + return ret; + + prec->oval = prec->val; + } + } + return 0; +} + +long readValue(svectorinRecord *prec, + svectorindset *pdset) +{ + long ret = pdset->read_sharedvector(prec); + + if(!ret) + ret = type_check(prec); + + return ret; +} + +void monitor(svectorinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + if ((prec->oval.dataPtr()!=prec->val.dataPtr()) + || (prec->oval.dataOffset()!=prec->val.dataOffset()) + || (prec->oval.size()!=prec->val.size())) { + monitor_mask |= DBE_VALUE | DBE_LOG; + prec->oval = prec->val; + } + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + db_post_events(prec, &prec->oval, monitor_mask); + } +} + +long process(struct dbCommon *pcommon) +{ + svectorinRecord *prec = (svectorinRecord*)pcommon; + svectorindset *pdset = (svectorindset *)(prec->dset); + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->read_sharedvector==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_svectorin"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_svectorin"); + return S_dev_missingSup; + + } else if(prec->stvl == (pvd::ScalarType)-1) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad FTVL"); + } + + status=readValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; +} + +long cvt_dbaddr(DBADDR *paddr) +{ + svectorinRecord *prec = (svectorinRecord*)paddr->precord; + // for both VAL and OVAL + + // we don't insist devsup allocate the required NELM + paddr->ro = 1; + // we provide vfield access + paddr->vfields = &vfSharedVectorList; + // arbitrary limit + paddr->no_elements = prec->nelm; + + paddr->field_type = paddr->dbr_field_type = prec->ftvl; + paddr->field_size = 1; + + + return 0; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + svectorinRecord *prec = (svectorinRecord*)paddr->precord; + + if(prec->stvl == (pvd::ScalarType)-1) { + return S_db_badDbrtype; + } + + size_t esize = pvd::ScalarTypeFunc::elementSize(prec->stvl); + + *offset = 0; + if(dbGetFieldIndex(paddr)==svectorinRecordVAL) { + paddr->pfield = (void*)prec->val.data(); + *no_elements = prec->val.size()/esize; + if(paddr->no_elements < *no_elements) + *no_elements = paddr->no_elements; + return 0; + + } else if(dbGetFieldIndex(paddr)==svectorinRecordOVAL) { + paddr->pfield = (void*)prec->oval.data(); + *no_elements = prec->oval.size()/esize; + return 0; + } + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + svectorinRecord *prec = (svectorinRecord*)paddr->precord; + + if(p->vtype==&vfSharedVector) { + VSharedVector *pstr = (VSharedVector*)p; + if(dbGetFieldIndex(paddr)==svectorinRecordVAL) { + *pstr->value = prec->val; + return 0; + } else if(dbGetFieldIndex(paddr)==svectorinRecordOVAL) { + *pstr->value = prec->oval; + return 0; + } + } + return S_db_badChoice; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + svectorinRecord *prec = (svectorinRecord*)paddr->precord; + + if(p->vtype==&vfSharedVector) { + const VSharedVector *pstr = (const VSharedVector*)p; + if(dbGetFieldIndex(paddr)==svectorinRecordVAL) { + prec->val = *pstr->value; + return 0; + } else if(dbGetFieldIndex(paddr)==svectorinRecordOVAL) { + prec->oval = *pstr->value; + return 0; + } + } + return S_db_badChoice; +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset svectorinRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +} // namespace + +extern "C" { +epicsExportAddress(rset,svectorinRSET); +} diff --git a/pdbApp/svectorinRecord.dbd b/pdbApp/svectorinRecord.dbd new file mode 100644 index 0000000..aa2af4f --- /dev/null +++ b/pdbApp/svectorinRecord.dbd @@ -0,0 +1,74 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuFtype.dbd" + +recordtype(svectorin) { + include "dbCommon.dbd" + %/* Declare Device Support Entry Table */ + %#include + %#include + %#include + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %QSRV_API extern const VFieldType vfSharedVector; + %struct VSharedVector { + % const VFieldType* vtype; + % epics::pvData::shared_vector* value; + %}; + %typedef struct svectorindset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*read_sharedvector)(struct svectorinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %} svectorindset; + %#define HAS_svectorindset + %#ifdef __cplusplus + %} + %#endif + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("::epics::pvData::shared_vector val") + } + field(OVAL,DBF_NOACCESS) { + prompt("Previous Value") + interest(3) + special(SPC_DBADDR) + extra("::epics::pvData::shared_vector oval") + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(NELM,DBF_ULONG) { + prompt("Number of Elements") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(FTVL,DBF_MENU) { + prompt("Field Type of Value") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + menu(menuFtype) + } + field(STVL,DBF_NOACCESS) { + prompt("ScalarType of Value") + special(SPC_NOMOD) + interest(5) + extra("epics::pvData::ScalarType stvl") + } +} + +device(svectorin, CONSTANT, devSVISoft, "Soft Channel") From 807f34e9cc4052fd314edac95fd153c6d3f506f0 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 5 Jul 2022 12:05:35 -0700 Subject: [PATCH 02/19] support DBR_VFIELD, use svectorinRecord --- pdbApp/pdbsingle.cpp | 2 +- pdbApp/pdbsingle.h | 2 +- pdbApp/pvif.cpp | 154 ++++++++++++++++++++++++++++++------------- pdbApp/pvif.h | 7 +- 4 files changed, 115 insertions(+), 50 deletions(-) diff --git a/pdbApp/pdbsingle.cpp b/pdbApp/pdbsingle.cpp index 564ca89..1b9814b 100644 --- a/pdbApp/pdbsingle.cpp +++ b/pdbApp/pdbsingle.cpp @@ -97,7 +97,7 @@ void pdb_single_event(void *user_arg, struct dbChannel *chan, PDBSinglePV::PDBSinglePV(DBCH& chan, const PDBProvider::shared_pointer& prov) :provider(prov) - ,builder(new ScalarBuilder(chan.chan)) + ,builder(PVIFBuilder::create(std::string(), chan.chan)) ,interested_iterating(false) ,evt_VALUE(this) ,evt_PROPERTY(this) diff --git a/pdbApp/pdbsingle.h b/pdbApp/pdbsingle.h index cc3842e..71f19fb 100644 --- a/pdbApp/pdbsingle.h +++ b/pdbApp/pdbsingle.h @@ -40,7 +40,7 @@ struct QSRV_API PDBSinglePV : public PDBPV epicsMutex lock; - p2p::auto_ptr builder; + p2p::auto_ptr builder; p2p::auto_ptr pvif; epics::pvData::PVStructurePtr complete; // complete copy from subscription diff --git a/pdbApp/pvif.cpp b/pdbApp/pvif.cpp index 76def06..654949e 100644 --- a/pdbApp/pvif.cpp +++ b/pdbApp/pvif.cpp @@ -27,6 +27,8 @@ #include +#include + #ifdef EPICS_VERSION_INT # if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0) # define USE_INT64 @@ -166,9 +168,15 @@ PVIF::PVIF(dbChannel *ch) namespace { -struct pvTimeAlarm { +struct pvPlain { dbChannel *chan; + const VFieldType *vtype; + + pvPlain() :chan(NULL), vtype(NULL) {} + explicit pvPlain(dbChannel *chan, const VFieldType *vtype=0) :chan(chan), vtype(vtype) {} +}; +struct pvTimeAlarm : public pvPlain { pvd::uint32 nsecMask; pvd::BitSet maskALWAYS, maskALARM; @@ -177,7 +185,7 @@ struct pvTimeAlarm { pvd::PVIntPtr status, severity, nsec, userTag; pvd::PVStringPtr message; - pvTimeAlarm() :chan(NULL), nsecMask(0) {} + pvTimeAlarm() :nsecMask(0) {} }; struct pvCommon : public pvTimeAlarm { @@ -426,12 +434,12 @@ void putTime(const pvTimeAlarm& pv, unsigned dbe, db_field_log *pfl) } } -void putValue(dbChannel *chan, pvd::PVScalar* value, db_field_log *pfl) +void putValue(const pvPlain& pv, pvd::PVScalar* value, db_field_log *pfl) { dbrbuf buf; long nReq = 1; - long status = dbChannelGet(chan, dbChannelFinalFieldType(chan), &buf, NULL, &nReq, pfl); + long status = dbChannelGet(pv.chan, dbChannelFinalFieldType(pv.chan), &buf, NULL, &nReq, pfl); if(status) throw std::runtime_error("dbGet for meta fails"); @@ -440,7 +448,7 @@ void putValue(dbChannel *chan, pvd::PVScalar* value, db_field_log *pfl) memset(&buf, 0, sizeof(buf)); } - switch(dbChannelFinalFieldType(chan)) { + switch(dbChannelFinalFieldType(pv.chan)) { #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: value->putFrom(buf.dbf_##DBFTYPE); break; #define CASE_ENUM #define CASE_SKIP_BOOL @@ -457,11 +465,11 @@ void putValue(dbChannel *chan, pvd::PVScalar* value, db_field_log *pfl) } } -void getValue(dbChannel *chan, pvd::PVScalar* value) +void getValue(pvPlain& pv, pvd::PVScalar* value) { dbrbuf buf; - switch(dbChannelFinalFieldType(chan)) { + switch(dbChannelFinalFieldType(pv.chan)) { #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBR_##DBFTYPE: buf.dbf_##DBFTYPE = value->getAs(); break; #define CASE_ENUM #define CASE_SKIP_BOOL @@ -480,22 +488,34 @@ void getValue(dbChannel *chan, pvd::PVScalar* value) throw std::runtime_error("getValue unsupported DBR code"); } - long status = dbChannelPut(chan, dbChannelFinalFieldType(chan), &buf, 1); + long status = dbChannelPut(pv.chan, dbChannelFinalFieldType(pv.chan), &buf, 1); if(status) throw std::runtime_error("dbPut for meta fails"); } -void getValue(dbChannel *chan, pvd::PVScalarArray* value) +void getValue(pvPlain& pv, pvd::PVScalarArray* value) { - short dbr = dbChannelFinalFieldType(chan); + short dbr = dbChannelFinalFieldType(pv.chan); + + if(pv.vtype==&vfSharedVector) { + epics::pvData::shared_vector buf; + value->getAs(buf); + + VSharedVector vect; + vect.vtype = pv.vtype; + vect.value = &buf; - if(dbr!=DBR_STRING) { + long status = dbChannelPut(pv.chan, DBR_VFIELD, &vect, 1); + if(status) + throw std::runtime_error("dbChannelPut fails"); + + } else if(dbr!=DBR_STRING) { pvd::shared_vector buf; value->getAs(buf); long nReq = buf.size()/pvd::ScalarTypeFunc::elementSize(value->getScalarArray()->getElementType()); - long status = dbChannelPut(chan, dbr, buf.data(), nReq); + long status = dbChannelPut(pv.chan, dbr, buf.data(), nReq); if(status) throw std::runtime_error("dbChannelPut fails"); @@ -512,24 +532,37 @@ void getValue(dbChannel *chan, pvd::PVScalarArray* value) temp[i*MAX_STRING_SIZE + MAX_STRING_SIZE-1] = '\0'; } - long status = dbChannelPut(chan, dbr, &temp[0], buf.size()); + long status = dbChannelPut(pv.chan, dbr, &temp[0], buf.size()); if(status) throw std::runtime_error("dbChannelPut fails"); } } -void putValue(dbChannel *chan, pvd::PVScalarArray* value, db_field_log *pfl) +void putValue(const pvPlain& pv, pvd::PVScalarArray* value, db_field_log *pfl) { - const short dbr = dbChannelFinalFieldType(chan); + const short dbr = dbChannelFinalFieldType(pv.chan); - long nReq = dbChannelFinalElements(chan); + long nReq = dbChannelFinalElements(pv.chan); const pvd::ScalarType etype = value->getScalarArray()->getElementType(); - if(dbr!=DBR_STRING) { + if(pv.vtype==&vfSharedVector) { + epics::pvData::shared_vector buf; + VSharedVector vect; + vect.vtype = pv.vtype; + vect.value = &buf; + nReq = 1; + + long status = dbChannelGet(pv.chan, DBR_VFIELD, &vect, NULL, &nReq, pfl); + if(status) + throw std::runtime_error("dbChannelGet for vfSharedVector value fails"); + + value->putFrom(buf); + + } else if(dbr!=DBR_STRING) { pvd::shared_vector buf(pvd::ScalarTypeFunc::allocArray(etype, nReq)); // TODO: pool? - long status = dbChannelGet(chan, dbr, buf.data(), NULL, &nReq, pfl); + long status = dbChannelGet(pv.chan, dbr, buf.data(), NULL, &nReq, pfl); if(status) throw std::runtime_error("dbChannelGet for value fails"); @@ -540,7 +573,7 @@ void putValue(dbChannel *chan, pvd::PVScalarArray* value, db_field_log *pfl) } else { std::vector temp(nReq*MAX_STRING_SIZE); - long status = dbChannelGet(chan, dbr, &temp[0], NULL, &nReq, pfl); + long status = dbChannelGet(pv.chan, dbr, &temp[0], NULL, &nReq, pfl); if(status) throw std::runtime_error("dbChannelGet for value fails"); @@ -607,7 +640,7 @@ template void putAll(const PVC &pv, unsigned dbe, db_field_log *pfl) { if(dbe&(DBE_VALUE|DBE_ARCHIVE)) { - putValue(pv.chan, pv.value.get(), pfl); + putValue(pv, pv.value.get(), pfl); } if(!(dbe&DBE_PROPERTY)) { putTime(pv, dbe, pfl); @@ -677,7 +710,7 @@ struct PVIFScalarNumeric : public PVIF PVX pvmeta; const epics::pvData::PVStructurePtr pvalue; - PVIFScalarNumeric(dbChannel *ch, const epics::pvData::PVFieldPtr& p, pvd::PVField *enclosing) + PVIFScalarNumeric(dbChannel *ch, const VFieldType* vtype, const epics::pvData::PVFieldPtr& p, pvd::PVField *enclosing) :PVIF(ch) ,pvalue(std::tr1::dynamic_pointer_cast(p)) { @@ -685,6 +718,7 @@ struct PVIFScalarNumeric : public PVIF throw std::runtime_error("Must attach to structure"); pvmeta.chan = ch; + pvmeta.vtype = vtype; attachAll(pvmeta, pvalue); if(enclosing) { size_t bit = enclosing->getFieldOffset(); @@ -734,7 +768,7 @@ struct PVIFScalarNumeric : public PVIF bool newval = mask.logical_and(pvmeta.maskVALUEPut); if(newval) { if(permit) - getValue(pvmeta.chan, pvmeta.value.get()); + getValue(pvmeta, pvmeta.value.get()); else ret = pvd::Status::error("Put not permitted"); } @@ -805,6 +839,14 @@ pvd::StructureConstPtr buildTimeStamp() ->createStructure(); } +ScalarBuilder::ScalarBuilder(dbChannel* chan, const VFieldType* vtype) + :PVIFBuilder(chan) + ,vtype(vtype) + ,scalar(dbChannelFinalElements(channel)==1 && vtype!=&vfSharedVector) +{} + +ScalarBuilder::~ScalarBuilder() {} + epics::pvData::FieldConstPtr ScalarBuilder::dtype() { @@ -812,13 +854,12 @@ ScalarBuilder::dtype() throw std::runtime_error("+type:\"scalar\" requires +channel:"); short dbr = dbChannelFinalFieldType(channel); - const long maxelem = dbChannelFinalElements(channel); const pvd::ScalarType pvt = DBR2PVD(dbr); if(INVALID_DB_REQ(dbr)) throw std::invalid_argument("DBF code out of range"); - if(maxelem!=1 && dbr==DBR_ENUM) + if(!scalar && dbr==DBR_ENUM) dbr = DBF_SHORT; pvd::FieldBuilderPtr builder(pvd::getFieldCreate()->createFieldBuilder()); @@ -831,7 +872,7 @@ ScalarBuilder::dtype() ->add("index", pvd::pvInt) ->addArray("choices", pvd::pvString) ->endNested(); - else if(maxelem==1) + else if(scalar) builder = builder->setId("epics:nt/NTScalar:1.0") ->add("value", pvt); else @@ -872,9 +913,8 @@ ScalarBuilder::attach(const epics::pvData::PVStructurePtr& root, const FieldName pvd::PVFieldPtr fld(fldname.lookup(root, &enclosing)); const short dbr = dbChannelFinalFieldType(channel); - const long maxelem = dbChannelFinalElements(channel); - if(maxelem==1) { + if(scalar) { switch(dbr) { case DBR_CHAR: case DBR_UCHAR: @@ -886,14 +926,14 @@ ScalarBuilder::attach(const epics::pvData::PVStructurePtr& root, const FieldName case DBR_INT64: case DBR_UINT64: #endif - return new PVIFScalarNumeric(channel, fld, enclosing); + return new PVIFScalarNumeric(channel, vtype, fld, enclosing); case DBR_FLOAT: case DBR_DOUBLE: - return new PVIFScalarNumeric(channel, fld, enclosing); + return new PVIFScalarNumeric(channel, vtype, fld, enclosing); case DBR_ENUM: - return new PVIFScalarNumeric(channel, fld, enclosing); + return new PVIFScalarNumeric(channel, vtype, fld, enclosing); case DBR_STRING: - return new PVIFScalarNumeric(channel, fld, enclosing); + return new PVIFScalarNumeric(channel, vtype, fld, enclosing); } } else { switch(dbr) { @@ -911,7 +951,7 @@ ScalarBuilder::attach(const epics::pvData::PVStructurePtr& root, const FieldName case DBR_UINT64: #endif case DBR_DOUBLE: - return new PVIFScalarNumeric(channel, fld, enclosing); + return new PVIFScalarNumeric(channel, vtype, fld, enclosing); } } @@ -924,12 +964,12 @@ struct PVIFPlain : public PVIF { const typename PVD::shared_pointer field; size_t fieldOffset; - dbChannel * const channel; + pvPlain pv; - PVIFPlain(dbChannel *channel, const epics::pvData::PVFieldPtr& fld, epics::pvData::PVField* enclosing=0) + PVIFPlain(dbChannel *channel, const VFieldType *vtype, const epics::pvData::PVFieldPtr& fld, epics::pvData::PVField* enclosing=0) :PVIF(channel) ,field(std::tr1::static_pointer_cast(fld)) - ,channel(channel) + ,pv(channel, vtype) { if(!field) throw std::logic_error("PVIFPlain attached type mis-match"); @@ -944,7 +984,7 @@ struct PVIFPlain : public PVIF virtual void put(epics::pvData::BitSet& mask, unsigned dbe, db_field_log *pfl) OVERRIDE FINAL { if(dbe&DBE_VALUE) { - putValue(channel, field.get(), pfl); + putValue(pv, field.get(), pfl); mask.set(fieldOffset); } } @@ -958,7 +998,7 @@ struct PVIFPlain : public PVIF bool newval = mask.get(fieldOffset); if(newval) { if(permit) - getValue(channel, field.get()); + getValue(pv, field.get()); else ret = pvd::Status::error("Put not permitted"); } @@ -984,7 +1024,12 @@ struct PVIFPlain : public PVIF struct PlainBuilder : public PVIFBuilder { - explicit PlainBuilder(dbChannel* chan) :PVIFBuilder(chan) {} + const VFieldType* const vtype; + + PlainBuilder(dbChannel* chan, const VFieldType *vtype) + :PVIFBuilder(chan) + ,vtype(vtype) + {} virtual ~PlainBuilder() {} // fetch the structure description @@ -1019,15 +1064,20 @@ struct PlainBuilder : public PVIFBuilder pvd::PVFieldPtr fld(fldname.lookup(root, &enclosing)); if(maxelem==1) - return new PVIFPlain(channel, fld, enclosing); + return new PVIFPlain(channel, vtype, fld, enclosing); else - return new PVIFPlain(channel, fld, enclosing); + return new PVIFPlain(channel, vtype, fld, enclosing); } }; struct AnyScalarBuilder : public PVIFBuilder { - explicit AnyScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {} + const VFieldType* const vtype; + + explicit AnyScalarBuilder(dbChannel* chan, const VFieldType *vtype) + :PVIFBuilder(chan) + ,vtype(vtype) + {} virtual ~AnyScalarBuilder() {} // fetch the structure description @@ -1066,9 +1116,9 @@ struct AnyScalarBuilder : public PVIFBuilder } if(maxelem==1) - return new PVIFPlain(channel, arr, enclosing ? enclosing : arr.get()); + return new PVIFPlain(channel, vtype, arr, enclosing ? enclosing : arr.get()); else - return new PVIFPlain(channel, arr, enclosing ? enclosing : arr.get()); + return new PVIFPlain(channel, vtype, arr, enclosing ? enclosing : arr.get()); } }; @@ -1308,12 +1358,24 @@ PVIFBuilder::dtype(epics::pvData::FieldBuilderPtr& builder, PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan) { + // may select a VFT to use + const VFieldType* vtype = 0; + if(chan && dbChannelVFields(chan)) { + for(ELLNODE *cur = ellFirst(dbChannelVFields(chan)); cur; cur = ellNext(cur)) { + const VFieldTypeNode* vnode = CONTAINER(cur, VFieldTypeNode, node); + if(vnode->vtype==&vfSharedVector) { + vtype = vnode->vtype; + break; + } + } + } + if(type.empty() || type=="scalar") - return new ScalarBuilder(chan); + return new ScalarBuilder(chan, vtype); else if(type=="plain") - return new PlainBuilder(chan); + return new PlainBuilder(chan, vtype); else if(type=="any") - return new AnyScalarBuilder(chan); + return new AnyScalarBuilder(chan, vtype); else if(type=="meta") return new MetaBuilder(chan); else if(type=="proc") diff --git a/pdbApp/pvif.h b/pdbApp/pvif.h index 7ac0c4c..2c7b287 100644 --- a/pdbApp/pvif.h +++ b/pdbApp/pvif.h @@ -428,11 +428,14 @@ struct QSRV_API PVIFBuilder struct QSRV_API ScalarBuilder : public PVIFBuilder { - explicit ScalarBuilder(dbChannel* chan) :PVIFBuilder(chan) {} - virtual ~ScalarBuilder() {} + explicit ScalarBuilder(dbChannel* chan, const VFieldType* vtype=0); + virtual ~ScalarBuilder(); virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL; virtual PVIF* attach(const epics::pvData::PVStructurePtr& root, const FieldName& fld) OVERRIDE FINAL; + + const VFieldType* vtype; + const bool scalar; }; From 77bcd56ab55b9f9e5211eaac251b57853be58b00 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 2 Jun 2020 17:40:57 -0700 Subject: [PATCH 03/19] add pvstructinRecord --- pdbApp/Makefile | 7 + pdbApp/devPVSInSoft.cpp | 84 +++++++++++ pdbApp/pvstructinRecord.cpp | 268 ++++++++++++++++++++++++++++++++++++ pdbApp/pvstructinRecord.dbd | 67 +++++++++ pdbApp/qsrv-new.dbd | 1 + pdbApp/qsrv-old.dbd | 1 + 6 files changed, 428 insertions(+) create mode 100644 pdbApp/devPVSInSoft.cpp create mode 100644 pdbApp/pvstructinRecord.cpp create mode 100644 pdbApp/pvstructinRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index ed7c210..62628c5 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -23,6 +23,7 @@ INC += pv/qsrv.h INC += pv/qsrvVersionNum.h DBDINC += svectorinRecord +DBDINC += pvstructinRecord EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION @@ -60,6 +61,9 @@ endif qsrv_SRCS += svectorinRecord.c qsrv_SRCS += devSVInSoft.c +qsrv_SRCS += pvstructinRecord.c +qsrv_SRCS += devPVSInSoft.c + qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -88,6 +92,7 @@ endif softIocPVA_DBD += softIoc.dbd softIocPVA_DBD += svectorinRecord.dbd +softIocPVA_DBD += pvstructinRecord.dbd softIocPVA_DBD += PVAServerRegister.dbd softIocPVA_DBD += qsrv.dbd @@ -99,6 +104,7 @@ include $(TOP)/configure/RULES # why is this not automatic! $(COMMON_DIR)/softIocPVA.dbd: ../svectorinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../pvstructinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd # Can't use EXPAND as generated headers must appear @@ -117,6 +123,7 @@ QSRVDBD = ../qsrv-old.dbd endif $(QSRVDBD) : ../svectorinRecord.dbd +$(QSRVDBD) : ../pvstructinRecord.dbd ../O.Common/qsrv.dbd: $(QSRVDBD) $(CP) $< $@ diff --git a/pdbApp/devPVSInSoft.cpp b/pdbApp/devPVSInSoft.cpp new file mode 100644 index 0000000..777d618 --- /dev/null +++ b/pdbApp/devPVSInSoft.cpp @@ -0,0 +1,84 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include + +// include by pvstructinRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#include "pvstructinRecord.h" +#include + +namespace { + +namespace pvd = epics::pvData; + +long readLocked(struct link *pinp, void *raw) +{ + const bool* doload = static_cast(raw); + pvstructinRecord *prec = (pvstructinRecord *) pinp->precord; + + VSharedPVStructure ival; + ival.vtype = &vfPVStructure; + ival.value = &prec->val; + ival.changed = &prec->chg; + + long status = *doload ? dbLoadLink(pinp, DBR_VFIELD, &ival) : dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +long init_record(struct dbCommon *pcommon) +{ + pvstructinRecord *prec = (pvstructinRecord *)pcommon; + bool doload = true; + + if (readLocked(&prec->inp, &doload)) { + prec->udf = FALSE; + } + + return 0; +} + +long read_svi(pvstructinRecord* prec) +{ + bool doload = false; + long status = dbLinkDoLocked(&prec->inp, readLocked, &doload); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &doload); + + if (!status && !dbLinkIsConstant(&prec->inp)) + prec->udf = FALSE; + + return status; +} + +pvstructindset devPVSISoft = { + {5, NULL, NULL, &init_record, NULL}, + &read_svi +}; + +} +extern "C" { +epicsExportAddress(dset, devPVSISoft); +} diff --git a/pdbApp/pvstructinRecord.cpp b/pdbApp/pvstructinRecord.cpp new file mode 100644 index 0000000..e54f3fd --- /dev/null +++ b/pdbApp/pvstructinRecord.cpp @@ -0,0 +1,268 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include +#include +#include +#include +#include + +// include by pvstructinRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +extern "C" { +const VFieldType vfStructure = {"epics::pvData::StructureConstPtr"}; +const VFieldType vfPVStructure = {"epics::pvData::PVStructurePtr"}; +} + +namespace { + +namespace pvd = epics::pvData; + +ELLLIST vfPVStructureList = ELLLIST_INIT; +VFieldTypeNode vfPVStructureNode[2]; + +long initialize() +{ + vfPVStructureNode[0].vtype = &vfStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[0].node); + vfPVStructureNode[1].vtype = &vfPVStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[1].node); + return 0; +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + pvstructinRecord *prec = (pvstructinRecord*)pcommon; + pvstructindset *pdset = (pvstructindset *)(prec->dset); + + if (!pdset) { + if(pass==0) + recGblRecordError(S_dev_noDSET, prec, "pvstructin: no DSET"); + return S_dev_noDSET; + } + + if(pass==0) { + new (&prec->val) pvd::PVStructurePtr(); + new (&prec->chg) pvd::BitSet(); + new (&prec->ptyp) pvd::StructureConstPtr(); + + } else { // pass==1 + + if(pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } + + if(!prec->val) { + recGblRecordError(S_dev_noDSET, prec, "pvstructin: init_record must set VAL with a valid PVStructure"); + return S_db_badDbrtype; + } + + prec->ptyp = prec->val->getStructure(); + } + return 0; +} + +long readValue(pvstructinRecord *prec, + pvstructindset *pdset) +{ + prec->chg.clear(); + + long ret = pdset->read_pvs(prec); + + if(!ret && prec->val && prec->val->getStructure()!=prec->ptyp) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "VAL must use PTYP"); + prec->val.reset(); + prec->chg.clear(); + ret = S_db_badDbrtype; + } + + return ret; +} + +void monitor(pvstructinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + if (!prec->chg.isEmpty()) { + monitor_mask |= DBE_VALUE | DBE_LOG; + } + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + } +} + +long process(struct dbCommon *pcommon) +{ + pvstructinRecord *prec = (pvstructinRecord*)pcommon; + pvstructindset *pdset = (pvstructindset *)(prec->dset); + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->read_pvs==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_pvs"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_pvs"); + return S_dev_missingSup; + + } else if(!prec->ptyp) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no no PTYP"); + return S_dev_NoInit; + } + + status=readValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; +} + +long cvt_dbaddr(DBADDR *paddr) +{ + pvstructinRecord *prec = (pvstructinRecord*)paddr->precord; + // for both VAL and OVAL + + // we don't provide a valid DBR buffer + paddr->ro = 1; + // arbitrary limit + paddr->no_elements = 1; + + paddr->field_type = DBF_NOACCESS; + + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + // we provide vfield access + paddr->vfields = &vfPVStructureList; + } + + return 0; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + pvstructinRecord *prec = (pvstructinRecord*)paddr->precord; + + if(!prec->ptyp) + return S_db_notInit; + + if(p->vtype==&vfPVStructure) { + VSharedPVStructure *pstr = (VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + if(!*pstr->value) + return S_db_notInit; + (*pstr->value)->copy(*prec->val); + *pstr->changed = prec->chg; + return 0; + } + + } else if(p->vtype==&vfStructure) { + VSharedStructure *pstr = (VSharedStructure*)p; + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + *pstr->value = prec->ptyp; + return 0; + } + } + return S_db_badChoice; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + pvstructinRecord *prec = (pvstructinRecord*)paddr->precord; + + if(!prec->ptyp) + return S_db_notInit; + + if(p->vtype==&vfPVStructure) { + const VSharedPVStructure *pstr = (const VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + prec->val->copy(**pstr->value); + prec->chg = *pstr->changed; + return 0; + } + } + return S_db_badChoice; +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset pvstructinRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +} // namespace + +extern "C" { +epicsExportAddress(rset,pvstructinRSET); +} diff --git a/pdbApp/pvstructinRecord.dbd b/pdbApp/pvstructinRecord.dbd new file mode 100644 index 0000000..8bcea6d --- /dev/null +++ b/pdbApp/pvstructinRecord.dbd @@ -0,0 +1,67 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuFtype.dbd" + +recordtype(pvstructin) { + include "dbCommon.dbd" + %/* Declare Device Support Entry Table */ + %#include + %#include + %#include + %#include + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %QSRV_API extern const VFieldType vfStructure; + %struct VSharedStructure { + % const VFieldType* vtype; + % epics::pvData::StructureConstPtr* value; + %}; + %QSRV_API extern const VFieldType vfPVStructure; + %struct VSharedPVStructure { + % const VFieldType* vtype; + % const epics::pvData::PVStructurePtr* value; + % epics::pvData::BitSet* changed; + %}; + %typedef struct pvstructindset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*read_pvs)(struct pvstructinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %} pvstructindset; + %#define HAS_pvstructindset + %#ifdef __cplusplus + %} + %#endif + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("::epics::pvData::PVStructurePtr val") + } + field(CHG,DBF_NOACCESS) { + prompt("Change Mask") + interest(3) + special(SPC_NOMOD) + extra("::epics::pvData::BitSet chg") + } + field(PTYP,DBF_NOACCESS) { + prompt("Type") + interest(3) + special(SPC_DBADDR) + extra("::epics::pvData::StructureConstPtr ptyp") + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } +} + +device(pvstructin, CONSTANT, devPVSISoft, "Soft Channel") diff --git a/pdbApp/qsrv-new.dbd b/pdbApp/qsrv-new.dbd index 6264efc..eec7904 100644 --- a/pdbApp/qsrv-new.dbd +++ b/pdbApp/qsrv-new.dbd @@ -18,3 +18,4 @@ variable(PDBProviderDebug, int) variable(pvaLinkNWorkers, int) include "svectorinRecord.dbd" +include "pvstructinRecord.dbd" diff --git a/pdbApp/qsrv-old.dbd b/pdbApp/qsrv-old.dbd index bc77b01..a6d22c2 100644 --- a/pdbApp/qsrv-old.dbd +++ b/pdbApp/qsrv-old.dbd @@ -17,3 +17,4 @@ variable(PDBProviderDebug, int) variable(pvaLinkNWorkers, int) include "svectorinRecord.dbd" +include "pvstructinRecord.dbd" From 61dd1bf9e8876a3144e0f6d2e83a972e3884da84 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 3 Jun 2020 11:23:50 -0700 Subject: [PATCH 04/19] use pvstructinRecord --- pdbApp/pvif.cpp | 130 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/pdbApp/pvif.cpp b/pdbApp/pvif.cpp index 654949e..25eedf7 100644 --- a/pdbApp/pvif.cpp +++ b/pdbApp/pvif.cpp @@ -28,6 +28,7 @@ #include #include +#include #ifdef EPICS_VERSION_INT # if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0) @@ -1022,6 +1023,126 @@ struct PVIFPlain : public PVIF } }; +struct Arbitrary : public PVIF +{ + const pvd::PVStructurePtr root; + const pvd::PVField * const enclosing; + pvd::BitSet scratch; + + Arbitrary(dbChannel *chan, const pvd::PVStructurePtr& root, pvd::PVField *enclosing) + :PVIF(chan) + ,root(root) + ,enclosing(enclosing) + {} + virtual ~Arbitrary() {} + + virtual void put(epics::pvData::BitSet &mask, unsigned dbe, db_field_log *pfl) OVERRIDE FINAL + { + if(dbe&DBE_VALUE) { + scratch.clear(); + + VSharedPVStructure arg; + arg.vtype = &vfPVStructure; + arg.value = &root; + arg.changed = &scratch; + + long status = dbChannelGet(chan, DBR_VFIELD, &arg, NULL, NULL, pfl); + if(status) + throw std::runtime_error(SB()<<"PVStructure Get error 0x"<getFieldOffset()); + + } else { + mask |= scratch; + } + } + } + virtual epics::pvData::Status get(const epics::pvData::BitSet &mask, proc_t proc, bool permit) OVERRIDE FINAL + { + if(!(dbe(mask)&DBE_VALUE)) { + return pvd::Status(); + + } else if(dbChannelRecord(chan)->disp && dbChannelField(chan)!=&dbChannelRecord(chan)->disp) { + return pvd::Status::error("Put Disabled"); + } + + pvd::int32 first = root->getFieldOffset(); + pvd::int32 end = root->getNextFieldOffset(); + + scratch.clear(); + if(enclosing) { + for(pvd::int32 i=first; igetFieldOffset())) + return DBE_VALUE; + else if(enclosing) + return 0; + + pvd::int32 first = root->getFieldOffset(); + pvd::int32 end = root->getNextFieldOffset(); + + pvd::int32 idx = mask.nextSetBit(first); + if(idx<0 || idx >= end) + return DBE_VALUE; + + return 0; + } +}; + +struct ArbitraryBuilder : public PVIFBuilder +{ + epics::pvData::StructureConstPtr type; + + ArbitraryBuilder(dbChannel* chan) + :PVIFBuilder(chan) + { + if(!channel) + throw std::runtime_error("+type:\"arbitrary\" requires +channel:"); + + pvd::BitSet changed; + VSharedStructure arg; + arg.vtype = &vfStructure; + arg.value = &type; + + long status = dbChannelGet(chan, DBR_VFIELD, &arg, NULL, NULL, NULL); + if(status || !type) + throw std::runtime_error("arbitrary mapping unable to fetch initial value/type"); + } + virtual ~ArbitraryBuilder() {} + + virtual epics::pvData::FieldConstPtr dtype() OVERRIDE FINAL + { + return type; + } + virtual PVIF *attach(const epics::pvData::PVStructurePtr &root, const FieldName &fldname) OVERRIDE FINAL + { + pvd::PVField *enclosing = 0; + pvd::PVFieldPtr fld(fldname.lookup(root, &enclosing)); + + return new Arbitrary(channel, std::tr1::static_pointer_cast(fld), enclosing); + } +}; + struct PlainBuilder : public PVIFBuilder { const VFieldType* const vtype; @@ -1363,14 +1484,19 @@ PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan) if(chan && dbChannelVFields(chan)) { for(ELLNODE *cur = ellFirst(dbChannelVFields(chan)); cur; cur = ellNext(cur)) { const VFieldTypeNode* vnode = CONTAINER(cur, VFieldTypeNode, node); - if(vnode->vtype==&vfSharedVector) { + if(vnode->vtype==&vfSharedVector || vnode->vtype==&vfPVStructure) { vtype = vnode->vtype; break; } } } - if(type.empty() || type=="scalar") + if(vtype==&vfPVStructure) { + if(!type.empty()) { + errlogPrintf("%s: ignore +type \"%s\"\n", dbChannelName(chan), type.c_str()); + } + return new ArbitraryBuilder(chan); + } else if(type.empty() || type=="scalar") return new ScalarBuilder(chan, vtype); else if(type=="plain") return new PlainBuilder(chan, vtype); From e11be1bc98b809a203b6ccbd73e19b1701a512fc Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 17 Aug 2020 10:56:24 -0700 Subject: [PATCH 05/19] add columnarinRecord --- pdbApp/Makefile | 5 + pdbApp/columnarinRecord.cpp | 346 ++++++++++++++++++++++++++++++++++++ pdbApp/columnarinRecord.dbd | 49 +++++ 3 files changed, 400 insertions(+) create mode 100644 pdbApp/columnarinRecord.cpp create mode 100644 pdbApp/columnarinRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 62628c5..2940e26 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -24,6 +24,7 @@ INC += pv/qsrvVersionNum.h DBDINC += svectorinRecord DBDINC += pvstructinRecord +DBDINC += columnarinRecord EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION @@ -64,6 +65,8 @@ qsrv_SRCS += devSVInSoft.c qsrv_SRCS += pvstructinRecord.c qsrv_SRCS += devPVSInSoft.c +qsrv_SRCS += columnarinRecord.c + qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -93,6 +96,7 @@ endif softIocPVA_DBD += softIoc.dbd softIocPVA_DBD += svectorinRecord.dbd softIocPVA_DBD += pvstructinRecord.dbd +softIocPVA_DBD += columnarinRecord.dbd softIocPVA_DBD += PVAServerRegister.dbd softIocPVA_DBD += qsrv.dbd @@ -105,6 +109,7 @@ include $(TOP)/configure/RULES # why is this not automatic! $(COMMON_DIR)/softIocPVA.dbd: ../svectorinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../pvstructinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../columnarinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd # Can't use EXPAND as generated headers must appear diff --git a/pdbApp/columnarinRecord.cpp b/pdbApp/columnarinRecord.cpp new file mode 100644 index 0000000..4f07a31 --- /dev/null +++ b/pdbApp/columnarinRecord.cpp @@ -0,0 +1,346 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace pvd = epics::pvData; + +namespace columnarin { + +struct RPvt { + pvd::shared_vector labels; + pvd::FieldBuilderPtr builder; + pvd::PVStructurePtr tbl; + pvd::BitSet valid, changed; +}; + +void add_column(struct columnarinRecord *prec, const char* fname, const char* label, ::epics::pvData::ScalarType type) +{ + if(!prec->rpvt->builder) + throw std::logic_error("Only from init_record()"); + + prec->rpvt->builder = prec->rpvt->builder->addArray(fname, type); + prec->rpvt->labels.push_back(label ? label : fname); +} + +void set_column(struct columnarinRecord *prec, const char* fname, const ::epics::pvData::shared_vector& cdata) +{ + if(!prec->rpvt->tbl) + throw std::logic_error("Only from read_tbl()"); + + pvd::PVScalarArrayPtr col(prec->rpvt->tbl->getSubFieldT("value")->getSubFieldT(fname)); + col->putFrom(cdata); + prec->rpvt->changed.set(col->getFieldOffset()); + +} + +} // namespace columnarin + +namespace { +using namespace columnarin; + +ELLLIST vfPVStructureList = ELLLIST_INIT; +VFieldTypeNode vfPVStructureNode[2]; + +long initialize() +{ + vfPVStructureNode[0].vtype = &vfStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[0].node); + vfPVStructureNode[1].vtype = &vfPVStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[1].node); + return 0; +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + columnarinRecord *prec = (columnarinRecord*)pcommon; + columnarindset *pdset = (columnarindset *)(prec->dset); + try { + + if (!pdset) { + if(pass==0) + recGblRecordError(S_dev_noDSET, prec, "pvstructin: no DSET"); + return S_dev_noDSET; + } + + if(pass==0) { + prec->rpvt = new RPvt; + + } else { // pass==1 + prec->rpvt->builder = pvd::FieldBuilder::begin() + ->setId("epics:nt/NTTable:1.0") + ->addArray("labels", pvd::pvString) + ->addNestedStructure("value"); + + if(pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } + + const pvd::StandardFieldPtr& sfld(pvd::getStandardField()); + + prec->rpvt->tbl = prec->rpvt->builder->endNested() + ->add("alarm", sfld->alarm()) + ->add("timeStamp", sfld->timeStamp()) + ->createStructure()->build(); + + pvd::PVStringArrayPtr lbls(prec->rpvt->tbl->getSubFieldT("labels")); + lbls->replace(pvd::freeze(prec->rpvt->labels)); + prec->rpvt->valid.set(lbls->getFieldOffset()); + } + + return 0; + }catch(std::exception& e){ + fprintf(stderr, "%s: init_record error: %s\n", prec->name, e.what()); + return -1; + } +} + +long readValue(columnarinRecord *prec, + columnarindset *pdset) +{ + prec->rpvt->changed.clear(); + + long ret = pdset->read_tbl(prec); + + return ret; +} + +void monitor(columnarinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + { + // was set_column called during read_tbl()? + pvd::int32 bit = prec->rpvt->changed.nextSetBit(0u); + if(bit>=0 && pvd::uint32(bit)rpvt->tbl->getNextFieldOffset()) + monitor_mask |= DBE_VALUE|DBE_LOG; + } + + if(monitor_mask&DBE_ALARM) { + pvd::PVScalarPtr fld(prec->rpvt->tbl->getSubFieldT("alarm.severity")); + fld->putFrom(prec->sevr); + prec->rpvt->changed.set(fld->getFieldOffset()); + + //TODO: map status properly + fld = prec->rpvt->tbl->getSubFieldT("alarm.status"); + fld->putFrom(prec->stat ? 1 : 0); + prec->rpvt->changed.set(fld->getFieldOffset()); + + fld = prec->rpvt->tbl->getSubFieldT("alarm.message"); + fld->putFrom(std::string(prec->amsg)); + prec->rpvt->changed.set(fld->getFieldOffset()); + } + + { + pvd::PVScalarPtr fld(prec->rpvt->tbl->getSubFieldT("timeStamp.secondsPastEpoch")); + fld->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + prec->rpvt->changed.set(fld->getFieldOffset()); + + fld = prec->rpvt->tbl->getSubFieldT("timeStamp.nanoseconds"); + fld->putFrom(prec->time.nsec); + prec->rpvt->changed.set(fld->getFieldOffset()); + + fld = prec->rpvt->tbl->getSubFieldT("timeStamp.userTag"); + fld->putFrom(prec->utag); + prec->rpvt->changed.set(fld->getFieldOffset()); + } + + monitor_mask |= DBE_VALUE | DBE_LOG; + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + } + + prec->rpvt->valid |= prec->rpvt->changed; +} + +long process(struct dbCommon *pcommon) +{ + columnarinRecord *prec = (columnarinRecord*)pcommon; + columnarindset *pdset = (columnarindset *)(prec->dset); + try { + + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->read_tbl==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_pvs"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_pvs"); + return S_dev_missingSup; + + } else if(!prec->rpvt->tbl) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no table"); + return S_dev_NoInit; + } + + status=readValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; + }catch(std::exception& e){ + fprintf(stderr, "%s: process error: %s\n", prec->name, e.what()); + return -1; + } +} + +long cvt_dbaddr(DBADDR *paddr) +{ + // for VAL + + // we don't provide a valid DBR buffer + paddr->ro = 1; + // arbitrary limit + paddr->no_elements = 1; + + paddr->field_type = DBF_NOACCESS; + + if(dbGetFieldIndex(paddr)==columnarinRecordVAL) { + // we provide vfield access + paddr->vfields = &vfPVStructureList; + } + + return 0; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + columnarinRecord *prec = (columnarinRecord*)paddr->precord; + + if(!prec->rpvt->tbl) + return S_db_notInit; + + if(p->vtype==&vfPVStructure) { + VSharedPVStructure *pstr = (VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==columnarinRecordVAL) { + if(!*pstr->value) + return S_db_notInit; + (*pstr->value)->copy(*prec->rpvt->tbl); + *pstr->changed = prec->rpvt->valid; // TODO: distinguish initial from update + return 0; + } + + } else if(p->vtype==&vfStructure) { + VSharedStructure *pstr = (VSharedStructure*)p; + if(dbGetFieldIndex(paddr)==columnarinRecordVAL) { + *pstr->value = prec->rpvt->tbl->getStructure(); + return 0; + } + } + return S_db_badChoice; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + return S_db_noMod; +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset columnarinRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +long init_record_soft(dbCommon *prec) +{ + return 0; +} + +long read_tbl_soft(columnarinRecord* prec) +{ + (void)recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return 0; +} + +columnarindset devCOLISoft = { + {5, NULL, NULL, &init_record_soft, NULL}, + &read_tbl_soft +}; + + +} // namespace + +extern "C" { +epicsExportAddress(rset,columnarinRSET); +epicsExportAddress(dset, devCOLISoft); +} diff --git a/pdbApp/columnarinRecord.dbd b/pdbApp/columnarinRecord.dbd new file mode 100644 index 0000000..b24e64b --- /dev/null +++ b/pdbApp/columnarinRecord.dbd @@ -0,0 +1,49 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuFtype.dbd" + +recordtype(columnarin) { + include "dbCommon.dbd" + %#include + %#include + %struct columnarinRecord; + %namespace columnarin { + %struct RPvt; + %QSRV_API + %void add_column(struct columnarinRecord *prec, const char* fname, const char* label, ::epics::pvData::ScalarType type); + %QSRV_API + %void set_column(struct columnarinRecord *prec, const char* fname, const ::epics::pvData::shared_vector& cdata); + %} // namespace columnarin + %#ifdef __cplusplus + %extern "C" { + %#endif + %struct columnarindset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*read_tbl)(struct columnarinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %}; + %#ifdef __cplusplus + %} // extern "C" + %#endif + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("void* val") + } + field(RPVT,DBF_NOACCESS) { + special(SPC_NOMOD) + extra("::columnarin::RPvt* rpvt") + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } +} + +device(columnarin, CONSTANT, devCOLISoft, "Soft Channel") From e258ca748e29a6c25605f2517447a1647137a9d6 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 15 Sep 2020 21:33:52 -0700 Subject: [PATCH 06/19] add statBinRecord --- pdbApp/Makefile | 5 + pdbApp/qsrv-new.dbd | 1 + pdbApp/qsrv-old.dbd | 1 + pdbApp/statBinRecord.cpp | 466 +++++++++++++++++++++++++++++++++++++++ pdbApp/statBinRecord.dbd | 52 +++++ 5 files changed, 525 insertions(+) create mode 100644 pdbApp/statBinRecord.cpp create mode 100644 pdbApp/statBinRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 2940e26..bb583ab 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -25,6 +25,7 @@ INC += pv/qsrvVersionNum.h DBDINC += svectorinRecord DBDINC += pvstructinRecord DBDINC += columnarinRecord +DBDINC += statBinRecord EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION @@ -67,6 +68,8 @@ qsrv_SRCS += devPVSInSoft.c qsrv_SRCS += columnarinRecord.c +qsrv_SRCS += statBinRecord.c + qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -97,6 +100,7 @@ softIocPVA_DBD += softIoc.dbd softIocPVA_DBD += svectorinRecord.dbd softIocPVA_DBD += pvstructinRecord.dbd softIocPVA_DBD += columnarinRecord.dbd +softIocPVA_DBD += statBinRecord.dbd softIocPVA_DBD += PVAServerRegister.dbd softIocPVA_DBD += qsrv.dbd @@ -110,6 +114,7 @@ include $(TOP)/configure/RULES $(COMMON_DIR)/softIocPVA.dbd: ../svectorinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../pvstructinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../columnarinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../statBinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd # Can't use EXPAND as generated headers must appear diff --git a/pdbApp/qsrv-new.dbd b/pdbApp/qsrv-new.dbd index eec7904..6ca4d9e 100644 --- a/pdbApp/qsrv-new.dbd +++ b/pdbApp/qsrv-new.dbd @@ -19,3 +19,4 @@ variable(pvaLinkNWorkers, int) include "svectorinRecord.dbd" include "pvstructinRecord.dbd" +include "statBinRecord.dbd" diff --git a/pdbApp/qsrv-old.dbd b/pdbApp/qsrv-old.dbd index a6d22c2..54dedaa 100644 --- a/pdbApp/qsrv-old.dbd +++ b/pdbApp/qsrv-old.dbd @@ -18,3 +18,4 @@ variable(pvaLinkNWorkers, int) include "svectorinRecord.dbd" include "pvstructinRecord.dbd" +include "statBinRecord.dbd" diff --git a/pdbApp/statBinRecord.cpp b/pdbApp/statBinRecord.cpp new file mode 100644 index 0000000..045d13d --- /dev/null +++ b/pdbApp/statBinRecord.cpp @@ -0,0 +1,466 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +// include by statBinRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#include "pvstructinRecord.h" +#include "svectorinRecord.h" + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace pvd = epics::pvData; + +namespace statBin { + +struct RPvt { + pvd::PVStructurePtr root; + pvd::BitSet valid, changed; + + template + inline + std::tr1::shared_ptr assign(const char* name) { + std::tr1::shared_ptr fld(root->getSubFieldT(name)); + changed.set(fld->getFieldOffset()); + return fld; + } +}; + +long consume(struct statBinRecord *prec, const epics::pvData::shared_vector& arr) +{ + short dbf; + size_t esize; + switch(arr.original_type()) { +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case pvd::pv##PVACODE: esize = sizeof(epics##BASETYPE); dbf = DBF_##DBFTYPE; break; +#define CASE_REAL_INT64 +#define CASE_SKIP_BOOL +#include "pv/typemap.h" +#undef CASE_REAL_INT64 +#undef CASE_SKIP_BOOL +#undef CASE + default: + return S_db_badDbrtype; + } + + return statBinConsume(prec, dbf, arr.data(), arr.size()/esize); +} + +} // namespace statBin + +namespace { +template +long statBinConsumeIt(struct statBinRecord *prec, const void* raw, size_t nelem) +{ + if(!prec->rpvt) + return S_db_notInit; + + const T* arr = static_cast(raw); + size_t dec = prec->dec ? prec->dec : nelem; + + size_t nbins = nelem/dec; + if(nelem%dec) + nbins++; + + pvd::shared_vector mean(nbins), + std(nbins), + min(nbins), + max(nbins), + idx(nbins); + + for(size_t i=0, n=0u; n e) + bmin = e; + if(bmax < e) + bmax = e; + bsum += e; + bsum2+= e*e; + } + + min[n] = bmin; + max[n] = bmax; + mean[n] = bsum/cnt; + std[n] = sqrt(bsum2/cnt - mean[n]*mean[n]); + } + + prec->rpvt->assign("idx.value")->replace(pvd::freeze(idx)); + prec->rpvt->assign("min.value")->replace(pvd::freeze(min)); + prec->rpvt->assign("max.value")->replace(pvd::freeze(max)); + prec->rpvt->assign("mean.value")->replace(pvd::freeze(mean)); + prec->rpvt->assign("std.value")->replace(pvd::freeze(std)); + + return -1; +} +} // namespace + +long statBinConsume(struct statBinRecord *prec, short dbf, const void* arr, size_t nelem) +{ + switch (dbf) { +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBF_##DBFTYPE: return statBinConsumeIt(prec, arr, nelem); +#define CASE_REAL_INT64 +#define CASE_SKIP_BOOL +#include "pv/typemap.h" +#undef CASE_REAL_INT64 +#undef CASE_SKIP_BOOL +#undef CASE + default: + return S_db_badDbrtype; + } +} + +namespace { +using namespace statBin; + +ELLLIST vfPVStructureList = ELLLIST_INIT; +VFieldTypeNode vfPVStructureNode[2]; + +long initialize() +{ + vfPVStructureNode[0].vtype = &vfStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[0].node); + vfPVStructureNode[1].vtype = &vfPVStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[1].node); + return 0; +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + statBinRecord *prec = (statBinRecord*)pcommon; + statBindset *pdset = (statBindset *)(prec->dset); + try { + + if (!pdset) { + if(pass==0) + recGblRecordError(S_dev_noDSET, prec, "statBin: no DSET"); + return S_dev_noDSET; + } + + if(pass==0) { + prec->rpvt = new RPvt; + + } else { // pass==1 + + const pvd::StandardFieldPtr& sfld(pvd::getStandardField()); + + pvd::StructureConstPtr elem(sfld->scalarArray(pvd::pvDouble, "alarm,timeStamp")); + + prec->rpvt->root = pvd::FieldBuilder::begin() + ->add("idx", elem) + ->add("mean", elem) + ->add("std", elem) + ->add("min", elem) + ->add("max", elem) + ->createStructure()->build(); + + if(pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } + + prec->rpvt->valid = prec->rpvt->changed; + } + + return 0; + }catch(std::exception& e){ + fprintf(stderr, "%s: init_record error: %s\n", prec->name, e.what()); + return -1; + } +} + +long readValue(statBinRecord *prec, + statBindset *pdset) +{ + prec->rpvt->changed.clear(); + + long ret = pdset->read_arr(prec); + + return ret; +} + +void monitor(statBinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + monitor_mask |= DBE_VALUE | DBE_LOG; + + prec->rpvt->assign("idx.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + prec->rpvt->assign("min.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + prec->rpvt->assign("max.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + prec->rpvt->assign("mean.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + prec->rpvt->assign("std.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + + prec->rpvt->assign("idx.timeStamp.nanoseconds")->putFrom(prec->time.nsec); + prec->rpvt->assign("min.timeStamp.nanoseconds")->putFrom(prec->time.nsec); + prec->rpvt->assign("max.timeStamp.nanoseconds")->putFrom(prec->time.nsec); + prec->rpvt->assign("mean.timeStamp.nanoseconds")->putFrom(prec->time.nsec); + prec->rpvt->assign("std.timeStamp.nanoseconds")->putFrom(prec->time.nsec); + + prec->rpvt->assign("idx.alarm.severity")->putFrom(prec->sevr); + prec->rpvt->assign("min.alarm.severity")->putFrom(prec->sevr); + prec->rpvt->assign("max.alarm.severity")->putFrom(prec->sevr); + prec->rpvt->assign("mean.alarm.severity")->putFrom(prec->sevr); + prec->rpvt->assign("std.alarm.severity")->putFrom(prec->sevr); + + prec->rpvt->assign("idx.alarm.status")->putFrom(prec->stat ? 1 : 0); + prec->rpvt->assign("min.alarm.status")->putFrom(prec->stat ? 1 : 0); + prec->rpvt->assign("max.alarm.status")->putFrom(prec->stat ? 1 : 0); + prec->rpvt->assign("mean.alarm.status")->putFrom(prec->stat ? 1 : 0); + prec->rpvt->assign("std.alarm.status")->putFrom(prec->stat ? 1 : 0); + + std::string amsg(prec->amsg); + prec->rpvt->assign("idx.alarm.message")->put(amsg); + prec->rpvt->assign("min.alarm.message")->put(amsg); + prec->rpvt->assign("max.alarm.message")->put(amsg); + prec->rpvt->assign("mean.alarm.message")->put(amsg); + prec->rpvt->assign("std.alarm.message")->put(amsg); + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + } + + prec->rpvt->valid |= prec->rpvt->changed; +} + +long process(struct dbCommon *pcommon) +{ + statBinRecord *prec = (statBinRecord*)pcommon; + statBindset *pdset = (statBindset *)(prec->dset); + try { + + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->read_arr==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_arr"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_arr"); + return S_dev_missingSup; + + } else if(!prec->rpvt->root) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no table"); + return S_dev_NoInit; + } + + status=readValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; + }catch(std::exception& e){ + fprintf(stderr, "%s: process error: %s\n", prec->name, e.what()); + return -1; + } +} + +long cvt_dbaddr(DBADDR *paddr) +{ + // for VAL + + // we don't provide a valid DBR buffer + paddr->ro = 1; + // arbitrary limit + paddr->no_elements = 1; + + paddr->field_type = DBF_NOACCESS; + + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + // we provide vfield access + paddr->vfields = &vfPVStructureList; + } + + return 0; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + statBinRecord *prec = (statBinRecord*)paddr->precord; + + if(!prec->rpvt->root) + return S_db_notInit; + + if(p->vtype==&vfPVStructure) { + VSharedPVStructure *pstr = (VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + if(!*pstr->value) + return S_db_notInit; + (*pstr->value)->copy(*prec->rpvt->root); + *pstr->changed = prec->rpvt->valid; // TODO: distinguish initial from update + return 0; + } + + } else if(p->vtype==&vfStructure) { + VSharedStructure *pstr = (VSharedStructure*)p; + if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { + *pstr->value = prec->rpvt->root->getStructure(); + return 0; + } + } + return S_db_badChoice; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + return S_db_noMod; +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset statBinRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +long readLocked(struct link *pinp, void *raw) +{ + const bool* doload = static_cast(raw); + statBinRecord *prec = (statBinRecord *) pinp->precord; + + pvd::shared_vector arr; + + VSharedVector ival; + ival.vtype = &vfSharedVector; + ival.value = &arr; + + long status = *doload ? dbLoadLink(pinp, DBR_VFIELD, &ival) : dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status==S_db_badDbrtype) { + // TODO: fallback to alloc and copy + } + + if (!status) + status = statBin::consume(prec, arr); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +long init_record_soft(struct dbCommon *pcommon) +{ + statBinRecord *prec = (statBinRecord *)pcommon; + bool doload = true; + + if (readLocked(&prec->inp, &doload)) { + prec->udf = FALSE; + } + return 0; +} + +long read_arr_soft(statBinRecord* prec) +{ + bool doload = false; + long status = dbLinkDoLocked(&prec->inp, readLocked, &doload); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, &doload); + + if (!status && !dbLinkIsConstant(&prec->inp)) + prec->udf = FALSE; + + return status; +} + +statBindset devSBSoft = { + {5, NULL, NULL, &init_record_soft, NULL}, + &read_arr_soft +}; + +} // namespace + +extern "C" { +epicsExportAddress(rset,statBinRSET); +epicsExportAddress(dset, devSBSoft); +} diff --git a/pdbApp/statBinRecord.dbd b/pdbApp/statBinRecord.dbd new file mode 100644 index 0000000..ec35021 --- /dev/null +++ b/pdbApp/statBinRecord.dbd @@ -0,0 +1,52 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuFtype.dbd" + +recordtype(statBin) { + include "dbCommon.dbd" + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %struct statBinRecord; + %struct statBindset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*read_arr)(struct statBinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %}; + %QSRV_API + %long statBinConsume(struct statBinRecord *prec, short dbf, const void* arr, size_t nelem); + %#ifdef __cplusplus + %} // extern "C" + %namespace statBin { + %struct RPvt; + %QSRV_API + %long consume(struct statBinRecord *prec, const epics::pvData::shared_vector& arr); + %} // namespace statBin + %#endif + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("void* val") + } + field(RPVT,DBF_NOACCESS) { + special(SPC_NOMOD) + extra("::statBin::RPvt* rpvt") + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(DEC, DBF_ULONG) { + prompt("Decimation factor") + } +} + +device(statBin, CONSTANT, devSBSoft, "Soft Channel") From 623e3d3de4c25a9610574722866ccdbadf569416 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 10 Nov 2020 18:59:40 -0800 Subject: [PATCH 07/19] move VFieldType definitions --- pdbApp/Makefile | 13 +++++++------ pdbApp/columnarinRecord.cpp | 4 +++- pdbApp/pv/qsrv.h | 25 +++++++++++++++++++++++++ pdbApp/pvstructinRecord.cpp | 7 ++----- pdbApp/pvstructinRecord.dbd | 11 ----------- pdbApp/svectorinRecord.cpp | 6 ++---- pdbApp/svectorinRecord.dbd | 5 ----- pdbApp/vfield.cpp | 7 +++++++ 8 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 pdbApp/vfield.cpp diff --git a/pdbApp/Makefile b/pdbApp/Makefile index bb583ab..897d25a 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -60,15 +60,16 @@ else qsrv_SRCS += pvalink_null.cpp endif -qsrv_SRCS += svectorinRecord.c -qsrv_SRCS += devSVInSoft.c +qsrv_SRCS += vfield.cpp +qsrv_SRCS += svectorinRecord.cpp +qsrv_SRCS += devSVInSoft.cpp -qsrv_SRCS += pvstructinRecord.c -qsrv_SRCS += devPVSInSoft.c +qsrv_SRCS += pvstructinRecord.cpp +qsrv_SRCS += devPVSInSoft.cpp -qsrv_SRCS += columnarinRecord.c +qsrv_SRCS += columnarinRecord.cpp -qsrv_SRCS += statBinRecord.c +qsrv_SRCS += statBinRecord.cpp qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) diff --git a/pdbApp/columnarinRecord.cpp b/pdbApp/columnarinRecord.cpp index 4f07a31..fb870d5 100644 --- a/pdbApp/columnarinRecord.cpp +++ b/pdbApp/columnarinRecord.cpp @@ -24,7 +24,9 @@ #include #include -#include +#include +#include +#include #define GEN_SIZE_OFFSET #include diff --git a/pdbApp/pv/qsrv.h b/pdbApp/pv/qsrv.h index 452fade..575350e 100644 --- a/pdbApp/pv/qsrv.h +++ b/pdbApp/pv/qsrv.h @@ -37,6 +37,9 @@ #endif +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -82,6 +85,28 @@ QSRV_API void testqsrvShutdownOk(void); */ QSRV_API void testqsrvCleanup(void); +QSRV_API +extern const VFieldType vfSharedVector; +struct VSharedVector { + const VFieldType* vtype; + epics::pvData::shared_vector* value; +}; + +QSRV_API +extern const VFieldType vfStructure; +struct VSharedStructure { + const VFieldType* vtype; + epics::pvData::StructureConstPtr* value; +}; +QSRV_API +extern const VFieldType vfPVStructure; +struct VSharedPVStructure { + const VFieldType* vtype; + const epics::pvData::PVStructurePtr* value; + epics::pvData::BitSet* changed; +}; + + #ifdef __cplusplus } #endif diff --git a/pdbApp/pvstructinRecord.cpp b/pdbApp/pvstructinRecord.cpp index e54f3fd..e1fd825 100644 --- a/pdbApp/pvstructinRecord.cpp +++ b/pdbApp/pvstructinRecord.cpp @@ -26,16 +26,13 @@ #include "devSup.h" #include "epicsTime.h" +#include + #define GEN_SIZE_OFFSET #include #undef GEN_SIZE_OFFSET #include -extern "C" { -const VFieldType vfStructure = {"epics::pvData::StructureConstPtr"}; -const VFieldType vfPVStructure = {"epics::pvData::PVStructurePtr"}; -} - namespace { namespace pvd = epics::pvData; diff --git a/pdbApp/pvstructinRecord.dbd b/pdbApp/pvstructinRecord.dbd index 8bcea6d..1f98d9d 100644 --- a/pdbApp/pvstructinRecord.dbd +++ b/pdbApp/pvstructinRecord.dbd @@ -18,17 +18,6 @@ recordtype(pvstructin) { %#ifdef __cplusplus %extern "C" { %#endif - %QSRV_API extern const VFieldType vfStructure; - %struct VSharedStructure { - % const VFieldType* vtype; - % epics::pvData::StructureConstPtr* value; - %}; - %QSRV_API extern const VFieldType vfPVStructure; - %struct VSharedPVStructure { - % const VFieldType* vtype; - % const epics::pvData::PVStructurePtr* value; - % epics::pvData::BitSet* changed; - %}; %typedef struct pvstructindset { % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ % long (*read_pvs)(struct pvstructinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ diff --git a/pdbApp/svectorinRecord.cpp b/pdbApp/svectorinRecord.cpp index 71a0341..83c55c0 100644 --- a/pdbApp/svectorinRecord.cpp +++ b/pdbApp/svectorinRecord.cpp @@ -26,15 +26,13 @@ #include "devSup.h" #include "epicsTime.h" +#include + #define GEN_SIZE_OFFSET #include #undef GEN_SIZE_OFFSET #include -extern "C" { -const VFieldType vfSharedVector = {"epics::pvData::shared_vector"}; -} - namespace { namespace pvd = epics::pvData; diff --git a/pdbApp/svectorinRecord.dbd b/pdbApp/svectorinRecord.dbd index aa2af4f..74bb8d4 100644 --- a/pdbApp/svectorinRecord.dbd +++ b/pdbApp/svectorinRecord.dbd @@ -17,11 +17,6 @@ recordtype(svectorin) { %#ifdef __cplusplus %extern "C" { %#endif - %QSRV_API extern const VFieldType vfSharedVector; - %struct VSharedVector { - % const VFieldType* vtype; - % epics::pvData::shared_vector* value; - %}; %typedef struct svectorindset { % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ % long (*read_sharedvector)(struct svectorinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ diff --git a/pdbApp/vfield.cpp b/pdbApp/vfield.cpp new file mode 100644 index 0000000..94ff4a2 --- /dev/null +++ b/pdbApp/vfield.cpp @@ -0,0 +1,7 @@ + +#include + +const VFieldType vfSharedVector = {"epics::pvData::shared_vector"}; + +const VFieldType vfStructure = {"epics::pvData::StructureConstPtr"}; +const VFieldType vfPVStructure = {"epics::pvData::PVStructurePtr"}; From fa27d3260eb472c6b5f79a7a8735bde22ef3298d Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 10 Nov 2020 20:07:26 -0800 Subject: [PATCH 08/19] add svectorout --- pdbApp/Makefile | 6 + pdbApp/devSVOutSoft.cpp | 82 ++++++++++ pdbApp/svectoroutRecord.cpp | 292 ++++++++++++++++++++++++++++++++++++ pdbApp/svectoroutRecord.dbd | 69 +++++++++ 4 files changed, 449 insertions(+) create mode 100644 pdbApp/devSVOutSoft.cpp create mode 100644 pdbApp/svectoroutRecord.cpp create mode 100644 pdbApp/svectoroutRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 897d25a..8366706 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -23,6 +23,7 @@ INC += pv/qsrv.h INC += pv/qsrvVersionNum.h DBDINC += svectorinRecord +DBDINC += svectoroutRecord DBDINC += pvstructinRecord DBDINC += columnarinRecord DBDINC += statBinRecord @@ -62,7 +63,9 @@ endif qsrv_SRCS += vfield.cpp qsrv_SRCS += svectorinRecord.cpp +qsrv_SRCS += svectoroutRecord.cpp qsrv_SRCS += devSVInSoft.cpp +qsrv_SRCS += devSVOutSoft.cpp qsrv_SRCS += pvstructinRecord.cpp qsrv_SRCS += devPVSInSoft.cpp @@ -99,6 +102,7 @@ endif softIocPVA_DBD += softIoc.dbd softIocPVA_DBD += svectorinRecord.dbd +softIocPVA_DBD += svectoroutRecord.dbd softIocPVA_DBD += pvstructinRecord.dbd softIocPVA_DBD += columnarinRecord.dbd softIocPVA_DBD += statBinRecord.dbd @@ -113,6 +117,7 @@ include $(TOP)/configure/RULES # why is this not automatic! $(COMMON_DIR)/softIocPVA.dbd: ../svectorinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../svectoroutRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../pvstructinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../columnarinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../statBinRecord.dbd @@ -134,6 +139,7 @@ QSRVDBD = ../qsrv-old.dbd endif $(QSRVDBD) : ../svectorinRecord.dbd +$(QSRVDBD) : ../svectoroutRecord.dbd $(QSRVDBD) : ../pvstructinRecord.dbd ../O.Common/qsrv.dbd: $(QSRVDBD) diff --git a/pdbApp/devSVOutSoft.cpp b/pdbApp/devSVOutSoft.cpp new file mode 100644 index 0000000..38d5db7 --- /dev/null +++ b/pdbApp/devSVOutSoft.cpp @@ -0,0 +1,82 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include + +// include by svectoroutRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#include "svectoroutRecord.h" +#include + +namespace { + +namespace pvd = epics::pvData; + +long writeLocked(struct link *pinp, void *raw) +{ + svectoroutRecord *prec = (svectoroutRecord *) pinp->precord; + + VSharedVector ival; + ival.vtype = &vfSharedVector; + ival.value = &prec->val; + + long status = dbPutLink(pinp, DBR_VFIELD, &ival, 1); + + if (status==S_db_badDbrtype && prec->ftvl!=DBF_STRING) { + status=0; + + size_t esize = dbValueSize(prec->ftvl); + long nReq = prec->val.size() / esize; + + status = dbPutLink(pinp, prec->ftvl, prec->val.data(), nReq); + } + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; +} + +long init_record(struct dbCommon *pcommon) +{ + return 0; +} + +long write_svi(svectoroutRecord* prec) +{ + long status = dbLinkDoLocked(&prec->out, writeLocked, 0); + + if (status == S_db_noLSET) + status = writeLocked(&prec->out, 0); + + if (!status && !dbLinkIsConstant(&prec->out)) + prec->udf = FALSE; + + return status; +} + +svectoroutdset devSVOSoft = { + {5, NULL, NULL, &init_record, NULL}, + &write_svi +}; + +} +extern "C" { +epicsExportAddress(dset, devSVOSoft); +} diff --git a/pdbApp/svectoroutRecord.cpp b/pdbApp/svectoroutRecord.cpp new file mode 100644 index 0000000..ef5832e --- /dev/null +++ b/pdbApp/svectoroutRecord.cpp @@ -0,0 +1,292 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include +#include +#include +#include +#include + +// include by svectoroutRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace { + +namespace pvd = epics::pvData; + +ELLLIST vfSharedVectorList = ELLLIST_INIT; +VFieldTypeNode vfSharedVectorNode; + +long initialize() +{ + vfSharedVectorNode.vtype = &vfSharedVector; + ellAdd(&vfSharedVectorList, &vfSharedVectorNode.node); + return 0; +} + +long type_check(svectoroutRecord *prec) +{ + if(prec->val.empty() || prec->val.original_type()==prec->stvl) + return 0; + + errlogPrintf("%s error: device support attempts type change. Clear VAL\n", prec->name); + + prec->val.clear(); + + return S_db_badDbrtype; +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + svectoroutRecord *prec = (svectoroutRecord*)pcommon; + svectoroutdset *pdset = (svectoroutdset *)(prec->dset); + + if(pass==0) { + new (&prec->val) pvd::shared_vector(); + new (&prec->oval) pvd::shared_vector(); + + if (!pdset) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no DSET"); + recGblRecordError(S_dev_noDSET, prec, "svectorout: no DSET"); + return S_dev_noDSET; + } + + switch(prec->ftvl) { + #define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBF_##DBFTYPE: prec->stvl = pvd::pv##PVACODE; break; + #define CASE_SKIP_BOOL + #define CASE_REAL_INT64 + #include + #undef CASE_SKIP_BOOL + #undef CASE_REAL_INT64 + #undef CASE + // not supporting ENUM or STRING + default: + prec->stvl = (pvd::ScalarType)-1; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad FTVL"); + recGblRecordError(S_db_badDbrtype, prec, "svectorout: bad FTVL"); + return S_db_badDbrtype; + } + + } else { // pass==1 + + if(pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(!ret) + ret = type_check(prec); + if(ret) + return ret; + + prec->oval = prec->val; + } + } + return 0; +} + +long readValue(svectoroutRecord *prec, + svectoroutdset *pdset) +{ + long ret = pdset->write_sharedvector(prec); + + if(!ret) + ret = type_check(prec); + + return ret; +} + +void monitor(svectoroutRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + if ((prec->oval.dataPtr()!=prec->val.dataPtr()) + || (prec->oval.dataOffset()!=prec->val.dataOffset()) + || (prec->oval.size()!=prec->val.size())) { + monitor_mask |= DBE_VALUE | DBE_LOG; + prec->oval = prec->val; + } + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + db_post_events(prec, &prec->oval, monitor_mask); + } +} + +long process(struct dbCommon *pcommon) +{ + svectoroutRecord *prec = (svectoroutRecord*)pcommon; + svectoroutdset *pdset = (svectoroutdset *)(prec->dset); + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->write_sharedvector==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_svectorout"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_svectorout"); + return S_dev_missingSup; + + } else if(prec->stvl == (pvd::ScalarType)-1) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad FTVL"); + } + + status=readValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; +} + +long cvt_dbaddr(DBADDR *paddr) +{ + svectoroutRecord *prec = (svectoroutRecord*)paddr->precord; + // for both VAL and OVAL + + // we don't insist devsup allocate the required NELM + paddr->ro = 1; + // we provide vfield access + paddr->vfields = &vfSharedVectorList; + // arbitrary limit + paddr->no_elements = prec->nelm; + + paddr->field_type = paddr->dbr_field_type = prec->ftvl; + paddr->field_size = 1; + + + return 0; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + svectoroutRecord *prec = (svectoroutRecord*)paddr->precord; + + if(prec->stvl == (pvd::ScalarType)-1) { + return S_db_badDbrtype; + } + + size_t esize = pvd::ScalarTypeFunc::elementSize(prec->stvl); + + *offset = 0; + if(dbGetFieldIndex(paddr)==svectoroutRecordVAL) { + paddr->pfield = (void*)prec->val.data(); + *no_elements = prec->val.size()/esize; + if(paddr->no_elements < *no_elements) + *no_elements = paddr->no_elements; + return 0; + + } else if(dbGetFieldIndex(paddr)==svectoroutRecordOVAL) { + paddr->pfield = (void*)prec->oval.data(); + *no_elements = prec->oval.size()/esize; + return 0; + } + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + svectoroutRecord *prec = (svectoroutRecord*)paddr->precord; + + if(p->vtype==&vfSharedVector) { + VSharedVector *pstr = (VSharedVector*)p; + if(dbGetFieldIndex(paddr)==svectoroutRecordVAL) { + *pstr->value = prec->val; + return 0; + } else if(dbGetFieldIndex(paddr)==svectoroutRecordOVAL) { + *pstr->value = prec->oval; + return 0; + } + } + return S_db_badChoice; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + svectoroutRecord *prec = (svectoroutRecord*)paddr->precord; + + if(p->vtype==&vfSharedVector) { + const VSharedVector *pstr = (const VSharedVector*)p; + if(dbGetFieldIndex(paddr)==svectoroutRecordVAL) { + prec->val = *pstr->value; + return 0; + } else if(dbGetFieldIndex(paddr)==svectoroutRecordOVAL) { + prec->oval = *pstr->value; + return 0; + } + } + return S_db_badChoice; +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset svectoroutRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +} // namespace + +extern "C" { +epicsExportAddress(rset,svectoroutRSET); +} diff --git a/pdbApp/svectoroutRecord.dbd b/pdbApp/svectoroutRecord.dbd new file mode 100644 index 0000000..8f269c8 --- /dev/null +++ b/pdbApp/svectoroutRecord.dbd @@ -0,0 +1,69 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuFtype.dbd" + +recordtype(svectorout) { + include "dbCommon.dbd" + %/* Declare Device Support Entry Table */ + %#include + %#include + %#include + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %typedef struct svectoroutdset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*write_sharedvector)(struct svectoroutRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %} svectoroutdset; + %#define HAS_svectoroutdset + %#ifdef __cplusplus + %} + %#endif + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("::epics::pvData::shared_vector val") + } + field(OVAL,DBF_NOACCESS) { + prompt("Previous Value") + interest(3) + special(SPC_DBADDR) + extra("::epics::pvData::shared_vector oval") + } + field(OUT,DBF_INLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } + field(NELM,DBF_ULONG) { + prompt("Number of Elements") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + initial("1") + } + field(FTVL,DBF_MENU) { + prompt("Field Type of Value") + promptgroup("30 - Action") + special(SPC_NOMOD) + interest(1) + menu(menuFtype) + } + field(STVL,DBF_NOACCESS) { + prompt("ScalarType of Value") + special(SPC_NOMOD) + interest(5) + extra("epics::pvData::ScalarType stvl") + } +} + +device(svectorout, CONSTANT, devSVOSoft, "Soft Channel") From ff55d0d5c8571aa157afad9ccb4194ddee71891e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 23 Nov 2020 10:30:27 -0800 Subject: [PATCH 09/19] add ndainRecord --- pdbApp/Makefile | 9 + pdbApp/devNDAInSoft.cpp | 126 +++++++++++++ pdbApp/ndainRecord.cpp | 391 ++++++++++++++++++++++++++++++++++++++++ pdbApp/ndainRecord.dbd | 91 ++++++++++ 4 files changed, 617 insertions(+) create mode 100644 pdbApp/devNDAInSoft.cpp create mode 100644 pdbApp/ndainRecord.cpp create mode 100644 pdbApp/ndainRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 8366706..7801fec 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -27,6 +27,7 @@ DBDINC += svectoroutRecord DBDINC += pvstructinRecord DBDINC += columnarinRecord DBDINC += statBinRecord +DBDINC += ndainRecord EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION @@ -74,6 +75,9 @@ qsrv_SRCS += columnarinRecord.cpp qsrv_SRCS += statBinRecord.cpp +qsrv_SRCS += ndainRecord.cpp +qsrv_SRCS += devNDAInSoft.cpp + qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -106,6 +110,7 @@ softIocPVA_DBD += svectoroutRecord.dbd softIocPVA_DBD += pvstructinRecord.dbd softIocPVA_DBD += columnarinRecord.dbd softIocPVA_DBD += statBinRecord.dbd +softIocPVA_DBD += ndainRecord.dbd softIocPVA_DBD += PVAServerRegister.dbd softIocPVA_DBD += qsrv.dbd @@ -121,6 +126,7 @@ $(COMMON_DIR)/softIocPVA.dbd: ../svectoroutRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../pvstructinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../columnarinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../statBinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../ndainRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd # Can't use EXPAND as generated headers must appear @@ -141,6 +147,9 @@ endif $(QSRVDBD) : ../svectorinRecord.dbd $(QSRVDBD) : ../svectoroutRecord.dbd $(QSRVDBD) : ../pvstructinRecord.dbd +$(QSRVDBD) : ../columnarinRecord.dbd +$(QSRVDBD) : ../statBinRecord.dbd +$(QSRVDBD) : ../ndainRecord.dbd ../O.Common/qsrv.dbd: $(QSRVDBD) $(CP) $< $@ diff --git a/pdbApp/devNDAInSoft.cpp b/pdbApp/devNDAInSoft.cpp new file mode 100644 index 0000000..4fd844a --- /dev/null +++ b/pdbApp/devNDAInSoft.cpp @@ -0,0 +1,126 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include + +// include by ndainRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" +#include "alarm.h" +#include "errlog.h" + +#include + +#include "ndainRecord.h" +#include + +namespace { + +namespace pvd = epics::pvData; + +long init_record(struct dbCommon *pcommon) +{ + return 0; +} + +long readLocked(struct link *pinp, void *raw) +{ + ndainRecord *prec = (ndainRecord *) pinp->precord; + + VSharedVector ival; + ival.vtype = &vfSharedVector; + ival.value = &prec->val; + + // prefer reading shared_vector directly + long status = dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status==S_db_badDbrtype && !dbLinkIsConstant(pinp)) { + // fallback to reading (and copying) DBF array + status=0; + + int dbf = dbGetLinkDBFtype(pinp); + pvd::ScalarType stype; + + switch(dbf) { +#define CASE_SKIP_BOOL +#define CASE_REAL_INT64 +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBF_##DBFTYPE: stype = pvd::pv##PVACODE; break; +#include +#undef CASE +#undef CASE_SKIP_BOOL +#undef CASE_REAL_INT64 + default: + // include DBF_STRING + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad Img DBF %d", dbf); + return S_db_badDbrtype; + } + + long nelem = 0; + + if(dbf==-1 || (status=dbGetNelements(pinp, &nelem))) { + return status; + } + + size_t esize = dbValueSize(dbf); + try { + pvd::shared_vector temp(nelem*esize); + + status = dbGetLink(pinp, dbf, temp.data(), 0, &nelem); + + if(!status) { + temp.resize(nelem*esize); + prec->val = pvd::static_shared_vector_cast(pvd::freeze(temp)); + prec->val.set_original_type(stype); + } + } catch(std::exception& e) { + errlogPrintf("%s: Error during alloc/copy of DBF -> PVD: %s\n", prec->name, e.what()); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "DBF CPY %s", e.what()); + status = S_rec_outMem; /* this is likely an allocation error */ + } + } + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStampTag(pinp, &prec->time, &prec->utag); + + return status; +} + +long read_nda(ndainRecord* prec) +{ + long status = 0; + + if(!status) + status = dbLinkDoLocked(&prec->inp, readLocked, 0); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, 0); + + if (!status && !dbLinkIsConstant(&prec->inp)) + prec->udf = FALSE; + + return status; +} + +ndaindset devNDAISoft = { + {5, NULL, NULL, &init_record, NULL}, + &read_nda +}; + +} +extern "C" { +epicsExportAddress(dset, devNDAISoft); +} diff --git a/pdbApp/ndainRecord.cpp b/pdbApp/ndainRecord.cpp new file mode 100644 index 0000000..b37e664 --- /dev/null +++ b/pdbApp/ndainRecord.cpp @@ -0,0 +1,391 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include +#include +#include +#include +#include +#include + +// include by ndainRecord.h +#include "epicsTypes.h" +#include "link.h" +#include "epicsMutex.h" +#include "ellLib.h" +#include "devSup.h" +#include "epicsTime.h" + +#include +#include +#include +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace pvd = epics::pvData; + +struct ndainPvt +{ +}; + +namespace { + +ELLLIST vfPVStructureList = ELLLIST_INIT; +VFieldTypeNode vfPVStructureNode[2]; + +pvd::StructureConstPtr ntndarray; + +long initialize() +{ + vfPVStructureNode[0].vtype = &vfStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[0].node); + vfPVStructureNode[1].vtype = &vfPVStructure; + ellAdd(&vfPVStructureList, &vfPVStructureNode[1].node); + + pvd::FieldConstPtr any = pvd::getFieldCreate()->createVariantUnion(); + pvd::StandardFieldPtr sf = pvd::StandardField::getStandardField(); + pvd::StructureConstPtr timeStamp = sf->timeStamp(); + + // avoid pulling in normativeTypeCPP dependency + ntndarray = pvd::FieldBuilder::begin() + ->setId("epics:nt/NTNDArray:1.0") + ->addNestedUnion("value") + ->addArray("booleanValue", pvd::pvBoolean) + ->addArray("byteValue", pvd::pvByte) + ->addArray("shortValue", pvd::pvShort) + ->addArray("intValue", pvd::pvInt) + ->addArray("longValue", pvd::pvLong) + ->addArray("ubyteValue", pvd::pvUByte) + ->addArray("ushortValue", pvd::pvUShort) + ->addArray("uintValue", pvd::pvUInt) + ->addArray("ulongValue", pvd::pvULong) + ->addArray("floatValue", pvd::pvFloat) + ->addArray("doubleValue", pvd::pvDouble) + ->endNested() + ->addNestedStructure("codec") + ->setId("codec_t") + ->add("name", pvd::pvString) + ->add("parameters", any) + ->endNested() + ->add("compressedSize", pvd::pvLong) + ->add("uncompressedSize", pvd::pvLong) + ->add("uniqueId", pvd::pvInt) + ->add("dataTimeStamp", timeStamp) + ->add("alarm", sf->alarm()) + ->add("timeStamp", timeStamp) + ->addNestedStructureArray("dimension") + ->setId("dimension_t") + ->add("size", pvd::pvInt) + ->add("offset", pvd::pvInt) + ->add("fullSize", pvd::pvInt) + ->add("binning", pvd::pvInt) + ->add("reverse", pvd::pvBoolean) + ->endNested() + ->addNestedStructureArray("attribute") + ->setId("epics:nt/NTAttribute:1.0") + ->add("name", pvd::pvString) + ->add("value", any) + ->addArray("tags", pvd::pvString) + ->add("descriptor", pvd::pvString) + ->add("alarm", sf->alarm()) + ->add("timeStamp", timeStamp) + ->add("sourceType", pvd::pvInt) + ->add("source", pvd::pvString) + ->endNested() + ->createStructure(); + + return 0; +} + + +long init_record(struct dbCommon *pcommon, int pass) +{ + ndainRecord *prec = (ndainRecord*)pcommon; + ndaindset *pdset = (ndaindset *)(prec->dset); + + if(pass==0) { + new (&prec->val) pvd::shared_vector(); + prec->rpvt = new ndainPvt; + new (&prec->root) pvd::PVStructurePtr(); + new (&prec->chg) pvd::BitSet(); + new (&prec->vld) pvd::BitSet(); + pvd::PVStructurePtr root = prec->root = ntndarray->build(); + + if (!pdset) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no DSET"); + recGblRecordError(S_dev_noDSET, prec, "ndain: no DSET"); + return S_dev_noDSET; + } + + } else { // pass==1 + + if(pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } + } + return 0; +} + +long readValue(ndainRecord *prec, + ndaindset *pdset) +{ + epicsUInt32 origShape[2] = {prec->w, prec->h}; + epicsInt32 oldID = prec->id; + long status = 0; + + if(!status && !dbLinkIsConstant(&prec->inh)) + status=dbGetLink(&prec->inh, DBF_ULONG, &prec->h, 0, 0); + + if(!status && !dbLinkIsConstant(&prec->inw)) + status=dbGetLink(&prec->inw, DBF_ULONG, &prec->w, 0, 0); + + if(status) + return status; + + prec->chg.clear(); + + status = pdset->read_ndarray(prec); + + pvd::PVUnionPtr value = prec->root->getSubFieldT("value"); + + switch(prec->val.original_type()) { + case pvd::pvBoolean: value->select("booleanValue")->putFrom(prec->val); break; + case pvd::pvByte: value->select("byteValue")->putFrom(prec->val); break; + case pvd::pvShort: value->select("shortValue")->putFrom(prec->val); break; + case pvd::pvInt: value->select("intValue")->putFrom(prec->val); break; + case pvd::pvLong: value->select("longValue")->putFrom(prec->val); break; + case pvd::pvUByte: value->select("ubyteValue")->putFrom(prec->val); break; + case pvd::pvUShort: value->select("ushortValue")->putFrom(prec->val); break; + case pvd::pvUInt: value->select("uintValue")->putFrom(prec->val); break; + case pvd::pvULong: value->select("ulongValue")->putFrom(prec->val); break; + case pvd::pvFloat: value->select("floatValue")->putFrom(prec->val); break; + case pvd::pvDouble: value->select("doubleValue")->putFrom(prec->val); break; + default: + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "unsup arr type %u", (unsigned)prec->val.original_type()); + // fall through + case (pvd::ScalarType)-1: + value->select(pvd::PVUnion::UNDEFINED_INDEX); + break; + } + prec->chg.set(value->getFieldOffset()); + + pvd::PVStructureArrayPtr dims = prec->root->getSubFieldT("dimension"); + { + if(origShape[0]!=prec->w) + db_post_events(prec, &prec->w, DBE_VALUE|DBE_ARCHIVE); + if(origShape[1]!=prec->h) + db_post_events(prec, &prec->h, DBE_VALUE|DBE_ARCHIVE); + + pvd::StructureConstPtr elemType = dims->getStructureArray()->getStructure(); + pvd::PVStructureArray::svector arr(2); + arr[0] = elemType->build(); + arr[1] = elemType->build(); + + arr[0]->getSubFieldT("size")->put(prec->w); + arr[1]->getSubFieldT("size")->put(prec->h); + + dims->replace(pvd::freeze(arr)); + } + prec->chg.set(dims->getFieldOffset()); + + if(oldID!=prec->id) { + pvd::PVIntPtr id(prec->root->getSubFieldT("uniqueId")); + id->put(prec->id); + prec->chg.set(id->getFieldOffset()); + } + + prec->vld |= prec->chg; + + return status; +} + +void monitor(ndainRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + if(monitor_mask&DBE_ALARM) { + pvd::PVScalarPtr fld(prec->root->getSubFieldT("alarm.severity")); + fld->putFrom(prec->sevr); + prec->chg.set(fld->getFieldOffset()); + + //TODO: map status properly + fld = prec->root->getSubFieldT("alarm.status"); + fld->putFrom(prec->stat ? 1 : 0); + prec->chg.set(fld->getFieldOffset()); + + fld = prec->root->getSubFieldT("alarm.message"); + fld->putFrom(std::string(prec->amsg)); + prec->chg.set(fld->getFieldOffset()); + } + + { + pvd::PVScalarPtr fld(prec->root->getSubFieldT("timeStamp.secondsPastEpoch")); + fld->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + prec->chg.set(fld->getFieldOffset()); + + fld = prec->root->getSubFieldT("timeStamp.nanoseconds"); + fld->putFrom(prec->time.nsec); + prec->chg.set(fld->getFieldOffset()); + + fld = prec->root->getSubFieldT("timeStamp.userTag"); + fld->putFrom(prec->utag); + prec->chg.set(fld->getFieldOffset()); + + monitor_mask |= DBE_VALUE|DBE_ARCHIVE; + } + + db_post_events(prec, &prec->val, monitor_mask); +} + +long process(struct dbCommon *pcommon) +{ + ndainRecord *prec = (ndainRecord*)pcommon; + ndaindset *pdset = (ndaindset *)(prec->dset); + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->read_ndarray==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_ndain"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_ndain"); + return S_dev_missingSup; + } + + status=readValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; +} + +long cvt_dbaddr(DBADDR *paddr) +{ + ndainRecord *prec = (ndainRecord*)paddr->precord; + // for both VAL and OVAL + + // we don't insist devsup allocate the required NELM + paddr->ro = 1; + // we provide vfield access + paddr->vfields = &vfPVStructureList; + // arbitrary limit + paddr->no_elements = 1; + + paddr->field_type = DBF_NOACCESS; + + return 0; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + ndainRecord *prec = (ndainRecord*)paddr->precord; + + if(p->vtype==&vfPVStructure) { + VSharedPVStructure *pstr = (VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==ndainRecordVAL) { + if(!*pstr->value) + return S_db_notInit; + (*pstr->value)->copy(*prec->root); + *pstr->changed = prec->vld; + return 0; + } + + } else if(p->vtype==&vfStructure) { + VSharedStructure *pstr = (VSharedStructure*)p; + if(dbGetFieldIndex(paddr)==ndainRecordVAL) { + *pstr->value = ntndarray; + return 0; + } + } + return S_db_badChoice; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + ndainRecord *prec = (ndainRecord*)paddr->precord; + + if(p->vtype==&vfPVStructure) { + const VSharedPVStructure *pstr = (const VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==ndainRecordVAL) { + prec->root->copy(**pstr->value); + prec->vld |= *pstr->changed; + return 0; + } + } + return S_db_badChoice; +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset ndainRSET={ + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +} // namespace + +extern "C" { +epicsExportAddress(rset,ndainRSET); +} diff --git a/pdbApp/ndainRecord.dbd b/pdbApp/ndainRecord.dbd new file mode 100644 index 0000000..b15b984 --- /dev/null +++ b/pdbApp/ndainRecord.dbd @@ -0,0 +1,91 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +menu(menuColor) { + choice(menuColorGray, "GRAY") + # ... RGB8 etc. +} + +recordtype(ndain) { + include "dbCommon.dbd" + %/* Declare Device Support Entry Table */ + %#include + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %typedef struct ndaindset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*read_ndarray)(struct ndainRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %} ndaindset; + %#define HAS_ndaindset + %struct ndainPvt; + %#ifdef __cplusplus + %} + %#endif + field(VAL,DBF_NOACCESS) { + prompt("Pixel array") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("::epics::pvData::shared_vector val") + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } + field(H, DBF_ULONG) { + prompt("Height in pixels") + interest(3) + special(SPC_NOMOD) + } + field(INH,DBF_INLINK) { + prompt("Read height") + promptgroup("40 - Input") + interest(3) + } + field(W, DBF_ULONG) { + prompt("Width in pixels") + interest(3) + special(SPC_NOMOD) + } + field(INW,DBF_INLINK) { + prompt("Read width") + promptgroup("40 - Input") + interest(3) + } + field(COL, DBF_MENU) { + prompt("Color Mode") + interest(3) + special(SPC_NOMOD) + menu(menuColor) + } + field(ID, DBF_LONG) { + prompt("Unique ID") + interest(3) + special(SPC_NOMOD) + } + field(RPVT, DBF_NOACCESS) { + extra("struct ndainPvt *rpvt") + } + field(ROOT,DBF_NOACCESS) { + special(SPC_NOMOD) + extra("::epics::pvData::PVStructurePtr root") + } + field(CHG,DBF_NOACCESS) { + special(SPC_NOMOD) + extra("::epics::pvData::BitSet chg") + } + field(VLD,DBF_NOACCESS) { + special(SPC_NOMOD) + extra("::epics::pvData::BitSet vld") + } +} + +device(ndain, CONSTANT, devNDAISoft, "Soft Channel") From d3ab74690d86857bca91905585b974d9a1ae63f9 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 23 Nov 2020 11:03:20 -0800 Subject: [PATCH 10/19] ndainRecord demo --- iocBoot/iocimagedemo/ndain.db | 6 +++ iocBoot/iocimagedemo/st.cmd | 1 + pdbApp/demo.cpp | 79 +++++++++++++++++++++++++++++++++++ pdbApp/qsrv-new.dbd | 4 ++ 4 files changed, 90 insertions(+) create mode 100644 iocBoot/iocimagedemo/ndain.db diff --git a/iocBoot/iocimagedemo/ndain.db b/iocBoot/iocimagedemo/ndain.db new file mode 100644 index 0000000..4398122 --- /dev/null +++ b/iocBoot/iocimagedemo/ndain.db @@ -0,0 +1,6 @@ +record(ndain, "$(N):Array") { + field(DTYP, "QSRV Demo") + field(H, "1024") + field(W, "768") + field(PINI, "YES") +} diff --git a/iocBoot/iocimagedemo/st.cmd b/iocBoot/iocimagedemo/st.cmd index 3fbb80d..4ed7b68 100755 --- a/iocBoot/iocimagedemo/st.cmd +++ b/iocBoot/iocimagedemo/st.cmd @@ -1,6 +1,7 @@ #!../../bin/linux-x86_64-debug/softIocPVA dbLoadRecords("image.db","N=TST:image1") +dbLoadRecords("ndain.db","N=TST:image2") dbLoadRecords("table.db","N=TST:table1") dbLoadRecords("ntenum.db","P=TST:enum1") diff --git a/pdbApp/demo.cpp b/pdbApp/demo.cpp index c5030d1..e7bd6f4 100644 --- a/pdbApp/demo.cpp +++ b/pdbApp/demo.cpp @@ -1,4 +1,6 @@ +#define USE_TYPED_DSET + #include #include #include @@ -8,12 +10,18 @@ #include #include +#include #include +#include #include namespace { +namespace pvd = epics::pvData; + +// 2*pi +const double two_pi = 6.283185307179586; // pi/180 static const double pi_180 = 0.017453292519943295; @@ -68,6 +76,67 @@ long process_utag(longinRecord *prec) return status; } +struct imgPvt { + double phase; +}; + +long init_img(dbCommon *pcom) +{ + ndainRecord *prec = (ndainRecord*)pcom; + try { + imgPvt * pvt = new imgPvt; + pvt->phase = 0.0; + prec->dpvt = pvt; + + return 0; + }catch(std::exception& e){ + errlogPrintf("%s: init_img error: %s\n", prec->name, e.what()); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "DEMO ERR: %s", e.what()); + return -1; + } +} + +long process_img(ndainRecord *prec) +{ + if(!prec->dpvt) + return -1; + + imgPvt *pvt = (imgPvt*)prec->dpvt; + try { + pvd::shared_vector pixels(prec->w * prec->h); + + // place blob center offset from image center by 1/4th + double centerX = prec->w/2 + cos(pvt->phase)*prec->w/4.0, + centerY = prec->h/2 + sin(pvt->phase)*prec->h/4.0; + double sigma = (prec->w < prec->h ? prec->w : prec->h)/4.0; + sigma = 2.0*sigma*sigma; + + for(epicsUInt32 h=0u; hh; h++) { + for(epicsUInt32 w=0u; ww; w++) { + double distX = w - centerX, + distY = h - centerY, + dist2 = distX*distX + distY*distY; + + pixels[h*prec->w + w] = (pvd::uint8)( 255.0 * exp(-dist2/sigma) ); + } + } + + for(epicsUInt32 w=0u; ww; w++) { + pixels[prec->h/2*prec->w + w] = 255u; + } + + prec->val = pvd::static_shared_vector_cast(pvd::freeze(pixels)); + + pvt->phase = fmod(pvt->phase + two_pi/60.0, two_pi); + + return 0; + }catch(std::exception& e){ + errlogPrintf("%s: process_img error: %s\n", prec->name, e.what()); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "DEMO ERR: %s", e.what()); + return -1; + } +} + template struct dset5 { @@ -82,9 +151,19 @@ struct dset5 dset5 devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin}; dset5 devLoPDBUTag = {5,0,0,0,0,&process_utag}; +ndaindset devNDAIPDBDemo = { + { + 5, 0, 0, + &init_img, + 0, + }, + &process_img, +}; + } // namespace extern "C" { epicsExportAddress(dset, devWfPDBDemo); epicsExportAddress(dset, devLoPDBUTag); +epicsExportAddress(dset, devNDAIPDBDemo); } diff --git a/pdbApp/qsrv-new.dbd b/pdbApp/qsrv-new.dbd index 6ca4d9e..1d6557d 100644 --- a/pdbApp/qsrv-new.dbd +++ b/pdbApp/qsrv-new.dbd @@ -20,3 +20,7 @@ variable(pvaLinkNWorkers, int) include "svectorinRecord.dbd" include "pvstructinRecord.dbd" include "statBinRecord.dbd" +include "ndainRecord.dbd" + +# from demo.cpp +device(ndain, CONSTANT, devNDAIPDBDemo, "QSRV Demo") From 47414c107d05c2b56995a12a1666774ace831e4c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sat, 28 Nov 2020 10:44:29 -0800 Subject: [PATCH 11/19] compat --- pdbApp/demo.cpp | 8 ++++++++ pdbApp/pv/qsrv.h | 4 ++++ pdbApp/vfield.cpp | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/pdbApp/demo.cpp b/pdbApp/demo.cpp index e7bd6f4..ee978c6 100644 --- a/pdbApp/demo.cpp +++ b/pdbApp/demo.cpp @@ -80,6 +80,8 @@ struct imgPvt { double phase; }; +#ifdef QSRV_HAS_VFIELD + long init_img(dbCommon *pcom) { ndainRecord *prec = (ndainRecord*)pcom; @@ -137,6 +139,8 @@ long process_img(ndainRecord *prec) } } +#endif // QSRV_HAS_VFIELD + template struct dset5 { @@ -151,6 +155,7 @@ struct dset5 dset5 devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin}; dset5 devLoPDBUTag = {5,0,0,0,0,&process_utag}; +#ifdef QSRV_HAS_VFIELD ndaindset devNDAIPDBDemo = { { 5, 0, 0, @@ -159,11 +164,14 @@ ndaindset devNDAIPDBDemo = { }, &process_img, }; +#endif } // namespace extern "C" { epicsExportAddress(dset, devWfPDBDemo); epicsExportAddress(dset, devLoPDBUTag); +#ifdef QSRV_HAS_VFIELD epicsExportAddress(dset, devNDAIPDBDemo); +#endif } diff --git a/pdbApp/pv/qsrv.h b/pdbApp/pv/qsrv.h index 575350e..e292f77 100644 --- a/pdbApp/pv/qsrv.h +++ b/pdbApp/pv/qsrv.h @@ -85,6 +85,9 @@ QSRV_API void testqsrvShutdownOk(void); */ QSRV_API void testqsrvCleanup(void); +#if EPICS_VERSION_INT>=VERSION_INT(7,0,0,1) +#define QSRV_HAS_VFIELD + QSRV_API extern const VFieldType vfSharedVector; struct VSharedVector { @@ -106,6 +109,7 @@ struct VSharedPVStructure { epics::pvData::BitSet* changed; }; +#endif #ifdef __cplusplus } diff --git a/pdbApp/vfield.cpp b/pdbApp/vfield.cpp index 94ff4a2..7dc0e30 100644 --- a/pdbApp/vfield.cpp +++ b/pdbApp/vfield.cpp @@ -1,7 +1,11 @@ #include +#ifdef QSRV_HAS_VFIELD + const VFieldType vfSharedVector = {"epics::pvData::shared_vector"}; const VFieldType vfStructure = {"epics::pvData::StructureConstPtr"}; const VFieldType vfPVStructure = {"epics::pvData::PVStructurePtr"}; + +#endif From 436472dd93d91dbcefd3ecc2171a3366da92e24d Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Nov 2020 19:55:32 -0800 Subject: [PATCH 12/19] update qsrv.dbd --- pdbApp/Makefile | 39 +++++++++--------------- pdbApp/demo.cpp | 8 ++--- pdbApp/pv/qsrv.h | 3 +- pdbApp/{qsrv-old.dbd => qsrv-common.dbd} | 4 --- pdbApp/qsrv-lset.dbd | 1 + pdbApp/qsrv-new.dbd | 26 ---------------- pdbApp/qsrv-vfield.dbd | 10 ++++++ pdbApp/vfield.cpp | 2 +- 8 files changed, 32 insertions(+), 61 deletions(-) rename pdbApp/{qsrv-old.dbd => qsrv-common.dbd} (85%) create mode 100644 pdbApp/qsrv-lset.dbd delete mode 100644 pdbApp/qsrv-new.dbd create mode 100644 pdbApp/qsrv-vfield.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 7801fec..8cda7df 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -1,6 +1,10 @@ TOP=.. include $(TOP)/configure/CONFIG +# cfg/ sometimes isn't correctly included due to a Base bug +# so we do here (maybe again) as workaround +include $(TOP)/configure/CONFIG_QSRV_VERSION + #---------------------------------------- # ADD MACRO DEFINITIONS AFTER THIS LINE #============================= @@ -99,18 +103,20 @@ softIocPVA_LIBS += $(EPICS_BASE_IOC_LIBS) DBD += softIocPVA.dbd DBD += qsrv.dbd +qsrv_pieces += ../qsrv-common.dbd +ifdef BASE_3_16 +qsrv_pieces += ../qsrv-lset.dbd +endif +ifeq (YES,$(HAS_DBR_VFIELD)) +qsrv_pieces += ../qsrv-vfield.dbd +endif + ifneq ($(FINAL_LOCATION),$(EPICS_BASE)) # Copy from Base if we won't be installed there DB_INSTALLS += $(EPICS_BASE)/db/softIocExit.db endif softIocPVA_DBD += softIoc.dbd -softIocPVA_DBD += svectorinRecord.dbd -softIocPVA_DBD += svectoroutRecord.dbd -softIocPVA_DBD += pvstructinRecord.dbd -softIocPVA_DBD += columnarinRecord.dbd -softIocPVA_DBD += statBinRecord.dbd -softIocPVA_DBD += ndainRecord.dbd softIocPVA_DBD += PVAServerRegister.dbd softIocPVA_DBD += qsrv.dbd @@ -138,24 +144,9 @@ $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd qsrv$(DEP): ../O.Common/pv/qsrvVersionNum.h pvalink$(DEP): ../O.Common/pv/qsrvVersionNum.h -ifdef BASE_3_16 -QSRVDBD = ../qsrv-new.dbd -else -QSRVDBD = ../qsrv-old.dbd -endif - -$(QSRVDBD) : ../svectorinRecord.dbd -$(QSRVDBD) : ../svectoroutRecord.dbd -$(QSRVDBD) : ../pvstructinRecord.dbd -$(QSRVDBD) : ../columnarinRecord.dbd -$(QSRVDBD) : ../statBinRecord.dbd -$(QSRVDBD) : ../ndainRecord.dbd - -../O.Common/qsrv.dbd: $(QSRVDBD) - $(CP) $< $@ - -qsrv.dbd$(DEP): +qsrv.dbd$(DEP): $(qsrv_pieces) @$(RM) $@ @echo "$(COMMONDEP_TARGET): ../Makefile" > $@ -../O.Common/softIocPVA.dbd: ../O.Common/qsrv.dbd +$(COMMON_DIR)/qsrv.dbd: $(qsrv_pieces) + $(CAT) $(filter %.dbd,$^) > $@ diff --git a/pdbApp/demo.cpp b/pdbApp/demo.cpp index ee978c6..c93fb74 100644 --- a/pdbApp/demo.cpp +++ b/pdbApp/demo.cpp @@ -80,7 +80,7 @@ struct imgPvt { double phase; }; -#ifdef QSRV_HAS_VFIELD +#ifdef DBR_VFIELD long init_img(dbCommon *pcom) { @@ -139,7 +139,7 @@ long process_img(ndainRecord *prec) } } -#endif // QSRV_HAS_VFIELD +#endif // DBR_VFIELD template struct dset5 @@ -155,7 +155,7 @@ struct dset5 dset5 devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin}; dset5 devLoPDBUTag = {5,0,0,0,0,&process_utag}; -#ifdef QSRV_HAS_VFIELD +#ifdef DBR_VFIELD ndaindset devNDAIPDBDemo = { { 5, 0, 0, @@ -171,7 +171,7 @@ ndaindset devNDAIPDBDemo = { extern "C" { epicsExportAddress(dset, devWfPDBDemo); epicsExportAddress(dset, devLoPDBUTag); -#ifdef QSRV_HAS_VFIELD +#ifdef DBR_VFIELD epicsExportAddress(dset, devNDAIPDBDemo); #endif } diff --git a/pdbApp/pv/qsrv.h b/pdbApp/pv/qsrv.h index e292f77..a233080 100644 --- a/pdbApp/pv/qsrv.h +++ b/pdbApp/pv/qsrv.h @@ -85,8 +85,7 @@ QSRV_API void testqsrvShutdownOk(void); */ QSRV_API void testqsrvCleanup(void); -#if EPICS_VERSION_INT>=VERSION_INT(7,0,0,1) -#define QSRV_HAS_VFIELD +#ifdef DBR_VFIELD QSRV_API extern const VFieldType vfSharedVector; diff --git a/pdbApp/qsrv-old.dbd b/pdbApp/qsrv-common.dbd similarity index 85% rename from pdbApp/qsrv-old.dbd rename to pdbApp/qsrv-common.dbd index 54dedaa..05b9edd 100644 --- a/pdbApp/qsrv-old.dbd +++ b/pdbApp/qsrv-common.dbd @@ -15,7 +15,3 @@ variable(PDBProviderDebug, int) # Number of worker threads for handling monitor updates. # Default: 1 variable(pvaLinkNWorkers, int) - -include "svectorinRecord.dbd" -include "pvstructinRecord.dbd" -include "statBinRecord.dbd" diff --git a/pdbApp/qsrv-lset.dbd b/pdbApp/qsrv-lset.dbd new file mode 100644 index 0000000..fc7b4a3 --- /dev/null +++ b/pdbApp/qsrv-lset.dbd @@ -0,0 +1 @@ +link("pva", "lsetPVA") diff --git a/pdbApp/qsrv-new.dbd b/pdbApp/qsrv-new.dbd deleted file mode 100644 index 1d6557d..0000000 --- a/pdbApp/qsrv-new.dbd +++ /dev/null @@ -1,26 +0,0 @@ -# Changes to this file may require incrementing -# the ABI version in CONFIG_QSRV_VERSION - -registrar(QSRVRegistrar) -registrar(installPVAAddLinkHook) -link("pva", "lsetPVA") - -# from demo.cpp -device(waveform, CONSTANT, devWfPDBDemo, "QSRV Demo") -device(longin, CONSTANT, devLoPDBUTag, "QSRV Set UTag") -# from imagedemo.c -function(QSRV_image_demo) -# from pdb.cpp -# Extra debug info when parsing group definitions -variable(PDBProviderDebug, int) -# Number of worker threads for handling monitor updates. -# Default: 1 -variable(pvaLinkNWorkers, int) - -include "svectorinRecord.dbd" -include "pvstructinRecord.dbd" -include "statBinRecord.dbd" -include "ndainRecord.dbd" - -# from demo.cpp -device(ndain, CONSTANT, devNDAIPDBDemo, "QSRV Demo") diff --git a/pdbApp/qsrv-vfield.dbd b/pdbApp/qsrv-vfield.dbd new file mode 100644 index 0000000..fb1f7cf --- /dev/null +++ b/pdbApp/qsrv-vfield.dbd @@ -0,0 +1,10 @@ +include "svectorinRecord.dbd" +include "svectoroutRecord.dbd" +include "columnarinRecord.dbd" +include "columnarinRecord.dbd" +include "statBinRecord.dbd" +include "ndainRecord.dbd" +include "pvstructinRecord.dbd" + +# from demo.cpp +device(ndain, CONSTANT, devNDAIPDBDemo, "QSRV Demo") diff --git a/pdbApp/vfield.cpp b/pdbApp/vfield.cpp index 7dc0e30..71acb28 100644 --- a/pdbApp/vfield.cpp +++ b/pdbApp/vfield.cpp @@ -1,7 +1,7 @@ #include -#ifdef QSRV_HAS_VFIELD +#ifdef DBR_VFIELD const VFieldType vfSharedVector = {"epics::pvData::shared_vector"}; From f4d0c5c8a85135aab9e6cff042f848a12b197600 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 23 Nov 2020 19:25:40 -0800 Subject: [PATCH 13/19] multiArrayCommon --- pdbApp/Makefile | 18 ++ pdbApp/columnarinRecord.cpp | 228 ++++------------- pdbApp/columnarinRecord.dbd | 22 +- pdbApp/columnaroutRecord.cpp | 206 +++++++++++++++ pdbApp/columnaroutRecord.dbd | 31 +++ pdbApp/menuMultiArrayLayout.dbd | 4 + pdbApp/multiArrayCommon.cpp | 408 ++++++++++++++++++++++++++++++ pdbApp/multiArrayCommon.dbd | 81 ++++++ pdbApp/multiArrayCommonRecord.dbd | 11 + pdbApp/statBinRecord.cpp | 253 +++++++----------- pdbApp/statBinRecord.dbd | 18 +- 11 files changed, 915 insertions(+), 365 deletions(-) create mode 100644 pdbApp/columnaroutRecord.cpp create mode 100644 pdbApp/columnaroutRecord.dbd create mode 100644 pdbApp/menuMultiArrayLayout.dbd create mode 100644 pdbApp/multiArrayCommon.cpp create mode 100644 pdbApp/multiArrayCommon.dbd create mode 100644 pdbApp/multiArrayCommonRecord.dbd diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 8cda7df..5aff455 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -26,12 +26,16 @@ USR_CPPFLAGS += -I$(TOP)/common -I$(TOP)/p2pApp INC += pv/qsrv.h INC += pv/qsrvVersionNum.h +DBD += menuMultiArrayLayout.dbd + DBDINC += svectorinRecord DBDINC += svectoroutRecord DBDINC += pvstructinRecord DBDINC += columnarinRecord +DBDINC += columnaroutRecord DBDINC += statBinRecord DBDINC += ndainRecord +DBDINC += multiArrayCommon EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION @@ -76,10 +80,12 @@ qsrv_SRCS += pvstructinRecord.cpp qsrv_SRCS += devPVSInSoft.cpp qsrv_SRCS += columnarinRecord.cpp +qsrv_SRCS += columnaroutRecord.cpp qsrv_SRCS += statBinRecord.cpp qsrv_SRCS += ndainRecord.cpp +qsrv_SRCS += multiArrayCommon.cpp qsrv_SRCS += devNDAInSoft.cpp qsrv_LIBS += pvAccess pvData @@ -131,6 +137,7 @@ $(COMMON_DIR)/softIocPVA.dbd: ../svectorinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../svectoroutRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../pvstructinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../columnarinRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../columnaroutRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../statBinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../ndainRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd @@ -144,6 +151,17 @@ $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd qsrv$(DEP): ../O.Common/pv/qsrvVersionNum.h pvalink$(DEP): ../O.Common/pv/qsrvVersionNum.h +# cf. specials for dbCommon.h in modules/database/src/ioc/db/RULES + +multiArrayCommon.h$(DEP): ../multiArrayCommonRecord.dbd ../multiArrayCommon.dbd + @$(RM) $@ + @$(DBTORECORDTYPEH) -D $(DBDFLAGS) -o $(COMMONDEP_TARGET) $< > $@ + +$(COMMON_DIR)/multiArrayCommon.h: ../multiArrayCommonRecord.dbd ../multiArrayCommon.dbd + @$(RM) $(notdir $@) + $(DBTORECORDTYPEH) $(DBDFLAGS) -o $(notdir $@) $< + @$(MV) $(notdir $@) $@ + qsrv.dbd$(DEP): $(qsrv_pieces) @$(RM) $@ @echo "$(COMMONDEP_TARGET): ../Makefile" > $@ diff --git a/pdbApp/columnarinRecord.cpp b/pdbApp/columnarinRecord.cpp index fb870d5..2248f6d 100644 --- a/pdbApp/columnarinRecord.cpp +++ b/pdbApp/columnarinRecord.cpp @@ -35,155 +35,53 @@ namespace pvd = epics::pvData; -namespace columnarin { - -struct RPvt { - pvd::shared_vector labels; - pvd::FieldBuilderPtr builder; - pvd::PVStructurePtr tbl; - pvd::BitSet valid, changed; -}; - -void add_column(struct columnarinRecord *prec, const char* fname, const char* label, ::epics::pvData::ScalarType type) -{ - if(!prec->rpvt->builder) - throw std::logic_error("Only from init_record()"); - - prec->rpvt->builder = prec->rpvt->builder->addArray(fname, type); - prec->rpvt->labels.push_back(label ? label : fname); -} - -void set_column(struct columnarinRecord *prec, const char* fname, const ::epics::pvData::shared_vector& cdata) -{ - if(!prec->rpvt->tbl) - throw std::logic_error("Only from read_tbl()"); - - pvd::PVScalarArrayPtr col(prec->rpvt->tbl->getSubFieldT("value")->getSubFieldT(fname)); - col->putFrom(cdata); - prec->rpvt->changed.set(col->getFieldOffset()); - -} - -} // namespace columnarin - namespace { -using namespace columnarin; - -ELLLIST vfPVStructureList = ELLLIST_INIT; -VFieldTypeNode vfPVStructureNode[2]; long initialize() { - vfPVStructureNode[0].vtype = &vfStructure; - ellAdd(&vfPVStructureList, &vfPVStructureNode[0].node); - vfPVStructureNode[1].vtype = &vfPVStructure; - ellAdd(&vfPVStructureList, &vfPVStructureNode[1].node); - return 0; + return multiArray::initialize(); } long init_record(struct dbCommon *pcommon, int pass) { columnarinRecord *prec = (columnarinRecord*)pcommon; columnarindset *pdset = (columnarindset *)(prec->dset); - try { - - if (!pdset) { - if(pass==0) - recGblRecordError(S_dev_noDSET, prec, "pvstructin: no DSET"); - return S_dev_noDSET; - } - - if(pass==0) { - prec->rpvt = new RPvt; - - } else { // pass==1 - prec->rpvt->builder = pvd::FieldBuilder::begin() - ->setId("epics:nt/NTTable:1.0") - ->addArray("labels", pvd::pvString) - ->addNestedStructure("value"); - - if(pdset->common.init_record) { - long ret = pdset->common.init_record(pcommon); - if(ret) - return ret; - } + /* overall ordering + * pass==0 + * - check dset + * - multiArray::init_record(, 0) + * pass==1 + * - dset::init_record() -- calls add_column() + * - multiArray::init_record(, 1) + */ + + if (!pdset && pass==0) { + recGblRecordError(S_dev_noDSET, prec, "columnarin: no DSET"); + return S_dev_noDSET; + } - const pvd::StandardFieldPtr& sfld(pvd::getStandardField()); + if(pass==1 && pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } - prec->rpvt->tbl = prec->rpvt->builder->endNested() - ->add("alarm", sfld->alarm()) - ->add("timeStamp", sfld->timeStamp()) - ->createStructure()->build(); + long status = multiArray::init_record(pcommon, pass); - pvd::PVStringArrayPtr lbls(prec->rpvt->tbl->getSubFieldT("labels")); - lbls->replace(pvd::freeze(prec->rpvt->labels)); - prec->rpvt->valid.set(lbls->getFieldOffset()); - } - - return 0; - }catch(std::exception& e){ - fprintf(stderr, "%s: init_record error: %s\n", prec->name, e.what()); - return -1; - } + return status; } long readValue(columnarinRecord *prec, columnarindset *pdset) { - prec->rpvt->changed.clear(); - - long ret = pdset->read_tbl(prec); - - return ret; + return pdset->read_tbl(prec); } void monitor(columnarinRecord *prec) { int monitor_mask = recGblResetAlarms(prec); - { - // was set_column called during read_tbl()? - pvd::int32 bit = prec->rpvt->changed.nextSetBit(0u); - if(bit>=0 && pvd::uint32(bit)rpvt->tbl->getNextFieldOffset()) - monitor_mask |= DBE_VALUE|DBE_LOG; - } - - if(monitor_mask&DBE_ALARM) { - pvd::PVScalarPtr fld(prec->rpvt->tbl->getSubFieldT("alarm.severity")); - fld->putFrom(prec->sevr); - prec->rpvt->changed.set(fld->getFieldOffset()); - - //TODO: map status properly - fld = prec->rpvt->tbl->getSubFieldT("alarm.status"); - fld->putFrom(prec->stat ? 1 : 0); - prec->rpvt->changed.set(fld->getFieldOffset()); - - fld = prec->rpvt->tbl->getSubFieldT("alarm.message"); - fld->putFrom(std::string(prec->amsg)); - prec->rpvt->changed.set(fld->getFieldOffset()); - } - - { - pvd::PVScalarPtr fld(prec->rpvt->tbl->getSubFieldT("timeStamp.secondsPastEpoch")); - fld->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); - prec->rpvt->changed.set(fld->getFieldOffset()); - - fld = prec->rpvt->tbl->getSubFieldT("timeStamp.nanoseconds"); - fld->putFrom(prec->time.nsec); - prec->rpvt->changed.set(fld->getFieldOffset()); - - fld = prec->rpvt->tbl->getSubFieldT("timeStamp.userTag"); - fld->putFrom(prec->utag); - prec->rpvt->changed.set(fld->getFieldOffset()); - } - - monitor_mask |= DBE_VALUE | DBE_LOG; - - if (monitor_mask) { - db_post_events(prec, &prec->val, monitor_mask); - } - - prec->rpvt->valid |= prec->rpvt->changed; + multiArray::monitor(prec, monitor_mask); } long process(struct dbCommon *pcommon) @@ -197,11 +95,11 @@ long process(struct dbCommon *pcommon) if( (pdset==NULL) || (pdset->read_tbl==NULL) ) { prec->pact=TRUE; - recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_pvs"); - recGblRecordError(S_dev_missingSup,(void *)prec,"read_pvs"); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_tbl"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_tbl"); return S_dev_missingSup; - } else if(!prec->rpvt->tbl) { + } else if(!prec->val) { prec->pact=TRUE; recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no table"); return S_dev_NoInit; @@ -229,21 +127,7 @@ long process(struct dbCommon *pcommon) long cvt_dbaddr(DBADDR *paddr) { - // for VAL - - // we don't provide a valid DBR buffer - paddr->ro = 1; - // arbitrary limit - paddr->no_elements = 1; - - paddr->field_type = DBF_NOACCESS; - - if(dbGetFieldIndex(paddr)==columnarinRecordVAL) { - // we provide vfield access - paddr->vfields = &vfPVStructureList; - } - - return 0; + return multiArray::cvt_dbaddr(paddr); } long get_array_info(DBADDR *paddr, long *no_elements, long *offset) @@ -258,34 +142,12 @@ long put_array_info(DBADDR *paddr, long nNew) long get_vfield(struct dbAddr *paddr, struct VField *p) { - columnarinRecord *prec = (columnarinRecord*)paddr->precord; - - if(!prec->rpvt->tbl) - return S_db_notInit; - - if(p->vtype==&vfPVStructure) { - VSharedPVStructure *pstr = (VSharedPVStructure*)p; - if(dbGetFieldIndex(paddr)==columnarinRecordVAL) { - if(!*pstr->value) - return S_db_notInit; - (*pstr->value)->copy(*prec->rpvt->tbl); - *pstr->changed = prec->rpvt->valid; // TODO: distinguish initial from update - return 0; - } - - } else if(p->vtype==&vfStructure) { - VSharedStructure *pstr = (VSharedStructure*)p; - if(dbGetFieldIndex(paddr)==columnarinRecordVAL) { - *pstr->value = prec->rpvt->tbl->getStructure(); - return 0; - } - } - return S_db_badChoice; + return multiArray::get_vfield(paddr, p); } long put_vfield(struct dbAddr *paddr, const struct VField *p) { - return S_db_noMod; + return multiArray::put_vfield(paddr, p); } #define report NULL @@ -323,19 +185,41 @@ rset columnarinRSET = { put_vfield, }; -long init_record_soft(dbCommon *prec) +long readLocked(struct link *pinp, void *raw) { - return 0; + columnarinRecord *prec = (columnarinRecord *) pinp->precord; + + VSharedPVStructure ival; + ival.vtype = &vfPVStructure; + ival.value = &prec->val; + ival.changed = &prec->vld; + + long status = dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + return status; } long read_tbl_soft(columnarinRecord* prec) { - (void)recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); - return 0; + long status = dbLinkDoLocked(&prec->inp, readLocked, 0); + + if (status == S_db_noLSET) + status = readLocked(&prec->inp, 0); + + if (!status && !dbLinkIsConstant(&prec->inp)) + prec->udf = FALSE; + + return status; } columnarindset devCOLISoft = { - {5, NULL, NULL, &init_record_soft, NULL}, + {5, NULL, NULL, NULL, NULL}, &read_tbl_soft }; diff --git a/pdbApp/columnarinRecord.dbd b/pdbApp/columnarinRecord.dbd index b24e64b..7cabb23 100644 --- a/pdbApp/columnarinRecord.dbd +++ b/pdbApp/columnarinRecord.dbd @@ -4,20 +4,13 @@ # in file LICENSE that is included with this distribution. #************************************************************************* -include "menuFtype.dbd" +include "menuMultiArrayLayout.dbd" recordtype(columnarin) { - include "dbCommon.dbd" + include "multiArrayCommon.dbd" %#include %#include %struct columnarinRecord; - %namespace columnarin { - %struct RPvt; - %QSRV_API - %void add_column(struct columnarinRecord *prec, const char* fname, const char* label, ::epics::pvData::ScalarType type); - %QSRV_API - %void set_column(struct columnarinRecord *prec, const char* fname, const ::epics::pvData::shared_vector& cdata); - %} // namespace columnarin %#ifdef __cplusplus %extern "C" { %#endif @@ -28,17 +21,6 @@ recordtype(columnarin) { %#ifdef __cplusplus %} // extern "C" %#endif - field(VAL,DBF_NOACCESS) { - prompt("Current Value") - asl(ASL0) - pp(TRUE) - special(SPC_DBADDR) - extra("void* val") - } - field(RPVT,DBF_NOACCESS) { - special(SPC_NOMOD) - extra("::columnarin::RPvt* rpvt") - } field(INP,DBF_INLINK) { prompt("Input Specification") promptgroup("40 - Input") diff --git a/pdbApp/columnaroutRecord.cpp b/pdbApp/columnaroutRecord.cpp new file mode 100644 index 0000000..18aee7e --- /dev/null +++ b/pdbApp/columnaroutRecord.cpp @@ -0,0 +1,206 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace pvd = epics::pvData; + +namespace { + +long initialize() +{ + return multiArray::initialize(); +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + columnaroutRecord *prec = (columnaroutRecord*)pcommon; + columnaroutdset *pdset = (columnaroutdset *)(prec->dset); + /* overall ordering + * pass==0 + * - check dset + * - multiArray::init_record(, 0) + * pass==1 + * - dset::init_record() -- calls add_column() + * - multiArray::init_record(, 1) + */ + + if (!pdset && pass==0) { + recGblRecordError(S_dev_noDSET, prec, "columnarout: no DSET"); + return S_dev_noDSET; + } + + if(pass==1 && pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } + + long status = multiArray::init_record(pcommon, pass); + + return status; +} + +long writeValue(columnaroutRecord *prec, + columnaroutdset *pdset) +{ + return pdset->write_tbl(prec); +} + +void monitor(columnaroutRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + multiArray::monitor(prec, monitor_mask); +} + +long process(struct dbCommon *pcommon) +{ + columnaroutRecord *prec = (columnaroutRecord*)pcommon; + columnaroutdset *pdset = (columnaroutdset *)(prec->dset); + try { + + unsigned char pact=prec->pact; + long status; + + if( (pdset==NULL) || (pdset->write_tbl==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no write_tbl"); + recGblRecordError(S_dev_missingSup,(void *)prec,"write_tbl"); + return S_dev_missingSup; + + } else if(!prec->val) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no table"); + return S_dev_NoInit; + } + + status=writeValue(prec, pdset); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return status; + }catch(std::exception& e){ + fprintf(stderr, "%s: process error: %s\n", prec->name, e.what()); + return -1; + } +} + +long cvt_dbaddr(DBADDR *paddr) +{ + return multiArray::cvt_dbaddr(paddr); +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + return multiArray::get_vfield(paddr, p); +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + return multiArray::put_vfield(paddr, p); +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset columnaroutRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +long write_tbl_soft(columnaroutRecord* prec) +{ + (void)recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + // TODO: custom link support to aggregate a list of arrays into a table? + return 0; +} + +columnaroutdset devCOLOSoft = { + {5, NULL, NULL, NULL, NULL}, + &write_tbl_soft +}; + + +} // namespace + +extern "C" { +epicsExportAddress(rset,columnaroutRSET); +epicsExportAddress(dset, devCOLOSoft); +} diff --git a/pdbApp/columnaroutRecord.dbd b/pdbApp/columnaroutRecord.dbd new file mode 100644 index 0000000..a524da9 --- /dev/null +++ b/pdbApp/columnaroutRecord.dbd @@ -0,0 +1,31 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuMultiArrayLayout.dbd" + +recordtype(columnarout) { + include "multiArrayCommon.dbd" + %#include + %#include + %struct columnaroutRecord; + %#ifdef __cplusplus + %extern "C" { + %#endif + %struct columnaroutdset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*write_tbl)(struct columnaroutRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %}; + %#ifdef __cplusplus + %} // extern "C" + %#endif + field(OUT,DBF_INLINK) { + prompt("Output Specification") + promptgroup("50 - Output") + interest(1) + } +} + +device(columnarout, CONSTANT, devCOLOSoft, "Soft Channel") diff --git a/pdbApp/menuMultiArrayLayout.dbd b/pdbApp/menuMultiArrayLayout.dbd new file mode 100644 index 0000000..9a0c0d9 --- /dev/null +++ b/pdbApp/menuMultiArrayLayout.dbd @@ -0,0 +1,4 @@ +menu(menuMultiArrayLayout) { + choice(menuMultiArrayLayoutComposite, "Composite") + choice(menuMultiArrayLayoutTable, "Table") +} diff --git a/pdbApp/multiArrayCommon.cpp b/pdbApp/multiArrayCommon.cpp new file mode 100644 index 0000000..23337a1 --- /dev/null +++ b/pdbApp/multiArrayCommon.cpp @@ -0,0 +1,408 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include + +#define GEN_SIZE_OFFSET +#include "multiArrayCommon.h" +#undef GEN_SIZE_OFFSET +#include + +namespace multiArray { + +namespace pvd = epics::pvData; + +// used during rset init_record to store column definitions and initial values +struct EntryStorage { + std::string name, label; + pvd::ScalarType type; + pvd::shared_vector value; +}; + +struct RPvt { + bool finished; + typedef std::list entries_t; + entries_t entries; + typedef std::map entries_map_t; + entries_map_t entries_map; + + RPvt() :finished(false) {} +}; + +static +void storeAlarm(pvd::PVStructure& root, pvd::BitSet& vld, + unsigned short sevr, const char* amsg) +{ + pvd::PVScalarPtr fld(root.getSubFieldT("alarm.severity")); + fld->putFrom(sevr); + vld.set(fld->getFieldOffset()); + + //TODO: map status properly + fld = root.getSubFieldT("alarm.status"); + fld->putFrom(sevr ? 1 : 0); + vld.set(fld->getFieldOffset()); + + fld = root.getSubFieldT("alarm.message"); + fld->putFrom(std::string(amsg)); + vld.set(fld->getFieldOffset()); +} + +static +void storeTime(pvd::PVStructure& root, pvd::BitSet& vld, + const epicsTimeStamp& time, epicsInt32 utag) +{ + pvd::PVScalarPtr fld(root.getSubFieldT("timeStamp.secondsPastEpoch")); + fld->putFrom(time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); + vld.set(fld->getFieldOffset()); + + fld = root.getSubFieldT("timeStamp.nanoseconds"); + fld->putFrom(time.nsec); + vld.set(fld->getFieldOffset()); + + fld = root.getSubFieldT("timeStamp.userTag"); + fld->putFrom(utag); + vld.set(fld->getFieldOffset()); +} + +void add_column(void *pvoid, const char* fname, const char* label, pvd::ScalarType type) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)pvoid; + + if(prec->rpvt->finished) + throw std::logic_error("can't add_column() after init_record()"); + + if(prec->rpvt->entries_map.find(fname)!=prec->rpvt->entries_map.end()) + throw std::logic_error("Can't add_column() with duplicate name"); + + prec->rpvt->entries.push_back(EntryStorage()); + EntryStorage& ent = prec->rpvt->entries.back(); + prec->rpvt->entries_map[fname] = &ent; + + ent.name = fname; + ent.label = label ? label : fname; + ent.type = type; +} + +void add_columns(void *pvoid, const Entry* cols) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)pvoid; + + for(;cols->name; ++cols) { + add_column(prec, cols->name, cols->label, cols->type); + } +} + +void set_column(void *pvoid, const char* fname, const pvd::shared_vector& cdata, const ColMeta* pmeta) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)pvoid; + + RPvt::entries_map_t::iterator it(prec->rpvt->entries_map.find(fname)); + if(it==prec->rpvt->entries_map.end()) + throw std::logic_error("No such column"); + + EntryStorage& ent = *it->second; + + if(!prec->rpvt->finished) { + // save for later during init_record(, 1) + ent.value = cdata; + // TODO: save meta + + } else { + // called from process() + + if(prec->lay==menuMultiArrayLayoutTable) { + pvd::PVScalarArrayPtr arr(prec->val->getSubFieldT("value")->getSubFieldT(ent.name)); + arr->putFrom(cdata); + prec->vld.set(arr->getFieldOffset()); + + } else if(prec->lay==menuMultiArrayLayoutComposite) { + pvd::PVStructurePtr base(prec->val->getSubFieldT(ent.name)); + + pvd::PVScalarArrayPtr arr(base->getSubFieldT("value")); + arr->putFrom(cdata); + prec->vld.set(arr->getFieldOffset()); + + if(pmeta) { + storeAlarm(*base, prec->vld, pmeta->sevr, pmeta->amsg ? pmeta->amsg : ""); + storeTime(*base, prec->vld, pmeta->time, pmeta->utag); + } + } + } +} + +void get_column(void *pvoid, const char* fname, ::epics::pvData::shared_vector& cdata, ColMeta* pmeta) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)pvoid; + + RPvt::entries_map_t::iterator it(prec->rpvt->entries_map.find(fname)); + if(it==prec->rpvt->entries_map.end()) + throw std::logic_error("No such column"); + + EntryStorage& ent = *it->second; + + if(!prec->rpvt->finished) { + cdata = ent.value; + + } else { + if(prec->lay==menuMultiArrayLayoutTable) { + prec->val->getSubFieldT(ent.name)->getAs(cdata); + + } else if(prec->lay==menuMultiArrayLayoutComposite) { + pvd::PVStructurePtr base(prec->val->getSubFieldT(ent.name)); + base->getSubFieldT("value")->getAs(cdata); + + if(pmeta) { + // TODO: extract alarm/time + } + } + } +} + +#define TRY try +#define CATCH() catch(std::exception& e) { \ + (void)recGblSetSevrMsg(prec, COMM_ALARM, INVALID_ALARM, "exc: %s", e.what()); \ + errlogPrintf("%s: unhandled exception: %s\n", prec->name, e.what()); \ +} + + +ELLLIST vfList = ELLLIST_INIT; +VFieldTypeNode vfNodes[2]; + +long initialize() +{ + vfNodes[0].vtype = &vfStructure; + ellAdd(&vfList, &vfNodes[0].node); + vfNodes[1].vtype = &vfPVStructure; + ellAdd(&vfList, &vfNodes[1].node); + return 0; +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)pcommon; + TRY { + if(pass==0) { + prec->rpvt = new RPvt; + new (&prec->val) pvd::PVStructurePtr(); + new (&prec->vld) pvd::BitSet; + + // caller may now add_column() + + return 0; + + } else if(pass==1) { + // caller has made all calls to add_column() + + prec->rpvt->finished = true; + + const pvd::StandardFieldPtr& sfld(pvd::getStandardField()); + + pvd::FieldBuilderPtr builder = pvd::FieldBuilder::begin(); + + if(prec->lay==menuMultiArrayLayoutTable) { + + pvd::shared_vector labels; + labels.reserve(prec->rpvt->entries.size()); + + builder = builder->setId("epics:nt/NTTable:1.0") + ->addArray("labels", pvd::pvString) + ->addNestedStructure("value"); + + for(RPvt::entries_t::const_iterator it(prec->rpvt->entries.begin()), + end(prec->rpvt->entries.end()); it!=end; ++it) + { + const EntryStorage& ent = *it; + + builder = builder->addArray(ent.name, ent.type); + labels.push_back(ent.label); + } + + builder = builder->endNested() + ->add("alarm", sfld->alarm()) + ->add("timeStamp", sfld->timeStamp()); + + prec->val = builder->createStructure()->build(); + + pvd::PVStringArrayPtr flabels(prec->val->getSubFieldT("labels")); + flabels->replace(pvd::freeze(labels)); + prec->vld.set(flabels->getFieldOffset()); + + pvd::PVStructurePtr value(prec->val->getSubFieldT("value")); + + for(RPvt::entries_t::const_iterator it(prec->rpvt->entries.begin()), + end(prec->rpvt->entries.end()); it!=end; ++it) + { + const EntryStorage& ent = *it; + + if(!ent.value.empty()) + continue; + + pvd::PVScalarArrayPtr fld(value->getSubFieldT(ent.name)); + fld->putFrom(ent.value); + prec->vld.set(fld->getFieldOffset()); + } + + } else if(prec->lay==menuMultiArrayLayoutComposite) { + + for(RPvt::entries_t::const_iterator it(prec->rpvt->entries.begin()), + end(prec->rpvt->entries.end()); it!=end; ++it) + { + const EntryStorage& ent = *it; + + pvd::StructureConstPtr substruct(sfld->scalarArray(ent.type, "alarm,timeStamp")); + + builder = builder->add(ent.name, substruct); + } + + prec->val = builder->createStructure()->build(); + + for(RPvt::entries_t::iterator it(prec->rpvt->entries.begin()), + end(prec->rpvt->entries.end()); it!=end; ++it) + { + EntryStorage& ent = *it; + + if(!ent.value.empty()) + continue; + + pvd::PVStructurePtr base(prec->val->getSubFieldT(ent.name)); + + pvd::PVScalarArrayPtr fld(base->getSubFieldT("value")); + fld->putFrom(ent.value); + ent.value = pvd::shared_vector(); // clear + prec->vld.set(fld->getFieldOffset()); + + // TODO initial alarm/time + } + + } else { + errlogPrintf("%s.LAY: out of range value %d\n", prec->name, prec->lay); + (void)recGblSetSevrMsg(prec, COMM_ALARM, INVALID_ALARM, "Bad LAY %d", prec->lay); + return -1; + } + + return 0; + } + }CATCH() + return -1; +} + +void monitor(void *pvoid, unsigned short monitor_mask) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)pvoid; + + if(monitor_mask&DBE_ALARM) { + // sync record alarm into PVD structure + if(prec->lay==menuMultiArrayLayoutTable) { + storeAlarm(*prec->val, prec->vld, prec->sevr, prec->amsg); + + } else if(prec->lay==menuMultiArrayLayoutComposite) { + // no top level alarm + } + } + + if(prec->lay==menuMultiArrayLayoutTable) { + storeTime(*prec->val, prec->vld, prec->time, prec->utag); + + } else if(prec->lay==menuMultiArrayLayoutComposite) { + // no top level timestamp + } + + if (monitor_mask) { + db_post_events(prec, &prec->val, monitor_mask); + } +} + +long cvt_dbaddr(DBADDR *paddr) +{ + dbCommon *prec = paddr->precord; + TRY { + // for VAL + + // we don't provide a valid DBR buffer + paddr->ro = 1; + // arbitrary limit + paddr->no_elements = 1; + + paddr->field_type = DBF_NOACCESS; + + if(dbGetFieldIndex(paddr)==multiArrayCommonRecordVAL) { + // we provide vfield access + paddr->vfields = &vfList; + } + + return 0; + }CATCH() + return -1; +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)paddr->precord; + TRY { + + if(!prec->val) + return S_db_notInit; + + if(p->vtype==&vfPVStructure) { + VSharedPVStructure *pstr = (VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==multiArrayCommonRecordVAL) { + if(!*pstr->value) + return S_db_notInit; + (*pstr->value)->copy(*prec->val); + *pstr->changed = prec->vld; + return 0; + } + + } else if(p->vtype==&vfStructure) { + VSharedStructure *pstr = (VSharedStructure*)p; + if(dbGetFieldIndex(paddr)==multiArrayCommonRecordVAL) { + *pstr->value = prec->val->getStructure(); + return 0; + } + } + return S_db_badChoice; + }CATCH() + return -1; +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + multiArrayCommonRecord *prec = (multiArrayCommonRecord*)paddr->precord; + TRY { + if(!prec->val) + return S_db_notInit; + + if(p->vtype==&vfPVStructure) { + const VSharedPVStructure *pstr = (const VSharedPVStructure*)p; + if(dbGetFieldIndex(paddr)==multiArrayCommonRecordVAL) { + prec->val->copy(**pstr->value); + prec->vld |= *pstr->changed; + } + } + return S_db_badChoice; + }CATCH() + return -1; +} + +} // namespace multiArray diff --git a/pdbApp/multiArrayCommon.dbd b/pdbApp/multiArrayCommon.dbd new file mode 100644 index 0000000..6aaf817 --- /dev/null +++ b/pdbApp/multiArrayCommon.dbd @@ -0,0 +1,81 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + include "dbCommon.dbd" + %#include + %#include + %#include + %#include + % + %#ifndef multiArrayCommonRecordDef + %#define multiArrayCommonRecordDef + %namespace multiArray { + %struct RPvt; + %struct Entry { + % const char* name; + % const char* label; + % ::epics::pvData::ScalarType type; + %}; + %struct ColMeta { + % epicsUInt16 sevr; + % const char *amsg; + % epicsTimeStamp time; + % epicsUTag utag; + % ColMeta() :sevr(0), amsg(0), utag(0) { time.secPastEpoch = 0; time.nsec = 0;} + %}; + %// Call from device support init_record(), define new column + %QSRV_API + %void add_column(void *prec, const char* fname, const char* label, ::epics::pvData::ScalarType type); + %// Call from device support init_record(), define new columns + %QSRV_API + %void add_columns(void *prec, const Entry* cols); + %// Call from device support init_record() and/or process(), Set value of column. + %QSRV_API + %void set_column(void *prec, const char* fname, const ::epics::pvData::shared_vector& cdata, const ColMeta* =0); + %QSRV_API + %void get_column(void *prec, const char* fname, ::epics::pvData::shared_vector& cdata, ColMeta* =0); + % + %// Call from sub-record support + %QSRV_API + %long initialize(); + %QSRV_API + %long init_record(struct dbCommon *pcommon, int pass); + %QSRV_API + %void monitor(void* prec, unsigned short monitor_mask); + %QSRV_API + %long cvt_dbaddr(DBADDR *paddr); + %QSRV_API + %long get_array_info(DBADDR *paddr, long *no_elements, long *offset); + %QSRV_API + %long put_array_info(DBADDR *paddr, long nNew); + %QSRV_API + %long get_vfield(struct dbAddr *paddr, struct VField *p); + %QSRV_API + %long put_vfield(struct dbAddr *paddr, const struct VField *p); + %} // namespace multiArray + %#endif // multiArrayCommonRecordDef + field(RPVT,DBF_NOACCESS) { + special(SPC_NOMOD) + extra("::multiArray::RPvt* rpvt") + } + field(VAL,DBF_NOACCESS) { + prompt("Current Value") + promptgroup("40 - Input") + asl(ASL0) + pp(TRUE) + special(SPC_DBADDR) + extra("::epics::pvData::PVStructurePtr val") + } + field(VLD,DBF_NOACCESS) { + prompt("Valid Mask") + special(SPC_NOMOD) + extra("::epics::pvData::BitSet vld") + } + field(LAY, DBF_MENU) { + prompt("Structure Layout") + interest(2) + special(SPC_NOMOD) + menu(menuMultiArrayLayout) + } diff --git a/pdbApp/multiArrayCommonRecord.dbd b/pdbApp/multiArrayCommonRecord.dbd new file mode 100644 index 0000000..3e9d430 --- /dev/null +++ b/pdbApp/multiArrayCommonRecord.dbd @@ -0,0 +1,11 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuMultiArrayLayout.dbd" + +recordtype(multiArrayCommon) { + include "multiArrayCommon.dbd" +} diff --git a/pdbApp/statBinRecord.cpp b/pdbApp/statBinRecord.cpp index 045d13d..dfdb514 100644 --- a/pdbApp/statBinRecord.cpp +++ b/pdbApp/statBinRecord.cpp @@ -32,6 +32,8 @@ #include "pvstructinRecord.h" #include "svectorinRecord.h" +#include + #define GEN_SIZE_OFFSET #include #undef GEN_SIZE_OFFSET @@ -41,19 +43,6 @@ namespace pvd = epics::pvData; namespace statBin { -struct RPvt { - pvd::PVStructurePtr root; - pvd::BitSet valid, changed; - - template - inline - std::tr1::shared_ptr assign(const char* name) { - std::tr1::shared_ptr fld(root->getSubFieldT(name)); - changed.set(fld->getFieldOffset()); - return fld; - } -}; - long consume(struct statBinRecord *prec, const epics::pvData::shared_vector& arr) { short dbf; @@ -123,11 +112,11 @@ long statBinConsumeIt(struct statBinRecord *prec, const void* raw, size_t nelem) std[n] = sqrt(bsum2/cnt - mean[n]*mean[n]); } - prec->rpvt->assign("idx.value")->replace(pvd::freeze(idx)); - prec->rpvt->assign("min.value")->replace(pvd::freeze(min)); - prec->rpvt->assign("max.value")->replace(pvd::freeze(max)); - prec->rpvt->assign("mean.value")->replace(pvd::freeze(mean)); - prec->rpvt->assign("std.value")->replace(pvd::freeze(std)); + multiArray::set_column(prec, "idx", pvd::static_shared_vector_cast(pvd::freeze(idx))); + multiArray::set_column(prec, "min", pvd::static_shared_vector_cast(pvd::freeze(min))); + multiArray::set_column(prec, "max", pvd::static_shared_vector_cast(pvd::freeze(max))); + multiArray::set_column(prec, "mean", pvd::static_shared_vector_cast(pvd::freeze(mean))); + multiArray::set_column(prec, "std", pvd::static_shared_vector_cast(pvd::freeze(std))); return -1; } @@ -151,115 +140,51 @@ long statBinConsume(struct statBinRecord *prec, short dbf, const void* arr, size namespace { using namespace statBin; -ELLLIST vfPVStructureList = ELLLIST_INIT; -VFieldTypeNode vfPVStructureNode[2]; - long initialize() { - vfPVStructureNode[0].vtype = &vfStructure; - ellAdd(&vfPVStructureList, &vfPVStructureNode[0].node); - vfPVStructureNode[1].vtype = &vfPVStructure; - ellAdd(&vfPVStructureList, &vfPVStructureNode[1].node); - return 0; + return multiArray::initialize(); } long init_record(struct dbCommon *pcommon, int pass) { statBinRecord *prec = (statBinRecord*)pcommon; statBindset *pdset = (statBindset *)(prec->dset); - try { - - if (!pdset) { - if(pass==0) - recGblRecordError(S_dev_noDSET, prec, "statBin: no DSET"); - return S_dev_noDSET; - } - - if(pass==0) { - prec->rpvt = new RPvt; - - } else { // pass==1 - - const pvd::StandardFieldPtr& sfld(pvd::getStandardField()); - - pvd::StructureConstPtr elem(sfld->scalarArray(pvd::pvDouble, "alarm,timeStamp")); - - prec->rpvt->root = pvd::FieldBuilder::begin() - ->add("idx", elem) - ->add("mean", elem) - ->add("std", elem) - ->add("min", elem) - ->add("max", elem) - ->createStructure()->build(); + /* overall ordering + * pass==0 + * - check dset + * - multiArray::init_record(, 0) + * pass==1 + * - dset::init_record() -- calls add_column() + * - multiArray::init_record(, 1) + */ + + if (!pdset && pass==0) { + recGblRecordError(S_dev_noDSET, prec, "statBin: no DSET"); + return S_dev_noDSET; + } - if(pdset->common.init_record) { - long ret = pdset->common.init_record(pcommon); - if(ret) - return ret; - } + if(pass==1 && pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } - prec->rpvt->valid = prec->rpvt->changed; - } + long status = multiArray::init_record(pcommon, pass); - return 0; - }catch(std::exception& e){ - fprintf(stderr, "%s: init_record error: %s\n", prec->name, e.what()); - return -1; - } + return status; } long readValue(statBinRecord *prec, statBindset *pdset) { - prec->rpvt->changed.clear(); - - long ret = pdset->read_arr(prec); - - return ret; + return pdset->read_stats(prec); } void monitor(statBinRecord *prec) { int monitor_mask = recGblResetAlarms(prec); - monitor_mask |= DBE_VALUE | DBE_LOG; - - prec->rpvt->assign("idx.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); - prec->rpvt->assign("min.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); - prec->rpvt->assign("max.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); - prec->rpvt->assign("mean.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); - prec->rpvt->assign("std.timeStamp.secondsPastEpoch")->putFrom(prec->time.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH); - - prec->rpvt->assign("idx.timeStamp.nanoseconds")->putFrom(prec->time.nsec); - prec->rpvt->assign("min.timeStamp.nanoseconds")->putFrom(prec->time.nsec); - prec->rpvt->assign("max.timeStamp.nanoseconds")->putFrom(prec->time.nsec); - prec->rpvt->assign("mean.timeStamp.nanoseconds")->putFrom(prec->time.nsec); - prec->rpvt->assign("std.timeStamp.nanoseconds")->putFrom(prec->time.nsec); - - prec->rpvt->assign("idx.alarm.severity")->putFrom(prec->sevr); - prec->rpvt->assign("min.alarm.severity")->putFrom(prec->sevr); - prec->rpvt->assign("max.alarm.severity")->putFrom(prec->sevr); - prec->rpvt->assign("mean.alarm.severity")->putFrom(prec->sevr); - prec->rpvt->assign("std.alarm.severity")->putFrom(prec->sevr); - - prec->rpvt->assign("idx.alarm.status")->putFrom(prec->stat ? 1 : 0); - prec->rpvt->assign("min.alarm.status")->putFrom(prec->stat ? 1 : 0); - prec->rpvt->assign("max.alarm.status")->putFrom(prec->stat ? 1 : 0); - prec->rpvt->assign("mean.alarm.status")->putFrom(prec->stat ? 1 : 0); - prec->rpvt->assign("std.alarm.status")->putFrom(prec->stat ? 1 : 0); - - std::string amsg(prec->amsg); - prec->rpvt->assign("idx.alarm.message")->put(amsg); - prec->rpvt->assign("min.alarm.message")->put(amsg); - prec->rpvt->assign("max.alarm.message")->put(amsg); - prec->rpvt->assign("mean.alarm.message")->put(amsg); - prec->rpvt->assign("std.alarm.message")->put(amsg); - - if (monitor_mask) { - db_post_events(prec, &prec->val, monitor_mask); - } - - prec->rpvt->valid |= prec->rpvt->changed; + multiArray::monitor(prec, monitor_mask); } long process(struct dbCommon *pcommon) @@ -271,13 +196,13 @@ long process(struct dbCommon *pcommon) unsigned char pact=prec->pact; long status; - if( (pdset==NULL) || (pdset->read_arr==NULL) ) { + if( (pdset==NULL) || (pdset->read_stats==NULL) ) { prec->pact=TRUE; - recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_arr"); - recGblRecordError(S_dev_missingSup,(void *)prec,"read_arr"); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_stats"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_stats"); return S_dev_missingSup; - } else if(!prec->rpvt->root) { + } else if(!prec->val) { prec->pact=TRUE; recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no table"); return S_dev_NoInit; @@ -305,21 +230,7 @@ long process(struct dbCommon *pcommon) long cvt_dbaddr(DBADDR *paddr) { - // for VAL - - // we don't provide a valid DBR buffer - paddr->ro = 1; - // arbitrary limit - paddr->no_elements = 1; - - paddr->field_type = DBF_NOACCESS; - - if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { - // we provide vfield access - paddr->vfields = &vfPVStructureList; - } - - return 0; + return multiArray::cvt_dbaddr(paddr); } long get_array_info(DBADDR *paddr, long *no_elements, long *offset) @@ -334,29 +245,7 @@ long put_array_info(DBADDR *paddr, long nNew) long get_vfield(struct dbAddr *paddr, struct VField *p) { - statBinRecord *prec = (statBinRecord*)paddr->precord; - - if(!prec->rpvt->root) - return S_db_notInit; - - if(p->vtype==&vfPVStructure) { - VSharedPVStructure *pstr = (VSharedPVStructure*)p; - if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { - if(!*pstr->value) - return S_db_notInit; - (*pstr->value)->copy(*prec->rpvt->root); - *pstr->changed = prec->rpvt->valid; // TODO: distinguish initial from update - return 0; - } - - } else if(p->vtype==&vfStructure) { - VSharedStructure *pstr = (VSharedStructure*)p; - if(dbGetFieldIndex(paddr)==pvstructinRecordVAL) { - *pstr->value = prec->rpvt->root->getStructure(); - return 0; - } - } - return S_db_badChoice; + return multiArray::get_vfield(paddr, p); } long put_vfield(struct dbAddr *paddr, const struct VField *p) @@ -399,9 +288,10 @@ rset statBinRSET={ put_vfield, }; +// Soft Channel (default) device support + long readLocked(struct link *pinp, void *raw) { - const bool* doload = static_cast(raw); statBinRecord *prec = (statBinRecord *) pinp->precord; pvd::shared_vector arr; @@ -410,10 +300,51 @@ long readLocked(struct link *pinp, void *raw) ival.vtype = &vfSharedVector; ival.value = &arr; - long status = *doload ? dbLoadLink(pinp, DBR_VFIELD, &ival) : dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + long status = dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status==S_db_badDbrtype && !dbLinkIsConstant(pinp)) { + // fallback to reading (and copying) DBF array + status=0; + + int dbf = dbGetLinkDBFtype(pinp); + pvd::ScalarType stype; - if (status==S_db_badDbrtype) { - // TODO: fallback to alloc and copy + switch(dbf) { +#define CASE_SKIP_BOOL +#define CASE_REAL_INT64 +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBF_##DBFTYPE: stype = pvd::pv##PVACODE; break; +#include +#undef CASE +#undef CASE_SKIP_BOOL +#undef CASE_REAL_INT64 + default: + // include DBF_STRING + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad Img DBF %d", dbf); + return S_db_badDbrtype; + } + + long nelem = 0; + + if(dbf==-1 || (status=dbGetNelements(pinp, &nelem))) { + return status; + } + + size_t esize = dbValueSize(dbf); + try { + pvd::shared_vector temp(nelem*esize); + + status = dbGetLink(pinp, dbf, temp.data(), 0, &nelem); + + if(!status) { + temp.resize(nelem*esize); + arr = pvd::static_shared_vector_cast(pvd::freeze(temp)); + arr.set_original_type(stype); + } + } catch(std::exception& e) { + errlogPrintf("%s: Error during alloc/copy of DBF -> PVD: %s\n", prec->name, e.what()); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "DBF CPY %s", e.what()); + status = S_rec_outMem; /* this is likely an allocation error */ + } } if (!status) @@ -428,24 +359,30 @@ long readLocked(struct link *pinp, void *raw) return status; } +multiArray::Entry columns[] = { + {"idx", "1st Index", pvd::pvDouble}, + {"min", "Min", pvd::pvDouble}, + {"max", "Max", pvd::pvDouble}, + {"mean","Mean", pvd::pvDouble}, + {"std", "StdDev", pvd::pvDouble}, + {0}, +}; + long init_record_soft(struct dbCommon *pcommon) { statBinRecord *prec = (statBinRecord *)pcommon; - bool doload = true; - if (readLocked(&prec->inp, &doload)) { - prec->udf = FALSE; - } + multiArray::add_columns(prec, columns); + return 0; } long read_arr_soft(statBinRecord* prec) { - bool doload = false; - long status = dbLinkDoLocked(&prec->inp, readLocked, &doload); + long status = dbLinkDoLocked(&prec->inp, readLocked, 0); if (status == S_db_noLSET) - status = readLocked(&prec->inp, &doload); + status = readLocked(&prec->inp, 0); if (!status && !dbLinkIsConstant(&prec->inp)) prec->udf = FALSE; diff --git a/pdbApp/statBinRecord.dbd b/pdbApp/statBinRecord.dbd index ec35021..c938ebb 100644 --- a/pdbApp/statBinRecord.dbd +++ b/pdbApp/statBinRecord.dbd @@ -4,10 +4,10 @@ # in file LICENSE that is included with this distribution. #************************************************************************* -include "menuFtype.dbd" +include "menuMultiArrayLayout.dbd" recordtype(statBin) { - include "dbCommon.dbd" + include "multiArrayCommon.dbd" %#include %#include %#ifdef __cplusplus @@ -16,29 +16,17 @@ recordtype(statBin) { %struct statBinRecord; %struct statBindset { % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ - % long (*read_arr)(struct statBinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + % long (*read_stats)(struct statBinRecord *prec); /*returns: (-1,0)=>(failure,success)*/ %}; %QSRV_API %long statBinConsume(struct statBinRecord *prec, short dbf, const void* arr, size_t nelem); %#ifdef __cplusplus %} // extern "C" %namespace statBin { - %struct RPvt; %QSRV_API %long consume(struct statBinRecord *prec, const epics::pvData::shared_vector& arr); %} // namespace statBin %#endif - field(VAL,DBF_NOACCESS) { - prompt("Current Value") - asl(ASL0) - pp(TRUE) - special(SPC_DBADDR) - extra("void* val") - } - field(RPVT,DBF_NOACCESS) { - special(SPC_NOMOD) - extra("::statBin::RPvt* rpvt") - } field(INP,DBF_INLINK) { prompt("Input Specification") promptgroup("40 - Input") From 140c3137381817f3c1f64a4405ad419b2748e1c5 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 8 Dec 2020 20:26:56 -0800 Subject: [PATCH 14/19] statBin/columnarin/out always post DBE_VALUE --- pdbApp/columnarinRecord.cpp | 2 +- pdbApp/columnaroutRecord.cpp | 2 +- pdbApp/statBinRecord.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pdbApp/columnarinRecord.cpp b/pdbApp/columnarinRecord.cpp index 2248f6d..f35ddd9 100644 --- a/pdbApp/columnarinRecord.cpp +++ b/pdbApp/columnarinRecord.cpp @@ -81,7 +81,7 @@ void monitor(columnarinRecord *prec) { int monitor_mask = recGblResetAlarms(prec); - multiArray::monitor(prec, monitor_mask); + multiArray::monitor(prec, monitor_mask | DBE_VALUE | DBE_ARCHIVE); } long process(struct dbCommon *pcommon) diff --git a/pdbApp/columnaroutRecord.cpp b/pdbApp/columnaroutRecord.cpp index 18aee7e..d786cf7 100644 --- a/pdbApp/columnaroutRecord.cpp +++ b/pdbApp/columnaroutRecord.cpp @@ -81,7 +81,7 @@ void monitor(columnaroutRecord *prec) { int monitor_mask = recGblResetAlarms(prec); - multiArray::monitor(prec, monitor_mask); + multiArray::monitor(prec, monitor_mask | DBE_VALUE | DBE_ARCHIVE); } long process(struct dbCommon *pcommon) diff --git a/pdbApp/statBinRecord.cpp b/pdbApp/statBinRecord.cpp index dfdb514..ccbbcce 100644 --- a/pdbApp/statBinRecord.cpp +++ b/pdbApp/statBinRecord.cpp @@ -184,7 +184,7 @@ void monitor(statBinRecord *prec) { int monitor_mask = recGblResetAlarms(prec); - multiArray::monitor(prec, monitor_mask); + multiArray::monitor(prec, monitor_mask | DBE_VALUE | DBE_ARCHIVE); } long process(struct dbCommon *pcommon) From 21b80bf56d8172e2e95d527e46f77698d39ed7ac Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 24 Nov 2020 22:38:37 -0800 Subject: [PATCH 15/19] doc --- documentation/Doxyfile | 51 +--------- documentation/mainpage.dox | 3 + documentation/records.dox | 197 +++++++++++++++++++++++++++++++++++++ pdbApp/pv/qsrv.h | 11 +++ 4 files changed, 215 insertions(+), 47 deletions(-) create mode 100644 documentation/records.dox diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 85102c0..77e3c54 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -792,9 +792,8 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = . \ - ../pdbApp \ - ../common \ - ../p2pApp + ../include \ + ../include/pv # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -819,50 +818,8 @@ INPUT_ENCODING = UTF-8 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf +FILE_PATTERNS = *.dox \ + *.h # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. diff --git a/documentation/mainpage.dox b/documentation/mainpage.dox index 71d8f2f..6ec67a3 100644 --- a/documentation/mainpage.dox +++ b/documentation/mainpage.dox @@ -15,6 +15,9 @@ Documentation of @ref qsrv_config including @ref qsrv_group_def , @ref qsrv_aslib and @ref qsrv_link configuration. +Beginning with UNRELEASED, several @ref qsrv_pvd_records are available +when build against Base with the DBR_VFIELD (UNMERGED) feature. + - @ref release_notes @subsection qsrv_build Building diff --git a/documentation/records.dox b/documentation/records.dox new file mode 100644 index 0000000..2c2e93e --- /dev/null +++ b/documentation/records.dox @@ -0,0 +1,197 @@ +/** +@page qsrv_pvd_records QSRV PVD record types + +@since UNRELEASED + +Several additional process database record types are provided along with QSRV +which make use of pvData container classes. +These records include some fields which are only accessible via PV Access. +Device support for these record types must be written in C++. + +- svectorin/out +- columnarin/out +- ndain +- statBin +- pvstructin + +All of these record types provide custom device support (dset) +struct definitions. eg. 'struct svectorindset' + +@section qsrv_svector svectorin/out record types + +The svectorin/out records are analogous to to aai/aao record types in EPICS Base, +with different management of array allocations. The aai/aao types manage a +C array which device support copies samples to/from. +The svectorin/out use an epics::pvData::shared_vector<> to allow immutable array data +references to be moved in/out without copying. + +Configuration is similar to aai/aao, with the FTVL field being required +to specify the array element type. + +Unlike aai/aao the NELM field is not required. It may be set to enable a limited +compatibility mode in which the NELM elements may be read (but not written) via Channel Access. + +Device support code for svectorin needs to populate a shared_vector instance of the correct type, +and assign it to VAL. +Assigning an array of an incorrect type is an error, VAL will be cleared and an the record alarmed. + +@code +/* + * record(svectorin, "myrec") { + * field(DTYP, "My Support") + * field(FTVL, "DOUBLE") + * field(NELM, "100") # only needed for CA read access + * } + */ +#include +... +static +long read_my_array(svectorinRecord *prec) { + epics::pvData::shared_vector arr(2); + arr[0] = 1.0; + arr[1] = 2.0; + prec->val = epics::pvData::static_shared_vector_cast(epics::pvData::freeze(arr)); + return 0; +} +... +static svectorindtype devSVIMySupport = {{5,0,0,0,0}, &read_my_array}; +... +@endcode + +Device support code for svectorout will access the shared_vector assigned to VAL. +Since the array data is immutable, references may be safely passed to other +threads. + +@section qsrv_columnar columnarin/out and statBin record types + +The columnarin/out and statBin types may be understood as storing +a collection of named arrays of POD or string types. +Depending on the LAY field, this collection can either be presented +as a group of NTScalarArray sub-structures (LAY="Composite"), or as a single NTTable +with the arrays becoming columns (LAY="Table"). + +At this point, the only difference between columnarin and columnarout is the +presence of an INP vs. OUT field. + +In either case, device support init_record() must use the multiArray::add_column() +or multiArray::add_columns() helper methods to define column name, type, and optionally +label (used with NTTable only). + +@code +#include +/* + * record(columnarin, "myrec") { + * field(DTYP, "My Support") + * field(LAY , "Table") # default is Composite + * } + */ +static +multiArray::Entry columns[] = { + {"a", "Column A", epics::pvData::pvString}, + {"b", "Column B", epics::pvData::pvDouble}, + {0}, // end +}; +static +long init_record_my_table(dbCommon *pcommon) { + multiArray::add_columns(pcommon, columns); + return 0; +} +@endcode + +Device support for the columnarin or statBin type will use multiArray::set_column() +to assign shared_vector instances for each "column". +In the case of LAY="Table" it is the responsibility for device support +to ensure that all arrays/columns have the same number of rows. + +@code +static +long read_my_table(columnarinRecord *prec) { + epics::pvData::shared_vector cola(2); + cola[0] = "hello"; + cola[1] = "world"; + + epics::pvData::shared_vector colb(2); + colb[0] = 1.0; + colb[1] = 2.0; + + multiArray::set_column(prec, "a", epics::pvData::static_shared_vector_cast(epics::pvData::freeze(cola))); + multiArray::set_column(prec, "b", epics::pvData::static_shared_vector_cast(epics::pvData::freeze(colb))); + return 0; +} +... +static columnarindtype devCOLIMySupport = {{5,0,0,&init_record_my_table,0}, &read_my_table}; +... +@endcode + +Device support for the columnarout type will use multiArray::get_column() to retrieve +column arrays. + +@subsection qsrv_statbin_softchannel statBin default device support + +The statBin type is similar to columnarin with added fields, and a default +device support where INP can be pointed to an array field (eg. aai or svectorin) +to compute certain statistical values. + +@code +record(aai, "some:array") { ... } +record(statBin, "my:stats") { + field(INP, "some:array") + field(DEC, "10") # decimate by 10. aka. 10 input samples to one output bin. +} +@endcode + + +@section qsrv_ndain ndain record type + +The ndain record type presents an NTNDArray structure a la. areaDetector. +Some additional fields are added, notably W and H, and FTVL is omitted. +Otherwise this type is similar to svectorin. + +Currently only Gray scale images are supported. + +See process_img() in pdbApp/src/demo.cpp for a more realistic example. + +@code +/* + * record(ndain, "myrec") { + * field(DTYP, "My Support") + * } + */ +#include +... +static +long read_my_image(ndainRecord *prec) { + // 3x2 8bit + prec->w = 3; + prec->h = 2; + epics::pvData::shared_vector arr(3*2); + // width is innermost/contiguous + // idx = h*prec->w + w + arr[0] = 1; // 0x0 + arr[1] = 2; + arr[2] = 3; // 2x0 + arr[3] = 4; // 0x1 + arr[4] = 5; + arr[5] = 6; + prec->val = epics::pvData::static_shared_vector_cast(epics::pvData::freeze(arr)); + return 0; +} +... +static ndaindtype devNDIMySupport = {{5,0,0,0,0}, &read_my_image}; +... +@endcode + +@section qsrv_pvstructin pvstructin record type + +By analogy, the pvstructin record type is to these special record types what aSub is the Base record types. +A tool to be used when no other would be sufficient. + +This record type allows device support to device an arbitrary pvData structure, +which is done by placing a PVStructure pointer in the VAL field during init_record(). + +It is then the responsibility of device support to manipulate the (arbitrary) structure, +and to set the appropriate bit(s) of the CHG bitmask when doing so. + +A discussion of the proper handling of a PVStructure is outside the scope of this document. + +*/ diff --git a/pdbApp/pv/qsrv.h b/pdbApp/pv/qsrv.h index a233080..2f78cbd 100644 --- a/pdbApp/pv/qsrv.h +++ b/pdbApp/pv/qsrv.h @@ -10,6 +10,17 @@ /* generated header with EPICS_QSRV_*_VERSION macros */ # include +/** QSRV module version. + * This single version number will match the Makefile variables provided by the installed cfg/CONFIG_QSRV_VERSION . + * The individual components numbers are also available as macros. + * + * @code + * #include + * #if QSRV_VERSION_INT > VERSION_INT(1,3,0) + * // enable some feature + * #endif + * @endcode + */ #define QSRV_VERSION_INT VERSION_INT(EPICS_QSRV_MAJOR_VERSION, EPICS_QSRV_MINOR_VERSION, EPICS_QSRV_MAINTENANCE_VERSION, !(EPICS_QSRV_DEVELOPMENT_FLAG)) #define QSRV_ABI_VERSION_INT VERSION_INT(EPICS_QSRV_ABI_MAJOR_VERSION, EPICS_QSRV_ABI_MINOR_VERSION, 0, 0) From 09f6b7ed0df89a477f49c32c9a9ed82ca14f549d Mon Sep 17 00:00:00 2001 From: Bruno Martins Date: Mon, 1 Feb 2021 22:42:32 -0500 Subject: [PATCH 16/19] Add tableAggRecord --- pdbApp/Makefile | 5 +- pdbApp/qsrv-vfield.dbd | 1 + pdbApp/tableAggRecord.cpp | 348 ++++++++++++++++++++++++++++++++++++++ pdbApp/tableAggRecord.dbd | 130 ++++++++++++++ testApp/Makefile | 10 ++ testApp/testtable.cpp | 122 +++++++++++++ testApp/testtable.db | 55 ++++++ 7 files changed, 670 insertions(+), 1 deletion(-) create mode 100644 pdbApp/tableAggRecord.cpp create mode 100644 pdbApp/tableAggRecord.dbd create mode 100644 testApp/testtable.cpp create mode 100644 testApp/testtable.db diff --git a/pdbApp/Makefile b/pdbApp/Makefile index 5aff455..6e6c5d9 100644 --- a/pdbApp/Makefile +++ b/pdbApp/Makefile @@ -35,6 +35,7 @@ DBDINC += columnarinRecord DBDINC += columnaroutRecord DBDINC += statBinRecord DBDINC += ndainRecord +DBDINC += tableAggRecord DBDINC += multiArrayCommon EXPANDVARS += EPICS_QSRV_MAJOR_VERSION @@ -83,8 +84,9 @@ qsrv_SRCS += columnarinRecord.cpp qsrv_SRCS += columnaroutRecord.cpp qsrv_SRCS += statBinRecord.cpp - qsrv_SRCS += ndainRecord.cpp +qsrv_SRCS += tableAggRecord.cpp + qsrv_SRCS += multiArrayCommon.cpp qsrv_SRCS += devNDAInSoft.cpp @@ -140,6 +142,7 @@ $(COMMON_DIR)/softIocPVA.dbd: ../columnarinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../columnaroutRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../statBinRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: ../ndainRecord.dbd +$(COMMON_DIR)/softIocPVA.dbd: ../tableAggRecord.dbd $(COMMON_DIR)/softIocPVA.dbd: $(COMMON_DIR)/qsrv.dbd # Can't use EXPAND as generated headers must appear diff --git a/pdbApp/qsrv-vfield.dbd b/pdbApp/qsrv-vfield.dbd index fb1f7cf..666415b 100644 --- a/pdbApp/qsrv-vfield.dbd +++ b/pdbApp/qsrv-vfield.dbd @@ -4,6 +4,7 @@ include "columnarinRecord.dbd" include "columnarinRecord.dbd" include "statBinRecord.dbd" include "ndainRecord.dbd" +include "tableAggRecord.dbd" include "pvstructinRecord.dbd" # from demo.cpp diff --git a/pdbApp/tableAggRecord.cpp b/pdbApp/tableAggRecord.cpp new file mode 100644 index 0000000..b299ca2 --- /dev/null +++ b/pdbApp/tableAggRecord.cpp @@ -0,0 +1,348 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#ifndef USE_TYPED_RSET +# define USE_TYPED_RSET +#endif +#ifndef USE_TYPED_DSET +# define USE_TYPED_DSET +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace pvd = epics::pvData; + +namespace { + +static const size_t MAX_COLS = 26; +static const size_t AMSG_LEN = 128; + +static pvd::ScalarType to_pvd_type(epicsEnum16 type) +{ + switch(type) { +#define CASE(BASETYPE, PVATYPE, DBFTYPE, PVACODE) case DBF_##DBFTYPE: return pvd::pv##PVACODE; +#define CASE_SKIP_BOOL +#define CASE_REAL_INT64 +#include +#undef CASE_SKIP_BOOL +#undef CASE_REAL_INT64 +#undef CASE + // STRING is special + case DBF_STRING: return pvd::pvString; + + // not supporting ENUM + default: + throw std::runtime_error("not supported"); + } +} + +template +static T field_ptr(R *prec, size_t index) { + dbFldDes **papFldDes = prec->rdes->papFldDes; + return reinterpret_cast( + reinterpret_cast(prec) + papFldDes[index]->offset + ); +} + +long initialize() +{ + return multiArray::initialize(); +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + tableAggRecord *prec = reinterpret_cast(pcommon); + tableAggdset *pdset = reinterpret_cast(prec->dset); + /* overall ordering + * pass==0 + * - check dset + * - multiArray::init_record(, 0) + * pass==1 + * - dset::init_record() -- calls add_column() + * - multiArray::init_record(, 1) + */ + + if (!pdset && pass == 0) { + recGblRecordError(S_dev_noDSET, prec, "tableAgg: no DSET"); + return S_dev_noDSET; + } + + if (pass == 1 && pdset->common.init_record) { + long ret = pdset->common.init_record(pcommon); + if(ret) + return ret; + } + + return multiArray::init_record(pcommon, pass); +} + +void monitor(tableAggRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + multiArray::monitor(prec, monitor_mask); +} + +long process(struct dbCommon *pcommon) +{ + tableAggRecord *prec = reinterpret_cast(pcommon); + tableAggdset *pdset = reinterpret_cast(prec->dset); + + try { + unsigned char pact = prec->pact; + long status; + + if (!pdset || !pdset->read_tbl) { + prec->pact = TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_tbl"); + recGblRecordError(S_dev_missingSup, (void *)prec, "read_tbl"); + return S_dev_missingSup; + + } else if (!prec->val) { + prec->pact = TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no table"); + return S_dev_NoInit; + } + + // Read the new value + status = pdset->read_tbl(prec); + + // Check if device support set pact + if (!pact && prec->pact) + return 0; + + prec->pact = TRUE; + recGblGetTimeStamp(prec); + + // Check event list + monitor(prec); + + // Process the forward scan link record + recGblFwdLink(prec); + + prec->pact = FALSE; + return status; + + } catch(std::exception& e) { + fprintf(stderr, "%s: process error: %s\n", prec->name, e.what()); + return -1; + } +} + +long cvt_dbaddr(DBADDR *paddr) +{ + return multiArray::cvt_dbaddr(paddr); +} + +long get_array_info(DBADDR *paddr, long *no_elements, long *offset) +{ + return S_db_badField; +} + +long put_array_info(DBADDR *paddr, long nNew) +{ + return S_db_noMod; +} + +long get_vfield(struct dbAddr *paddr, struct VField *p) +{ + return multiArray::get_vfield(paddr, p); +} + +long put_vfield(struct dbAddr *paddr, const struct VField *p) +{ + return multiArray::put_vfield(paddr, p); +} + +#define report NULL +#define special NULL +#define get_value NULL +#define get_units NULL +#define get_precision NULL +#define get_enum_str NULL +#define get_enum_strs NULL +#define put_enum_str NULL +#define get_graphic_double NULL +#define get_control_double NULL +#define get_alarm_double NULL + +rset tableAggRSET = { + RSETNUMBER, + report, + initialize, + init_record, + process, + special, + get_value, + cvt_dbaddr, + get_array_info, + put_array_info, + get_units, + get_precision, + get_enum_str, + get_enum_strs, + put_enum_str, + get_graphic_double, + get_control_double, + get_alarm_double, + get_vfield, + put_vfield, +}; + +long readLocked(struct link *pinp, void *raw) +{ + const char *fname = static_cast(raw); + dbCommon *prec = pinp->precord; + + pvd::shared_vector arr; + + VSharedVector ival; + ival.vtype = &vfSharedVector; + ival.value = &arr; + + long status = dbGetLink(pinp, DBR_VFIELD, &ival, 0, 0); + + if (status == S_db_badDbrtype && !dbLinkIsConstant(pinp)) { + // Fallback to reading (and copying) DBF array + status = 0; + + int dbf = dbGetLinkDBFtype(pinp); + + pvd::ScalarType stype; + + try { + stype = to_pvd_type(dbf); + } catch (std::runtime_error &) { + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "Bad DBF %d", dbf); + return S_db_badDbrtype; + } + + long nelem = 0; + + if (dbf == -1 || (status = dbGetNelements(pinp, &nelem))) + return status; + + size_t esize = dbValueSize(dbf); + + try { + pvd::shared_vector temp(nelem*esize); + + status = dbGetLink(pinp, dbf, temp.data(), 0, &nelem); + + if (!status) { + temp.resize(nelem*esize); + + if (stype == pvd::pvString) { + // ugh, copy *again* + pvd::shared_vector temp_str; + temp_str.reserve(nelem); + + for (long i = 0; i < nelem; ++i) + temp_str.push_back(temp.data() + i*esize); // Assume NUL-terminated + + arr = pvd::static_shared_vector_cast(pvd::freeze(temp_str)); + } else { + arr = pvd::static_shared_vector_cast(pvd::freeze(temp)); + } + + arr.set_original_type(stype); + } + } catch(std::exception& e) { + errlogPrintf("%s: Error during alloc/copy of DBF -> PVD: %s\n", prec->name, e.what()); + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "DBF CPY %s", e.what()); + return S_rec_outMem; /* this is likely an allocation error */ + } + } + + multiArray::ColMeta meta; + char amsg[AMSG_LEN] = {}; + + if ((status = dbGetTimeStampTag(pinp, &meta.time, &meta.utag))) + errlogPrintf("%s [%s]: failed to get timestamp\n", prec->name, fname); + + if ((status = dbGetAlarmMsg(pinp, NULL, &meta.sevr, amsg, sizeof(amsg)))) + errlogPrintf("%s [%s]: failed to get alarm\n", prec->name, fname); + + meta.amsg = amsg; + multiArray::set_column(prec, fname, arr, &meta); + + return 0; +} + +long init_record_soft(struct dbCommon *pcommon) +{ + tableAggRecord *prec = reinterpret_cast(pcommon); + + for (size_t i = 0; i < MAX_COLS; ++i) { + // Assume NUL-terminated strings + const char *fname = field_ptr(prec, tableAggRecordFNAA + i); + const char *label = field_ptr(prec, tableAggRecordLABA + i); + epicsEnum16 *type = field_ptr(prec, tableAggRecordFTA + i); + + // Stop at first unnamed column + if (!strlen(fname)) + break; + + multiArray::add_column(pcommon, fname, label, to_pvd_type(*type)); + } + + return 0; +} + +long read_tbl_soft(tableAggRecord* prec) +{ + // Read data + metadata from links + for (size_t i = 0; i < MAX_COLS; ++i) { + const char *fname = field_ptr(prec, tableAggRecordFNAA + i); + struct link *pinp = field_ptr(prec, tableAggRecordINPA + i); + + // Stop at first unnamed column + if (!strlen(fname)) + break; + + long status = dbLinkDoLocked(pinp, readLocked, (void*)fname); + if (status == S_db_noLSET) + status = readLocked(pinp, (void*)fname); + + if (!status) + prec->udf = FALSE; + + if (status) + return status; + } + return 0; +} + +tableAggdset devTASoft = { + {5, NULL, NULL, &init_record_soft, NULL}, + &read_tbl_soft +}; + +} // namespace + +extern "C" { +epicsExportAddress(rset,tableAggRSET); +epicsExportAddress(dset,devTASoft); +} diff --git a/pdbApp/tableAggRecord.dbd b/pdbApp/tableAggRecord.dbd new file mode 100644 index 0000000..d9babf4 --- /dev/null +++ b/pdbApp/tableAggRecord.dbd @@ -0,0 +1,130 @@ +#************************************************************************* +# Copyright (c) 2020 Michael Davidsaver +# EPICS BASE is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +#************************************************************************* + +include "menuMultiArrayLayout.dbd" + +recordtype(tableAgg) { + include "multiArrayCommon.dbd" + %#include + %#include + %struct tableAggRecord; + %#ifdef __cplusplus + %extern "C" { + %#endif + %struct tableAggdset { + % dset common; /*init_record returns: (-1,0)=>(failure,success)*/ + % long (*read_tbl)(struct tableAggRecord *prec); /*returns: (-1,0)=>(failure,success)*/ + %}; + %#ifdef __cplusplus + %} // extern "C" + %#endif + field(INPA,DBF_INLINK) { prompt("Input A") promptgroup("41 - Input A-H") interest(1) } + field(INPB,DBF_INLINK) { prompt("Input B") promptgroup("41 - Input A-H") interest(1) } + field(INPC,DBF_INLINK) { prompt("Input C") promptgroup("41 - Input A-H") interest(1) } + field(INPD,DBF_INLINK) { prompt("Input D") promptgroup("41 - Input A-H") interest(1) } + field(INPE,DBF_INLINK) { prompt("Input E") promptgroup("41 - Input A-H") interest(1) } + field(INPF,DBF_INLINK) { prompt("Input F") promptgroup("41 - Input A-H") interest(1) } + field(INPG,DBF_INLINK) { prompt("Input G") promptgroup("41 - Input A-H") interest(1) } + field(INPH,DBF_INLINK) { prompt("Input H") promptgroup("41 - Input A-H") interest(1) } + field(INPI,DBF_INLINK) { prompt("Input I") promptgroup("42 - Input I-P") interest(1) } + field(INPJ,DBF_INLINK) { prompt("Input J") promptgroup("42 - Input I-P") interest(1) } + field(INPK,DBF_INLINK) { prompt("Input K") promptgroup("42 - Input I-P") interest(1) } + field(INPL,DBF_INLINK) { prompt("Input L") promptgroup("42 - Input I-P") interest(1) } + field(INPM,DBF_INLINK) { prompt("Input M") promptgroup("42 - Input I-P") interest(1) } + field(INPN,DBF_INLINK) { prompt("Input N") promptgroup("42 - Input I-P") interest(1) } + field(INPO,DBF_INLINK) { prompt("Input O") promptgroup("42 - Input I-P") interest(1) } + field(INPP,DBF_INLINK) { prompt("Input P") promptgroup("42 - Input I-P") interest(1) } + field(INPQ,DBF_INLINK) { prompt("Input Q") promptgroup("43 - Input Q-X") interest(1) } + field(INPR,DBF_INLINK) { prompt("Input R") promptgroup("43 - Input Q-X") interest(1) } + field(INPS,DBF_INLINK) { prompt("Input S") promptgroup("43 - Input Q-X") interest(1) } + field(INPT,DBF_INLINK) { prompt("Input T") promptgroup("43 - Input Q-X") interest(1) } + field(INPU,DBF_INLINK) { prompt("Input U") promptgroup("43 - Input Q-X") interest(1) } + field(INPV,DBF_INLINK) { prompt("Input V") promptgroup("43 - Input Q-X") interest(1) } + field(INPW,DBF_INLINK) { prompt("Input W") promptgroup("43 - Input Q-X") interest(1) } + field(INPX,DBF_INLINK) { prompt("Input X") promptgroup("43 - Input Q-X") interest(1) } + field(INPY,DBF_INLINK) { prompt("Input Y") promptgroup("44 - Input Y-Z") interest(1) } + field(INPZ,DBF_INLINK) { prompt("Input Z") promptgroup("44 - Input Y-Z") interest(1) } + field(FNAA,DBF_STRING) { prompt("Field Name A") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAB,DBF_STRING) { prompt("Field Name B") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAC,DBF_STRING) { prompt("Field Name C") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAD,DBF_STRING) { prompt("Field Name D") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAE,DBF_STRING) { prompt("Field Name E") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAF,DBF_STRING) { prompt("Field Name F") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAG,DBF_STRING) { prompt("Field Name G") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAH,DBF_STRING) { prompt("Field Name H") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(FNAI,DBF_STRING) { prompt("Field Name I") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAJ,DBF_STRING) { prompt("Field Name J") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAK,DBF_STRING) { prompt("Field Name K") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAL,DBF_STRING) { prompt("Field Name L") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAM,DBF_STRING) { prompt("Field Name M") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAN,DBF_STRING) { prompt("Field Name N") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAO,DBF_STRING) { prompt("Field Name O") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAP,DBF_STRING) { prompt("Field Name P") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(FNAQ,DBF_STRING) { prompt("Field Name Q") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAR,DBF_STRING) { prompt("Field Name R") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAS,DBF_STRING) { prompt("Field Name S") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAT,DBF_STRING) { prompt("Field Name T") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAU,DBF_STRING) { prompt("Field Name U") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAV,DBF_STRING) { prompt("Field Name V") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAW,DBF_STRING) { prompt("Field Name W") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAX,DBF_STRING) { prompt("Field Name X") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(FNAY,DBF_STRING) { prompt("Field Name Y") promptgroup("44 - Input Y-Z") special(SPC_NOMOD) interest(1) size(41) } + field(FNAZ,DBF_STRING) { prompt("Field Name Z") promptgroup("44 - Input Y-Z") special(SPC_NOMOD) interest(1) size(41) } + field(LABA,DBF_STRING) { prompt("Field Label A") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABB,DBF_STRING) { prompt("Field Label B") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABC,DBF_STRING) { prompt("Field Label C") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABD,DBF_STRING) { prompt("Field Label D") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABE,DBF_STRING) { prompt("Field Label E") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABF,DBF_STRING) { prompt("Field Label F") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABG,DBF_STRING) { prompt("Field Label G") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABH,DBF_STRING) { prompt("Field Label H") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABI,DBF_STRING) { prompt("Field Label I") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) size(41) } + field(LABJ,DBF_STRING) { prompt("Field Label J") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABK,DBF_STRING) { prompt("Field Label K") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABL,DBF_STRING) { prompt("Field Label L") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABM,DBF_STRING) { prompt("Field Label M") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABN,DBF_STRING) { prompt("Field Label N") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABO,DBF_STRING) { prompt("Field Label O") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABP,DBF_STRING) { prompt("Field Label P") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) size(41) } + field(LABQ,DBF_STRING) { prompt("Field Label Q") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABR,DBF_STRING) { prompt("Field Label R") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABS,DBF_STRING) { prompt("Field Label S") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABT,DBF_STRING) { prompt("Field Label T") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABU,DBF_STRING) { prompt("Field Label U") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABV,DBF_STRING) { prompt("Field Label V") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABW,DBF_STRING) { prompt("Field Label W") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABX,DBF_STRING) { prompt("Field Label X") promptgroup("42 - Input Q-X") special(SPC_NOMOD) interest(1) size(41) } + field(LABY,DBF_STRING) { prompt("Field Label Y") promptgroup("42 - Input Y-Z") special(SPC_NOMOD) interest(1) size(41) } + field(LABZ,DBF_STRING) { prompt("Field Label Z") promptgroup("42 - Input Y-Z") special(SPC_NOMOD) interest(1) size(41) } + field(FTA,DBF_MENU) { prompt("Field Type A") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTB,DBF_MENU) { prompt("Field Type B") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTC,DBF_MENU) { prompt("Field Type C") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTD,DBF_MENU) { prompt("Field Type D") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTE,DBF_MENU) { prompt("Field Type E") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTF,DBF_MENU) { prompt("Field Type F") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTG,DBF_MENU) { prompt("Field Type G") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTH,DBF_MENU) { prompt("Field Type H") promptgroup("41 - Input A-H") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTI,DBF_MENU) { prompt("Field Type I") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTJ,DBF_MENU) { prompt("Field Type J") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTK,DBF_MENU) { prompt("Field Type K") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTL,DBF_MENU) { prompt("Field Type L") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTM,DBF_MENU) { prompt("Field Type M") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTN,DBF_MENU) { prompt("Field Type N") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTO,DBF_MENU) { prompt("Field Type O") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTP,DBF_MENU) { prompt("Field Type P") promptgroup("42 - Input I-P") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTQ,DBF_MENU) { prompt("Field Type Q") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTR,DBF_MENU) { prompt("Field Type R") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTS,DBF_MENU) { prompt("Field Type S") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTT,DBF_MENU) { prompt("Field Type T") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTU,DBF_MENU) { prompt("Field Type U") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTV,DBF_MENU) { prompt("Field Type V") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTW,DBF_MENU) { prompt("Field Type W") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTX,DBF_MENU) { prompt("Field Type X") promptgroup("43 - Input Q-X") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTY,DBF_MENU) { prompt("Field Type Y") promptgroup("44 - Input Y-Z") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } + field(FTZ,DBF_MENU) { prompt("Field Type Z") promptgroup("44 - Input Y-Z") special(SPC_NOMOD) interest(1) initial("DOUBLE") menu(menuFtype) } +} + +device(tableAgg, CONSTANT, devTASoft, "Soft Channel") diff --git a/testApp/Makefile b/testApp/Makefile index b6ae290..d5ba689 100644 --- a/testApp/Makefile +++ b/testApp/Makefile @@ -67,6 +67,16 @@ testdbf_copy_LIBS += $(EPICS_BASE_IOC_LIBS) TESTS += testdbf_copy endif +TARGETS += $(COMMON_DIR)/tableTestIoc.dbd +tableTestIoc_DBD += base.dbd +tableTestIoc_DBD += qsrv.dbd + +TESTPROD_HOST += testtable +testtable_SRCS += testtable.cpp +testtable_SRCS += tableTestIoc_registerRecordDeviceDriver.cpp +testtable_LIBS += qsrv pvData +TESTS += testtable + TESTSCRIPTS_HOST += $(TESTS:%=%.t) diff --git a/testApp/testtable.cpp b/testApp/testtable.cpp new file mode 100644 index 0000000..19cccc5 --- /dev/null +++ b/testApp/testtable.cpp @@ -0,0 +1,122 @@ +/*************************************************************************\ +* Copyright (c) 2020 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +static const size_t NELM = 5; + +extern "C" { +void tableTestIoc_registerRecordDeviceDriver(struct dbBase *); +} + +namespace { + +namespace pvd = epics::pvData; + +template +void testVector(const pvd::PVStructurePtr & value, const std::string & fname, size_t n, const T* expected) { + pvd::PVScalarArrayPtr ptr = value->getSubFieldT(fname); + pvd::shared_vector actual; + pvd::shared_vector exp; + + exp.reserve(n); + for (size_t i = 0; i < n; ++i) + exp.push_back(expected[i]); + + ptr->getAs(actual); + + std::stringstream ss; + ss << fname << ": " << actual << " == " << exp; + + testOk(actual == exp, "%s", ss.str().c_str()); +} + +void testGetPut() +{ + // Get handles to record + tableAggRecord *ptable = (tableAggRecord*)testdbRecordPtr("TABLE"); + + DBADDR addr; + + if (!ptable || dbNameToAddr("TABLE", &addr)) + testAbort("Failed to find record TABLE"); + + // Process table record + testdbPutFieldOk("TABLE.PROC", DBF_LONG, 1); + + // Fetch Structure + VSharedStructure sval; + sval.vtype = &vfStructure; + sval.value = new pvd::StructureConstPtr(); + + testOk1(!dbGetField(&addr, DBR_VFIELD, &sval, 0, 0, 0)); + + // Check that the names of the columns are being correctly set + const pvd::StringArray & cols = (*sval.value)->getFieldT("value")->getFieldNames(); + std::vector exp_cols; + exp_cols.push_back("col1"); + exp_cols.push_back("col2"); + exp_cols.push_back("col3"); + exp_cols.push_back("col4"); + testOk1(cols==exp_cols); + + // Fetch PVStructure + VSharedPVStructure val; + val.vtype = &vfPVStructure; + val.value = new pvd::PVStructurePtr((*sval.value)->build()); + val.changed = new pvd::BitSet(); + + testOk1(!dbGetField(&addr, DBR_VFIELD, &val, 0, 0, 0)); + + // Check that the labels are being correctly set + const std::string labels[] = {"One", "Two", "Three", "Four"}; + testVector(*val.value, "labels", NELEMENTS(labels), labels); + + // Check column values + const double col1[] = {1, 2, 3}; + testVector(*val.value, "value.col1", NELEMENTS(col1), col1); + + const pvd::int32 col2[] = {4, 3, 2, 1}; + testVector(*val.value, "value.col2", NELEMENTS(col2), col2); + + const pvd::int16 col3[] = {7, 8, 9}; + testVector(*val.value, "value.col3", NELEMENTS(col3), col3); + + const std::string col4[] = {"First", "Second"}; + testVector(*val.value, "value.col4", NELEMENTS(col4), col4); +} + +} + +MAIN(testtable) +{ + testPlan(9); + testdbPrepare(); + testdbReadDatabase("tableTestIoc.dbd", 0, 0); + tableTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("testtable.db", 0, 0); + + eltc(0); + testIocInitOk(); + eltc(1); + + testGetPut(); + + testIocShutdownOk(); + epicsExitCallAtExits(); // calls db_close_events() + testdbCleanup(); // calls db_cleanup_events() + return testDone(); +} diff --git a/testApp/testtable.db b/testApp/testtable.db new file mode 100644 index 0000000..8a192b4 --- /dev/null +++ b/testApp/testtable.db @@ -0,0 +1,55 @@ +record(waveform, "INA") { + field(FTVL, "DOUBLE") + field(NELM, "3") + field(INP, {const:[1,2,3]}) + field(PINI, "YES") +} + +record(waveform, "INB") { + field(FTVL, "CHAR") + field(NELM, "4") + field(INP, {const:[4,3,2,1]}) + field(PINI, "YES") +} + +record(svectorin, "INC") { + field(FTVL, "SHORT") + field(NELM, "3") + field(INP, {const:[7,8,9]}) + field(PINI, "YES") +} + +record(waveform, "IND") { + field(FTVL, "STRING") + field(NELM, "2") + field(INP, {const:["First","Second"]}) + field(PINI, "YES") +} + +record(tableAgg, "TABLE") { + field(INPA, "INA") + field(FNAA, "col1") + field(LABA, "One") + + field(INPB, "INB") + field(FNAB, "col2") + field(LABB, "Two") + field(FTB, "LONG") + + field(INPC, "INC") + field(FNAC, "col3") + field(LABC, "Three") + field(FTC, "SHORT") + + field(INPD, "IND") + field(FNAD, "col4") + field(LABD, "Four") + field(FTD, "STRING") + + # The following should be ignored + field(INPF, "BOGUS") + field(FNAF, "col6") + field(LABF, "Six") + + field(LAY, "Table") +} From 636ce7b24d3c0b6a40b9b24b779bf53e6d7a1758 Mon Sep 17 00:00:00 2001 From: Bruno Martins Date: Mon, 1 Mar 2021 19:05:48 -0500 Subject: [PATCH 17/19] tableAgg: documentation --- documentation/records.dox | 41 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/documentation/records.dox b/documentation/records.dox index 2c2e93e..2166878 100644 --- a/documentation/records.dox +++ b/documentation/records.dox @@ -13,6 +13,7 @@ Device support for these record types must be written in C++. - ndain - statBin - pvstructin +- tableAgg All of these record types provide custom device support (dset) struct definitions. eg. 'struct svectorindset' @@ -194,4 +195,42 @@ and to set the appropriate bit(s) of the CHG bitmask when doing so. A discussion of the proper handling of a PVStructure is outside the scope of this document. -*/ +@section qsrv_tableagg tableAgg record type + +The tableAgg record builds a collection of arrays out of individual array inputs. Like +the columnarin/out and statBin records, the resulting value can be presented either as +a Table or as a Composite (structure) type, depending on the selection in the LAY field. + +There are 26 inputs available to the record, A..Z: + +INPA..INPZ specify where to obtain the data for each column +FNAA..FNAZ specify the field name for the corresponding column +LABA..LABZ specify the field label for each corresponding column +FTA..FTZ specify the data type of the elements of the corresponding column (default: DOUBLE) + +Similar to columnarin/out and statBin, device support for tableAgg should call multiArray::add_column +or multiArray::add_columns in order to create the appropriate columns in the collection. Setting column +values should be done via multiArray::set_column. + +@subsection qsrv_tableagg_softchannel tableAgg default device support + +During initialization, the default device support for tableAgg creates columns based on FNAA..FNAZ, +LABA..LABZ and FTA..FTZ. It reads these inputs in order A..Z and stops creating new columns as soon +as it finds and empty FNAx field. For example, the following record will have only one column: + +@code +record(tableAgg, "my:table") { + field(FNAA, "some_array") + field(INPA, "some:array") + + # FNAB is empty, all following columns will be ignored + + field(FNAC, "another_array") + field(INPC, "another:array") +} +@endcode + +When processed, the default device support will read each INPx field and populate the corresponding +value with the obtained array. + +*/ \ No newline at end of file From 8427d68ebe6ff7812295709eb5085e893978e4f1 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 2 Jun 2020 12:48:01 -0700 Subject: [PATCH 18/19] WIP: build again patched Base --- .appveyor.yml | 9 +++------ .ci-local/defaults.set | 2 +- .github/workflows/ci-scripts-build.yml | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 2b42845..f26db7c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -47,16 +47,13 @@ environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 CMP: vs2019 - BASE: 7.0 + BASE: vfield - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 CMP: gcc - BASE: 7.0 + BASE: vfield - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 CMP: vs2017 - BASE: 7.0 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - CMP: vs2019 - BASE: 3.15 + BASE: vfield # Platform: processor architecture platform: diff --git a/.ci-local/defaults.set b/.ci-local/defaults.set index 8aad835..93320f4 100644 --- a/.ci-local/defaults.set +++ b/.ci-local/defaults.set @@ -1,7 +1,7 @@ # EPICS Base BASE_DIRNAME=base BASE_REPONAME=epics-base -BASE_REPOOWNER=epics-base +BASE_REPOOWNER=mdavidsaver BASE_VARNAME=EPICS_BASE BASE_RECURSIVE=NO diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 49d0797..cf24054 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -17,6 +17,7 @@ on: env: SETUP_PATH: .ci-local:.ci EPICS_TEST_IMPRECISE_TIMING: YES + BASE: vfield jobs: native: @@ -24,7 +25,6 @@ jobs: runs-on: ${{ matrix.os }} # Set environment variables from matrix parameters env: - BASE: ${{ matrix.base }} CMP: ${{ matrix.cmp }} BCFG: ${{ matrix.configuration }} WINE: ${{ matrix.wine }} @@ -145,7 +145,6 @@ jobs: image: ${{ matrix.image }} # Set environment variables from matrix parameters env: - BASE: ${{ matrix.base }} CMP: ${{ matrix.cmp }} BCFG: ${{ matrix.configuration }} WINE: ${{ matrix.wine }} From aba9fa7c008f0d9d0f253ed187cbd34ff3933850 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 15 Sep 2020 22:01:05 -0700 Subject: [PATCH 19/19] WIP: pva link w/ DBR_VFIELD --- pdbApp/pvalink_lset.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pdbApp/pvalink_lset.cpp b/pdbApp/pvalink_lset.cpp index 653b33d..7b2c573 100644 --- a/pdbApp/pvalink_lset.cpp +++ b/pdbApp/pvalink_lset.cpp @@ -4,6 +4,8 @@ #include #include // redirect stdout/stderr +#include + #include #include "pvalink.h" @@ -215,6 +217,31 @@ long pvaGetValue(DBLINK *plink, short dbrType, void *pbuffer, return -1; } + if(dbrType==DBR_VFIELD) { + if(!self->lchan->op_mon.root) + return -1; + + VField *vfield = static_cast(pbuffer); + if(vfield->vtype==&vfStructure) { + VSharedStructure *vstruct = static_cast(pbuffer); + *vstruct->value = self->lchan->op_mon.root->getStructure(); + return 0; + + } else if(vfield->vtype==&vfPVStructure) { + VSharedPVStructure *vinst = static_cast(pbuffer); + if((*vinst->value)->getStructure()!=self->lchan->op_mon.root->getStructure()) { + // TODO: how to handle type change? + return S_db_badDbrtype; + } + (*vinst->value)->copy(*self->lchan->op_mon.root); + (*vinst->changed) |= self->lchan->op_mon.changed; + return 0; + + } else { + return S_db_badDbrtype; + } + } + if(self->fld_value) { long status = copyPVD2DBF(self->fld_value, pbuffer, dbrType, pnRequest); if(status) {