diff --git a/Makefile b/Makefile index 06316288..a2037865 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ #Makefile at top of application tree TOP = . include $(TOP)/configure/CONFIG -DIRS := configure mrfCommon evrApp mrmShared evgMrmApp evrMrmApp mrfApp evrFRIBApp iocBoot +DIRS := configure mrfCommon evrApp mrmShared evgMrmApp evrMrmApp mrfApp evrFRIBApp testApp iocBoot define DIR_template $(1)_DEPEND_DIRS = configure @@ -23,4 +23,6 @@ evrFRIBApp_DEPEND_DIRS += evrApp mrfApp_DEPEND_DIRS += evrMrmApp evgMrmApp evrFRIBApp +testApp_DEPEND_DIRS += mrfCommon + include $(TOP)/configure/RULES_TOP diff --git a/mrfCommon/src/Makefile b/mrfCommon/src/Makefile index 4eaadea8..564c8e64 100644 --- a/mrfCommon/src/Makefile +++ b/mrfCommon/src/Makefile @@ -62,6 +62,7 @@ mrfCommon_SRCS += devObjString.cpp mrfCommon_SRCS += devObjCommand.cpp mrfCommon_SRCS += devObjWf.cpp mrfCommon_SRCS += devMbboDirectSoft.c +mrfCommon_SRCS += devlutstring.cpp mrfCommon_SRCS += databuf.cpp mrfCommon_SRCS += mrfCommon.cpp diff --git a/mrfCommon/src/devlutstring.cpp b/mrfCommon/src/devlutstring.cpp new file mode 100644 index 00000000..e19b543b --- /dev/null +++ b/mrfCommon/src/devlutstring.cpp @@ -0,0 +1,182 @@ +/*************************************************************************\ +* Copyright (c) 2017 Michael Davidsaver +* mrfioc2 is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/** Arbitrary size lookup table mapping integer to string. + * + * record(stringin, "blah") { + * field(INP , "some:int") + * info(lut0, " 0 = zero") + * info(lut1, " 1 = one") + * info(lut2, " 3 = three") + * info(lutX, "unknown") + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include // For S_dev_* +#include +#include + +#include + +#include "mrfCommon.h" +#include "devObj.h" + +#include + +namespace { + +std::string strip(const std::string& inp) +{ + size_t S = inp.find_first_not_of(" \t"), + E = inp.find_last_not_of(" \t"); + if(S==inp.npos) + return std::string(); // empty + else + return inp.substr(S, E-S+1); +} + +struct DBENT { + DBENTRY entry; + template + DBENT(REC *prec) + { + dbInitEntry(pdbbase, &entry); + if(dbFindRecord(&entry, prec->name)) + throw std::logic_error("Failed to lookup DBENTRY from dbCommon"); + } + ~DBENT() { + dbFinishEntry(&entry); + } +}; + +struct LUTPriv { + typedef std::map lut_t; + lut_t lut; + std::string unknown; +}; + +static +long init_record_lut(stringinRecord *prec) +{ + try { + mrf::auto_ptr priv(new LUTPriv); + + priv->unknown = ""; + + DBENT entry(prec); + + for(long status = dbFirstInfo(&entry.entry); status==0; status = dbNextInfo(&entry.entry)) + { + const char * const name = dbGetInfoName(&entry.entry); + + std::string line(dbGetInfoString(&entry.entry)); + + if(strcmp(name, "lutX")==0) { + priv->unknown = strip(line); + if(prec->tpro>1) + printf("%s : LUT -> \"%s\"\n", prec->name, priv->unknown.c_str()); + continue; + + } else if(strncmp(name, "lut", 3)!=0) { + continue; + } + + size_t eq = line.find_first_of('='); + if(eq==line.npos) { + fprintf(stderr, "%s : info %s value missing '=' : %s\n", prec->name, name, line.c_str()); + return 0; + } + + epicsUInt32 raw; + { + std::string num(line.substr(0, eq)); + if(epicsParseUInt32(num.c_str(), &raw, 0, 0)) { + fprintf(stderr, "%s : info %s index not number \"%s\"\n", prec->name, name, num.c_str()); + throw std::runtime_error("Invalid LUT entry"); + } + } + + std::pair ret = + priv->lut.insert(std::make_pair(raw, strip(line.substr(eq+1)))); + + if(prec->tpro>1) + printf("%s : LUT %u -> \"%s\"\n", prec->name, ret.first->first, ret.first->second.c_str()); + } + + /* initialize w/ unknown value */ + strncpy(prec->val, priv->unknown.c_str(), sizeof(prec->val)); + prec->val[sizeof(prec->val)-1] = '\0'; + + prec->dpvt = priv.release(); + + return 0; + }CATCH(-ENODEV) +} + +static +long read_lut(stringinRecord *prec) +{ + const LUTPriv * const priv = static_cast(prec->dpvt); + if(!priv) { + (void)recGblSetSevr(prec, COMM_ALARM, INVALID_ALARM); + return 0; + } + try { + const std::string *val = &priv->unknown; + + epicsUInt32 raw; + long status = dbGetLink(&prec->inp, DBR_LONG, &raw, 0, 0); + + if(status) { + (void)recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM); + + } else { + LUTPriv::lut_t::const_iterator it = priv->lut.find(raw); + if(it==priv->lut.end()) { + (void)recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + } else { + val = &it->second; + } + } + + if(prec->tpro>2) + printf("%s : LUT status=%ld select=%u VAL=\"%s\"\n", + prec->name, status, (unsigned)raw, val->c_str()); + + strncpy(prec->val, val->c_str(), sizeof(prec->val)); + prec->val[sizeof(prec->val)-1] = '\0'; + + return 0; + }CATCH(-ENODEV) +} + +common_dset devLUTSI = { + 5, + 0, + 0, + (DEVSUPFUN)&init_record_lut, + 0, + (DEVSUPFUN)&read_lut +}; + +} // namespace + +extern "C" { +epicsExportAddress(dset, devLUTSI); +} diff --git a/mrfCommon/src/mrfCommon.cpp b/mrfCommon/src/mrfCommon.cpp index b350f5fb..6b80708a 100644 --- a/mrfCommon/src/mrfCommon.cpp +++ b/mrfCommon/src/mrfCommon.cpp @@ -1,6 +1,8 @@ -#include #include +#include +#include + #include #include #include "mrfCommon.h" @@ -39,3 +41,56 @@ char *allocSNPrintf(size_t N, const char *fmt, ...) return mem; } + +#if (EPICS_VERSION_INT < VERSION_INT(3,15,0,2)) + +static +epicsParseULong(const char *str, unsigned long *to, int base, char **units) +{ + int c; + char *endp; + unsigned long value; + + while ((c = *str) && isspace(c)) + ++str; + + errno = 0; + value = strtoul(str, &endp, base); + + if (endp == str) + return S_stdlib_noConversion; + if (errno == EINVAL) /* Not universally supported */ + return S_stdlib_badBase; + if (errno == ERANGE) + return S_stdlib_overflow; + + while ((c = *endp) && isspace(c)) + ++endp; + if (c && !units) + return S_stdlib_extraneous; + + *to = value; + if (units) + *units = endp; + return 0; +} + +int +epicsParseUInt32(const char *str, epicsUInt32 *to, int base, char **units) +{ + unsigned long value; + int status = epicsParseULong(str, &value, base, units); + + if (status) + return status; + +#if (ULONG_MAX > 0xffffffffULL) + if (value > 0xffffffffUL && value <= ~0xffffffffUL) + return S_stdlib_overflow; +#endif + + *to = (epicsUInt32) value; + return 0; +} + +#endif diff --git a/mrfCommon/src/mrfCommon.dbd b/mrfCommon/src/mrfCommon.dbd index e8dbb99f..5be0d30d 100644 --- a/mrfCommon/src/mrfCommon.dbd +++ b/mrfCommon/src/mrfCommon.dbd @@ -56,6 +56,8 @@ device(bo, INST_IO, devBOCommand, "Obj Prop command") device(waveform , INST_IO, devWFIn, "Obj Prop waveform in") device(waveform , INST_IO, devWFOut, "Obj Prop waveform out") +# from devlutstring.cpp +device(stringin , CONSTANT, devLUTSI, "LUT uint32 -> string") # Special version of normal mbboDirect soft dset which restores bit fields from VAL device(mbboDirect, CONSTANT, devMbboDirectRestore, "Soft and restore") diff --git a/mrfCommon/src/mrfCommon.h b/mrfCommon/src/mrfCommon.h index d53f1da4..3c009a5d 100644 --- a/mrfCommon/src/mrfCommon.h +++ b/mrfCommon/src/mrfCommon.h @@ -73,6 +73,7 @@ using std::auto_ptr; #include /* EPICS Time definitions */ #include /* EPICS Common math functions & definitions */ #include +#include #include /* EPICS Alarm status and severity definitions */ #include /* EPICS Database Access definitions */ @@ -274,6 +275,18 @@ epicsShareFunc char *allocSNPrintf(size_t N, const char *fmt, ...) EPICS_PRINTF_ # endif #endif /*EPICS 64-bit integer types need defining*/ + +#define S_stdlib_noConversion (M_stdlib | 1) /* No digits to convert */ +#define S_stdlib_extraneous (M_stdlib | 2) /* Extraneous characters */ +#define S_stdlib_underflow (M_stdlib | 3) /* Too small to represent */ +#define S_stdlib_overflow (M_stdlib | 4) /* Too large to represent */ +#define S_stdlib_badBase (M_stdlib | 5) /* Number base not supported */ + +extern "C" { +epicsShareFunc int +epicsParseUInt32(const char *str, epicsUInt32 *to, int base, char **units); +} + #endif diff --git a/testApp/Makefile b/testApp/Makefile new file mode 100644 index 00000000..58edb0a6 --- /dev/null +++ b/testApp/Makefile @@ -0,0 +1,33 @@ +TOP=.. + +include $(TOP)/configure/CONFIG +#---------------------------------------- +# ADD MACRO DEFINITIONS AFTER THIS LINE +#============================= + +ifdef BASE_3_15 + +TARGETS += $(COMMON_DIR)/testmrf.dbd +DBDDEPENDS_FILES += testmrf.dbd$(DEP) + +testmrf_DBD += base.dbd +testmrf_DBD += mrfCommon.dbd + +TESTPROD_HOST += testlut +testlut_SRCS += testlut.c +testlut_SRCS += testmrf_registerRecordDeviceDriver.cpp +TESTS += testlut + +PROD_LIBS += mrfCommon +PROD_LIBS += $(EPICS_BASE_IOC_LIBS) + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +endif # BASE_3_15 + +#=========================== + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/testApp/lut.db b/testApp/lut.db new file mode 100644 index 00000000..58e94b93 --- /dev/null +++ b/testApp/lut.db @@ -0,0 +1,11 @@ +record(longout, "input") { + field(FLNK, "output") +} +record(stringin, "output") { + field(DTYP, "LUT uint32 -> string") + field(INP , "input NPP MSI") + info(lutX , "unknown") + info(lut0 , " 0 = zero") + info(lut1 , " 0x5 = five") + field(TPRO, "2") +} diff --git a/testApp/testlut.c b/testApp/testlut.c new file mode 100644 index 00000000..3c231b1f --- /dev/null +++ b/testApp/testlut.c @@ -0,0 +1,57 @@ +/*************************************************************************\ +* Copyright (c) 2017 Michael Davidsaver +* mrfioc2 is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include +#include +#include +#include + +void testmrf_registerRecordDeviceDriver(struct dbBase *); + +MAIN(testlut) +{ + testPlan(15); + + testdbPrepare(); + + testdbReadDatabase("testmrf.dbd", 0, 0); + testmrf_registerRecordDeviceDriver(pdbbase); + testdbReadDatabase("lut.db", 0, 0); + + eltc(0); + testIocInitOk(); + eltc(1); + + testDiag("Initial values"); + testdbGetFieldEqual("output.STAT", DBF_LONG, UDF_ALARM); + testdbGetFieldEqual("output.SEVR", DBF_LONG, INVALID_ALARM); + testdbGetFieldEqual("output.VAL", DBF_STRING, "unknown"); + + testDiag("Set valid 0"); + testdbPutFieldOk("input", DBR_LONG, 0); + testdbGetFieldEqual("output.STAT", DBF_LONG, 0); + testdbGetFieldEqual("output.SEVR", DBF_LONG, 0); + testdbGetFieldEqual("output.VAL", DBF_STRING, "zero"); + + testDiag("Set valid 5"); + testdbPutFieldOk("input", DBR_LONG, 5); + testdbGetFieldEqual("output.STAT", DBF_LONG, 0); + testdbGetFieldEqual("output.SEVR", DBF_LONG, 0); + testdbGetFieldEqual("output.VAL", DBF_STRING, "five"); + + testDiag("Set invalid 3"); + testdbPutFieldOk("input", DBR_LONG, 3); + testdbGetFieldEqual("output.STAT", DBF_LONG, READ_ALARM); + testdbGetFieldEqual("output.SEVR", DBF_LONG, INVALID_ALARM); + testdbGetFieldEqual("output.VAL", DBF_STRING, "unknown"); + + testIocShutdownOk(); + + testdbCleanup(); + + return testDone(); +}