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 }} 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..2166878 --- /dev/null +++ b/documentation/records.dox @@ -0,0 +1,236 @@ +/** +@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 +- tableAgg + +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. + +@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 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/Makefile b/pdbApp/Makefile index a2e9c09..6e6c5d9 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 #============================= @@ -22,6 +26,18 @@ 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 += tableAggRecord +DBDINC += multiArrayCommon + EXPANDVARS += EPICS_QSRV_MAJOR_VERSION EXPANDVARS += EPICS_QSRV_MINOR_VERSION EXPANDVARS += EPICS_QSRV_MAINTENANCE_VERSION @@ -55,6 +71,25 @@ else qsrv_SRCS += pvalink_null.cpp 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 + +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 + qsrv_LIBS += pvAccess pvData qsrv_LIBS += $(EPICS_BASE_IOC_LIBS) @@ -76,6 +111,14 @@ 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 @@ -91,6 +134,17 @@ include $(TOP)/configure/RULES #---------------------------------------- # ADD RULES AFTER THIS LINE +# 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: ../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 # in O.Common, but EXPAND emits rules for O.$(T_A) ../O.Common/pv/qsrvVersionNum.h: ../pv/qsrvVersionNum.h@ @@ -100,15 +154,20 @@ include $(TOP)/configure/RULES 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) $< $@ -else -../O.Common/qsrv.dbd: ../qsrv-old.dbd - $(CP) $< $@ -endif -qsrv.dbd$(DEP): +# 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" > $@ -../O.Common/softIocPVA.dbd: ../O.Common/qsrv.dbd +$(COMMON_DIR)/qsrv.dbd: $(qsrv_pieces) + $(CAT) $(filter %.dbd,$^) > $@ diff --git a/pdbApp/columnarinRecord.cpp b/pdbApp/columnarinRecord.cpp new file mode 100644 index 0000000..f35ddd9 --- /dev/null +++ b/pdbApp/columnarinRecord.cpp @@ -0,0 +1,232 @@ +/*************************************************************************\ +* 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) +{ + columnarinRecord *prec = (columnarinRecord*)pcommon; + columnarindset *pdset = (columnarindset *)(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, "columnarin: 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 readValue(columnarinRecord *prec, + columnarindset *pdset) +{ + return pdset->read_tbl(prec); +} + +void monitor(columnarinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + multiArray::monitor(prec, monitor_mask | DBE_VALUE | DBE_ARCHIVE); +} + +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_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; + } + + 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) +{ + 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 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 readLocked(struct link *pinp, void *raw) +{ + 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) +{ + 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, NULL, 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..7cabb23 --- /dev/null +++ b/pdbApp/columnarinRecord.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(columnarin) { + include "multiArrayCommon.dbd" + %#include + %#include + %struct columnarinRecord; + %#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(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup("40 - Input") + interest(1) + } +} + +device(columnarin, CONSTANT, devCOLISoft, "Soft Channel") diff --git a/pdbApp/columnaroutRecord.cpp b/pdbApp/columnaroutRecord.cpp new file mode 100644 index 0000000..d786cf7 --- /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 | DBE_VALUE | DBE_ARCHIVE); +} + +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/demo.cpp b/pdbApp/demo.cpp index c5030d1..c93fb74 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,71 @@ long process_utag(longinRecord *prec) return status; } +struct imgPvt { + double phase; +}; + +#ifdef DBR_VFIELD + +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; + } +} + +#endif // DBR_VFIELD + template struct dset5 { @@ -82,9 +155,23 @@ struct dset5 dset5 devWfPDBDemo = {5,0,0,&init_spin,0,&process_spin}; dset5 devLoPDBUTag = {5,0,0,0,0,&process_utag}; +#ifdef DBR_VFIELD +ndaindset devNDAIPDBDemo = { + { + 5, 0, 0, + &init_img, + 0, + }, + &process_img, +}; +#endif + } // namespace extern "C" { epicsExportAddress(dset, devWfPDBDemo); epicsExportAddress(dset, devLoPDBUTag); +#ifdef DBR_VFIELD +epicsExportAddress(dset, devNDAIPDBDemo); +#endif } 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/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/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/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/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/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") 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/pv/qsrv.h b/pdbApp/pv/qsrv.h index 452fade..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) @@ -37,6 +48,9 @@ #endif +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -82,6 +96,31 @@ QSRV_API void testqsrvShutdownOk(void); */ QSRV_API void testqsrvCleanup(void); +#ifdef DBR_VFIELD + +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; +}; + +#endif + #ifdef __cplusplus } #endif 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) { diff --git a/pdbApp/pvif.cpp b/pdbApp/pvif.cpp index 76def06..25eedf7 100644 --- a/pdbApp/pvif.cpp +++ b/pdbApp/pvif.cpp @@ -27,6 +27,9 @@ #include +#include +#include + #ifdef EPICS_VERSION_INT # if EPICS_VERSION_INT>=VERSION_INT(3,16,1,0) # define USE_INT64 @@ -166,9 +169,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 +186,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 +435,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 +449,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 +466,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 +489,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); - if(dbr!=DBR_STRING) { + VSharedVector vect; + vect.vtype = pv.vtype; + vect.value = &buf; + + 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 +533,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 +574,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 +641,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 +711,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 +719,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 +769,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 +840,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 +855,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 +873,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 +914,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 +927,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 +952,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 +965,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 +985,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 +999,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"); } @@ -982,9 +1023,134 @@ 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 { - 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 +1185,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 +1237,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 +1479,29 @@ PVIFBuilder::dtype(epics::pvData::FieldBuilderPtr& builder, PVIFBuilder* PVIFBuilder::create(const std::string& type, dbChannel* chan) { - if(type.empty() || type=="scalar") - return new ScalarBuilder(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 || vnode->vtype==&vfPVStructure) { + vtype = vnode->vtype; + break; + } + } + } + + 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); + 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; }; diff --git a/pdbApp/pvstructinRecord.cpp b/pdbApp/pvstructinRecord.cpp new file mode 100644 index 0000000..e1fd825 --- /dev/null +++ b/pdbApp/pvstructinRecord.cpp @@ -0,0 +1,265 @@ +/*************************************************************************\ +* 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" + +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +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..1f98d9d --- /dev/null +++ b/pdbApp/pvstructinRecord.dbd @@ -0,0 +1,56 @@ +#************************************************************************* +# 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 + %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-old.dbd b/pdbApp/qsrv-common.dbd similarity index 100% rename from pdbApp/qsrv-old.dbd rename to pdbApp/qsrv-common.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 7ac8602..0000000 --- a/pdbApp/qsrv-new.dbd +++ /dev/null @@ -1,18 +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) diff --git a/pdbApp/qsrv-vfield.dbd b/pdbApp/qsrv-vfield.dbd new file mode 100644 index 0000000..666415b --- /dev/null +++ b/pdbApp/qsrv-vfield.dbd @@ -0,0 +1,11 @@ +include "svectorinRecord.dbd" +include "svectoroutRecord.dbd" +include "columnarinRecord.dbd" +include "columnarinRecord.dbd" +include "statBinRecord.dbd" +include "ndainRecord.dbd" +include "tableAggRecord.dbd" +include "pvstructinRecord.dbd" + +# from demo.cpp +device(ndain, CONSTANT, devNDAIPDBDemo, "QSRV Demo") diff --git a/pdbApp/statBinRecord.cpp b/pdbApp/statBinRecord.cpp new file mode 100644 index 0000000..ccbbcce --- /dev/null +++ b/pdbApp/statBinRecord.cpp @@ -0,0 +1,403 @@ +/*************************************************************************\ +* 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" + +#include + +#define GEN_SIZE_OFFSET +#include +#undef GEN_SIZE_OFFSET +#include + +namespace pvd = epics::pvData; + +namespace statBin { + +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]); + } + + 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; +} +} // 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; + +long initialize() +{ + return multiArray::initialize(); +} + +long init_record(struct dbCommon *pcommon, int pass) +{ + statBinRecord *prec = (statBinRecord*)pcommon; + statBindset *pdset = (statBindset *)(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, "statBin: 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 readValue(statBinRecord *prec, + statBindset *pdset) +{ + return pdset->read_stats(prec); +} + +void monitor(statBinRecord *prec) +{ + int monitor_mask = recGblResetAlarms(prec); + + multiArray::monitor(prec, monitor_mask | DBE_VALUE | DBE_ARCHIVE); +} + +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_stats==NULL) ) { + prec->pact=TRUE; + recGblSetSevrMsg(prec, READ_ALARM, INVALID_ALARM, "no read_stats"); + recGblRecordError(S_dev_missingSup,(void *)prec,"read_stats"); + return S_dev_missingSup; + + } else if(!prec->val) { + 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) +{ + 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 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, +}; + +// Soft Channel (default) device support + +long readLocked(struct link *pinp, void *raw) +{ + statBinRecord *prec = (statBinRecord *) 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; + + 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) + status = statBin::consume(prec, arr); + + if (status) return status; + + if (dbLinkIsConstant(&prec->tsel) && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(pinp, &prec->time); + + 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; + + multiArray::add_columns(prec, columns); + + return 0; +} + +long read_arr_soft(statBinRecord* prec) +{ + 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; +} + +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..c938ebb --- /dev/null +++ b/pdbApp/statBinRecord.dbd @@ -0,0 +1,40 @@ +#************************************************************************* +# 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(statBin) { + include "multiArrayCommon.dbd" + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %struct statBinRecord; + %struct statBindset { + % dset common; /*init_record 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 { + %QSRV_API + %long consume(struct statBinRecord *prec, const epics::pvData::shared_vector& arr); + %} // namespace statBin + %#endif + 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") diff --git a/pdbApp/svectorinRecord.cpp b/pdbApp/svectorinRecord.cpp new file mode 100644 index 0000000..83c55c0 --- /dev/null +++ b/pdbApp/svectorinRecord.cpp @@ -0,0 +1,293 @@ +/*************************************************************************\ +* 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" + +#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(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..74bb8d4 --- /dev/null +++ b/pdbApp/svectorinRecord.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(svectorin) { + include "dbCommon.dbd" + %/* Declare Device Support Entry Table */ + %#include + %#include + %#include + %#include + %#include + %#ifdef __cplusplus + %extern "C" { + %#endif + %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") 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") 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/pdbApp/vfield.cpp b/pdbApp/vfield.cpp new file mode 100644 index 0000000..71acb28 --- /dev/null +++ b/pdbApp/vfield.cpp @@ -0,0 +1,11 @@ + +#include + +#ifdef DBR_VFIELD + +const VFieldType vfSharedVector = {"epics::pvData::shared_vector"}; + +const VFieldType vfStructure = {"epics::pvData::StructureConstPtr"}; +const VFieldType vfPVStructure = {"epics::pvData::PVStructurePtr"}; + +#endif 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") +}