diff --git a/src/Makefile b/src/Makefile index 3307b8786..74dfc172a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -75,6 +75,8 @@ std_DEPEND_DIRS = ioc libCom/RTEMS DIRS += std/filters/test std/filters/test_DEPEND_DIRS = std +DIRS += std/rec/test +std/rec/test_DEPEND_DIRS = std include $(TOP)/configure/RULES_DIRS diff --git a/src/ioc/db/recGbl.c b/src/ioc/db/recGbl.c index 7388d6e29..5880fdbfd 100644 --- a/src/ioc/db/recGbl.c +++ b/src/ioc/db/recGbl.c @@ -274,7 +274,34 @@ void recGblTSELwasModified(struct link *plink) ppv_link->pvlMask |= pvlOptTSELisTime; } } - + +void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval, + const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask) +{ + double delta = 0; + + if (finite(newval) && finite(*poldval)) { + /* both are finite -> compare delta with deadband */ + delta = *poldval - newval; + if (delta < 0.0) delta = -delta; + } + else if (!isnan(newval) != !isnan(*poldval) || + !isinf(newval) != !isinf(*poldval)) { + /* one is NaN or +-inf, the other not -> send update */ + delta = epicsINF; + } + else if (isinf(newval) && newval != *poldval) { + /* one is +inf, the other -inf -> send update */ + delta = epicsINF; + } + if (delta > deadband) { + /* add bits to monitor mask */ + *monitor_mask |= add_mask; + /* update last value monitored */ + *poldval = newval; + } +} + static void getMaxRangeValues(short field_type, double *pupper_limit, double *plower_limit) { diff --git a/src/ioc/db/recGbl.h b/src/ioc/db/recGbl.h index 64db229ed..04a0ee4d1 100644 --- a/src/ioc/db/recGbl.h +++ b/src/ioc/db/recGbl.h @@ -62,6 +62,8 @@ epicsShareFunc int recGblSetSevr(void *precord, epicsEnum16 new_stat, epicsShareFunc void recGblFwdLink(void *precord); epicsShareFunc void recGblGetTimeStamp(void *precord); epicsShareFunc void recGblTSELwasModified(struct link *plink); +epicsShareFunc void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval, + const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask); #ifdef __cplusplus } diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index 2c374985b..72c6ed24f 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -81,6 +81,12 @@ TESTS += arrShorthandTest TESTPROD_HOST += benchdbConvert benchdbConvert_SRCS += benchdbConvert.c +TESTPROD_HOST += recGblCheckDeadbandTest +recGblCheckDeadbandTest_SRCS += recGblCheckDeadbandTest.c +recGblCheckDeadbandTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += recGblCheckDeadbandTest.c +TESTS += recGblCheckDeadbandTest + # The testHarness runs all the test programs in a known working order. testHarness_SRCS += epicsRunDbTests.c diff --git a/src/ioc/db/test/epicsRunDbTests.c b/src/ioc/db/test/epicsRunDbTests.c index ed6fd3c2a..ae150b55a 100644 --- a/src/ioc/db/test/epicsRunDbTests.c +++ b/src/ioc/db/test/epicsRunDbTests.c @@ -25,6 +25,7 @@ int dbPutLinkTest(void); int testDbChannel(void); int chfPluginTest(void); int arrShorthandTest(void); +int recGblCheckDeadbandTest(void); void epicsRunDbTests(void) { @@ -38,6 +39,7 @@ void epicsRunDbTests(void) runTest(dbPutLinkTest); runTest(testDbChannel); runTest(arrShorthandTest); + runTest(recGblCheckDeadbandTest); runTest(chfPluginTest); dbmfFreeChunks(); diff --git a/src/ioc/db/test/recGblCheckDeadbandTest.c b/src/ioc/db/test/recGblCheckDeadbandTest.c new file mode 100644 index 000000000..d3cac3a45 --- /dev/null +++ b/src/ioc/db/test/recGblCheckDeadbandTest.c @@ -0,0 +1,119 @@ +/*************************************************************************\ +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "recGbl.h" +#include "dbBase.h" +#include "epicsMath.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +/* Test parameters */ + +#define NO_OF_DEADBANDS 3 +#define NO_OF_PATTERNS 19 + +void dbTestIoc_registerRecordDeviceDriver(struct dbBase *); + +/* Indices for deadband value, test number, val in sequence */ +static int idbnd, itest; + +/* Different deadbands to test with */ +static double t_Deadband[NO_OF_DEADBANDS] = { -1, 0, 1.5 }; +/* Value sequences for each of the 16 tests */ +static double t_SetValues[NO_OF_PATTERNS][2]; +/* Expected updates (1=yes) for each sequence of each test of each deadband */ +static int t_ExpectedUpdates[NO_OF_DEADBANDS][NO_OF_PATTERNS] = { + { /* deadband = -1 */ + 1, 1, 1, 1, + 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + }, + { /* deadband = 0 */ + 1, 1, 0, 0, + 1, 1, 1, + 1, 0, 1, 1, + 1, 1, 0, 1, + 1, 1, 1, 0, + }, + { /* deadband = 1.5 */ + 0, 1, 0, 0, + 1, 1, 1, + 1, 0, 1, 1, + 1, 1, 0, 1, + 1, 1, 1, 0, + }, +}; + +MAIN(recGblCheckDeadbandTest) +{ + unsigned mask; + double oldval, newval; + + /* Test patterns: + * 0: step less than deadband (of 1.5) + * 1: step larger than deadband (of 1.5) + * 2: no change + * 3: -0.0 -> +0.0 + * ... all possible combinations of steps + * between: finite / NaN / -inf / +inf + */ + t_SetValues[ 0][0] = 1.0; t_SetValues[ 0][1] = 2.0; + t_SetValues[ 1][0] = 0.0; t_SetValues[ 1][1] = 2.0; + t_SetValues[ 2][0] = 0.0; t_SetValues[ 2][1] = 0.0; + t_SetValues[ 3][0] = -0.0; t_SetValues[ 3][1] = 0.0; + t_SetValues[ 4][0] = 1.0; t_SetValues[ 4][1] = epicsNAN; + t_SetValues[ 5][0] = 1.0; t_SetValues[ 5][1] = epicsINF; + t_SetValues[ 6][0] = 1.0; t_SetValues[ 6][1] = -epicsINF; + t_SetValues[ 7][0] = epicsNAN; t_SetValues[ 7][1] = 1.0; + t_SetValues[ 8][0] = epicsNAN; t_SetValues[ 8][1] = epicsNAN; + t_SetValues[ 9][0] = epicsNAN; t_SetValues[ 9][1] = epicsINF; + t_SetValues[10][0] = epicsNAN; t_SetValues[10][1] = -epicsINF; + t_SetValues[11][0] = epicsINF; t_SetValues[11][1] = 1.0; + t_SetValues[12][0] = epicsINF; t_SetValues[12][1] = epicsNAN; + t_SetValues[13][0] = epicsINF; t_SetValues[13][1] = epicsINF; + t_SetValues[14][0] = epicsINF; t_SetValues[14][1] = -epicsINF; + t_SetValues[15][0] = -epicsINF; t_SetValues[15][1] = 1.0; + t_SetValues[16][0] = -epicsINF; t_SetValues[16][1] = epicsNAN; + t_SetValues[17][0] = -epicsINF; t_SetValues[17][1] = epicsINF; + t_SetValues[18][0] = -epicsINF; t_SetValues[18][1] = -epicsINF; + + testPlan(114); + + /* Loop over all tested deadband values */ + for (idbnd = 0; idbnd < NO_OF_DEADBANDS; idbnd++) { + + /* Loop over all test patterns */ + for (itest = 0; itest < NO_OF_PATTERNS; itest++) { + oldval = t_SetValues[itest][0]; + newval = t_SetValues[itest][1]; + mask = 0; + + recGblCheckDeadband(&oldval, newval, t_Deadband[idbnd], &mask, 1); + + /* Check expected vs. actual test result */ + testOk(t_ExpectedUpdates[idbnd][itest] == mask, + "deadband=%2.1f: check for oldvalue=%f newvalue=%f (expected %d, got %d)", + t_Deadband[idbnd], t_SetValues[itest][0], t_SetValues[itest][1], + t_ExpectedUpdates[idbnd][itest], mask); + + if (mask) { + testOk((oldval == newval) || (isnan(oldval) && isnan(newval)), "mask set, oldval equals newval"); + } else { + testOk((oldval == t_SetValues[itest][0]) || (isnan(oldval) && isnan(t_SetValues[itest][0])), + "mask not set, oldval unchanged"); + } + } + } + return testDone(); +} diff --git a/src/std/filters/dbnd.c b/src/std/filters/dbnd.c index 3147c088f..36c852b2b 100644 --- a/src/std/filters/dbnd.c +++ b/src/std/filters/dbnd.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -56,17 +57,11 @@ static int parse_ok(void *pvt) return 0; } -static void shiftval (myStruct *my, double val) { - my->last = val; - if (my->mode == 1) - my->hyst = val * my->cval/100.; -} - static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { myStruct *my = (myStruct*) pvt; long status; - double val, delta; - short drop = 0; + double val; + unsigned send = 1; /* * Only scalar values supported - strings, arrays, and conversion errors @@ -81,19 +76,14 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { status = dbFastGetConvertRoutine[pfl->field_type][DBR_DOUBLE] (localAddr.pfield, (void*) &val, &localAddr); if (!status) { - if (isnan(my->last)) { - shiftval(my, val); - } else { - delta = fabs(my->last - val); - if (delta <= my->hyst) { - drop = 1; - } else { - shiftval(my, val); - } + send = 0; + recGblCheckDeadband(&my->last, val, my->hyst, &send, 1); + if (send && my->mode == 1) { + my->hyst = val * my->cval/100.; } } } - if (drop) { + if (!send) { db_delete_field_log(pfl); return NULL; } else return pfl; diff --git a/src/std/rec/aiRecord.c b/src/std/rec/aiRecord.c index c7f4d7977..b624fde87 100644 --- a/src/std/rec/aiRecord.c +++ b/src/std/rec/aiRecord.c @@ -456,29 +456,13 @@ static void convert(aiRecord *prec) static void monitor(aiRecord *prec) { - unsigned short monitor_mask; - double delta; + unsigned monitor_mask = recGblResetAlarms(prec); - monitor_mask = recGblResetAlarms(prec); - /* check for value change */ - delta = prec->mlst - prec->val; - if(delta<0.0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); - /* check for archive change */ - delta = prec->alst - prec->val; - if(delta<0.0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); /* send out monitors connected to the value field */ if (monitor_mask){ diff --git a/src/std/rec/aoRecord.c b/src/std/rec/aoRecord.c index 78febffe5..a3def5607 100644 --- a/src/std/rec/aoRecord.c +++ b/src/std/rec/aoRecord.c @@ -507,34 +507,19 @@ static void convert(aoRecord *prec, double value) static void monitor(aoRecord *prec) { - unsigned short monitor_mask; - double delta; + unsigned monitor_mask = recGblResetAlarms(prec); - monitor_mask = recGblResetAlarms(prec); - /* check for value change */ - delta = prec->mlst - prec->val; - if(delta<0.0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } - /* check for archive change */ - delta = prec->alst - prec->val; - if(delta<0.0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } - /* send out monitors connected to the value field */ - if (monitor_mask){ - db_post_events(prec,&prec->val,monitor_mask); - } if(prec->omod) monitor_mask |= (DBE_VALUE|DBE_LOG); if(monitor_mask) { prec->omod = FALSE; diff --git a/src/std/rec/calcRecord.c b/src/std/rec/calcRecord.c index 9a7a56e96..a83ee073a 100644 --- a/src/std/rec/calcRecord.c +++ b/src/std/rec/calcRecord.c @@ -395,34 +395,23 @@ static void checkAlarms(calcRecord *prec, epicsTimeStamp *timeLast) static void monitor(calcRecord *prec) { - unsigned short monitor_mask; - double delta, *pnew, *pprev; + unsigned monitor_mask; + double *pnew, *pprev; int i; monitor_mask = recGblResetAlarms(prec); + /* check for value change */ - delta = prec->mlst - prec->val; - if (delta < 0.0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + /* check for archive change */ - delta = prec->alst - prec->val; - if (delta < 0.0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); /* send out monitors connected to the value field */ if (monitor_mask){ db_post_events(prec, &prec->val, monitor_mask); } + /* check all input fields for changes*/ pnew = &prec->a; pprev = &prec->la; diff --git a/src/std/rec/calcoutRecord.c b/src/std/rec/calcoutRecord.c index 190c94c96..c62b3f446 100644 --- a/src/std/rec/calcoutRecord.c +++ b/src/std/rec/calcoutRecord.c @@ -622,50 +622,38 @@ static void execOutput(calcoutRecord *prec) static void monitor(calcoutRecord *prec) { - unsigned monitor_mask; - double delta; - double *pnew; - double *pprev; - int i; + unsigned monitor_mask; + double *pnew; + double *pprev; + int i; - monitor_mask = recGblResetAlarms(prec); - /* check for value change */ - delta = prec->mlst - prec->val; - if (delta<0.0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } - /* check for archive change */ - delta = prec->alst - prec->val; - if (delta<0.0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + monitor_mask = recGblResetAlarms(prec); - /* send out monitors connected to the value field */ - if (monitor_mask){ - db_post_events(prec, &prec->val, monitor_mask); + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec, &prec->val, monitor_mask); + } + + /* check all input fields for changes*/ + for (i = 0, pnew = &prec->a, pprev = &prec->la; ia, pprev = &prec->la; ipovl != prec->oval) { - db_post_events(prec, &prec->oval, monitor_mask|DBE_VALUE|DBE_LOG); - prec->povl = prec->oval; - } - return; + } + /* Check OVAL field */ + if (prec->povl != prec->oval) { + db_post_events(prec, &prec->oval, monitor_mask|DBE_VALUE|DBE_LOG); + prec->povl = prec->oval; + } + return; } static int fetch_values(calcoutRecord *prec) diff --git a/src/std/rec/dfanoutRecord.c b/src/std/rec/dfanoutRecord.c index cccc91ab4..15d06fb3c 100644 --- a/src/std/rec/dfanoutRecord.c +++ b/src/std/rec/dfanoutRecord.c @@ -259,37 +259,22 @@ static void checkAlarms(dfanoutRecord *prec) static void monitor(dfanoutRecord *prec) { - unsigned short monitor_mask; + unsigned monitor_mask = recGblResetAlarms(prec); - double delta; + /* check for value change */ + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); - monitor_mask = recGblResetAlarms(prec); - /* check for value change */ - delta = prec->mlst - prec->val; - if(delta<0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } - /* check for archive change */ - delta = prec->alst - prec->val; - if(delta<0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + /* check for archive change */ + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); - /* send out monitors connected to the value field */ - if (monitor_mask){ - db_post_events(prec,&prec->val,monitor_mask); - } - return; + /* send out monitors connected to the value field */ + if (monitor_mask){ + db_post_events(prec,&prec->val,monitor_mask); + } + + return; } - + static void push_values(dfanoutRecord *prec) { struct link *plink; /* structure of the link field */ diff --git a/src/std/rec/selRecord.c b/src/std/rec/selRecord.c index eb9a04af8..b9f385173 100644 --- a/src/std/rec/selRecord.c +++ b/src/std/rec/selRecord.c @@ -307,31 +307,18 @@ static void checkAlarms(selRecord *prec) static void monitor(selRecord *prec) { - unsigned short monitor_mask; - double delta; + unsigned monitor_mask; double *pnew; double *pprev; int i; monitor_mask = recGblResetAlarms(prec); + /* check for value change */ - delta = prec->mlst - prec->val; - if(delta<0.0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + /* check for archive change */ - delta = prec->alst - prec->val; - if(delta<0.0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); /* send out monitors connected to the value field */ if (monitor_mask) @@ -367,62 +354,57 @@ static void do_sel(selRecord *prec) pvalue = &prec->a; switch (prec->selm){ case (selSELM_Specified): - if (prec->seln >= SEL_MAX) { - recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); - return; - } - val = *(pvalue+prec->seln); - break; + if (prec->seln >= SEL_MAX) { + recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); + return; + } + val = *(pvalue+prec->seln); + break; case (selSELM_High_Signal): - val = -epicsINF; - for (i = 0; i < SEL_MAX; i++,pvalue++){ - if (!isnan(*pvalue) && val < *pvalue) { - val = *pvalue; - prec->seln = i; - } - } - break; + val = -epicsINF; + for (i = 0; i < SEL_MAX; i++,pvalue++){ + if (!isnan(*pvalue) && val < *pvalue) { + val = *pvalue; + prec->seln = i; + } + } + break; case (selSELM_Low_Signal): - val = epicsINF; - for (i = 0; i < SEL_MAX; i++,pvalue++){ - if (!isnan(*pvalue) && val > *pvalue) { - val = *pvalue; - prec->seln = i; - } - } - break; + val = epicsINF; + for (i = 0; i < SEL_MAX; i++,pvalue++){ + if (!isnan(*pvalue) && val > *pvalue) { + val = *pvalue; + prec->seln = i; + } + } + break; case (selSELM_Median_Signal): - count = 0; - order[0] = epicsNAN; - for (i = 0; i < SEL_MAX; i++,pvalue++){ - if (!isnan(*pvalue)){ - /* Insertion sort */ - j = count; - while ((j > 0) && (order[j-1] > *pvalue)){ - order[j] = order[j-1]; - j--; - } - order[j] = *pvalue; - count++; - } - } - prec->seln = count; - val = order[count / 2]; - break; + count = 0; + order[0] = epicsNAN; + for (i = 0; i < SEL_MAX; i++,pvalue++){ + if (!isnan(*pvalue)){ + /* Insertion sort */ + j = count; + while ((j > 0) && (order[j-1] > *pvalue)){ + order[j] = order[j-1]; + j--; + } + order[j] = *pvalue; + count++; + } + } + prec->seln = count; + val = order[count / 2]; + break; default: - recGblSetSevr(prec,CALC_ALARM,INVALID_ALARM); - return; - } - if (!isinf(val)){ - prec->val=val; - prec->udf=isnan(prec->val); - } else { - recGblSetSevr(prec,UDF_ALARM,MAJOR_ALARM); - /* If UDF is TRUE this alarm will be overwritten by checkAlarms*/ + recGblSetSevr(prec,CALC_ALARM,INVALID_ALARM); + return; } + prec->val = val; + prec->udf = isnan(prec->val); return; } - + /* * FETCH_VALUES * diff --git a/src/std/rec/subRecord.c b/src/std/rec/subRecord.c index 9e4c1875f..f8808a849 100644 --- a/src/std/rec/subRecord.c +++ b/src/std/rec/subRecord.c @@ -373,35 +373,24 @@ static void checkAlarms(subRecord *prec) static void monitor(subRecord *prec) { unsigned monitor_mask; - double delta; double *pnew; double *pold; int i; /* get alarm mask */ monitor_mask = recGblResetAlarms(prec); + /* check for value change */ - delta = prec->val - prec->mlst; - if (delta < 0.0) delta = -delta; - if (!(delta <= prec->mdel)) { /* Handles MDEL == NAN */ - /* post events for value change */ - monitor_mask |= DBE_VALUE; - /* update last value monitored */ - prec->mlst = prec->val; - } + recGblCheckDeadband(&prec->mlst, prec->val, prec->mdel, &monitor_mask, DBE_VALUE); + /* check for archive change */ - delta = prec->val - prec->alst; - if (delta < 0.0) delta = -delta; - if (!(delta <= prec->adel)) { /* Handles ADEL == NAN */ - /* post events on value field for archive change */ - monitor_mask |= DBE_LOG; - /* update last archive value monitored */ - prec->alst = prec->val; - } + recGblCheckDeadband(&prec->alst, prec->val, prec->adel, &monitor_mask, DBE_ARCHIVE); + /* send out monitors connected to the value field */ if (monitor_mask) { db_post_events(prec, &prec->val, monitor_mask); } + /* check all input fields for changes */ for (i = 0, pnew = &prec->a, pold = &prec->la; i < INP_ARG_MAX; i++, pnew++, pold++) { diff --git a/src/std/rec/test/Makefile b/src/std/rec/test/Makefile new file mode 100644 index 000000000..b8657ce83 --- /dev/null +++ b/src/std/rec/test/Makefile @@ -0,0 +1,39 @@ +#************************************************************************* +# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +# National Laboratory. +# Copyright (c) 2002 The Regents of the University of California, as +# Operator of Los Alamos National Laboratory. +# EPICS BASE is distributed subject to a Software License Agreement found +# in the file LICENSE that is included with this distribution. +#************************************************************************* +TOP=../../../.. + +include $(TOP)/configure/CONFIG + +PROD_LIBS = dbRecStd dbCore ca Com + +TARGETS += $(COMMON_DIR)/analogMonitorTest.dbd +analogMonitorTest_DBD += base.dbd +TESTPROD_HOST += analogMonitorTest +analogMonitorTest_SRCS += analogMonitorTest.c +analogMonitorTest_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp +testHarness_SRCS += analogMonitorTest.c +testHarness_SRCS += analogMonitorTest_registerRecordDeviceDriver.cpp +TESTFILES += $(COMMON_DIR)/analogMonitorTest.dbd ../analogMonitorTest.db +TESTS += analogMonitorTest + +# epicsRunTests runs all the test programs in a known working order. +testHarness_SRCS += epicsRunTests.c + +recordTestHarness_SRCS += $(testHarness_SRCS) +recordTestHarness_SRCS_RTEMS += rtemsTestHarness.c + +PROD_vxWorks = recordTestHarness +PROD_RTEMS = recordTestHarness + +TESTSPEC_vxWorks = recordTestHarness.munch; epicsRunTests +TESTSPEC_RTEMS = recordTestHarness.boot; epicsRunTests + +TESTSCRIPTS_HOST += $(TESTS:%=%.t) + +include $(TOP)/configure/RULES diff --git a/src/std/rec/test/analogMonitorTest.c b/src/std/rec/test/analogMonitorTest.c new file mode 100644 index 000000000..fb7f61e39 --- /dev/null +++ b/src/std/rec/test/analogMonitorTest.c @@ -0,0 +1,240 @@ +/*************************************************************************\ +* Copyright (c) 2014 ITER Organization. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Author: Ralph Lange + */ + +#include + +#include "registryFunction.h" +#include "osiFileName.h" +#include "epicsThread.h" +#include "epicsMath.h" +#include "epicsUnitTest.h" +#include "dbAccessDefs.h" +#include "dbStaticLib.h" +#include "dbEvent.h" +#include "caeventmask.h" +#include "db_field_log.h" +#include "chfPlugin.h" +#include "iocInit.h" +#include "testMain.h" +#include "epicsExport.h" + +/* Test parameters */ + +#define NO_OF_RECORD_TYPES 7 +#define NO_OF_DEADBANDS 3 +#define NO_OF_PATTERNS 16 +#define NO_OF_VALUES_PER_SEQUENCE 2 + +void analogMonitorTest_registerRecordDeviceDriver(struct dbBase *); + +/* Indices for record type, deadband type, deadband value, test number, val in sequence */ +static int irec, ityp, idbnd, itest, iseq; + +/* Records to test with */ +static const char t_Record[NO_OF_RECORD_TYPES][10] = { + {"ai"}, {"ao"}, {"calc"}, {"calcout"}, {"dfanout"}, {"sel"}, {"sub"}, +}; +/* Deadband types to test */ +static const char t_DbndType[2][6] = { {".MDEL"}, {".ADEL"} }; +/* Different deadbands to test with */ +static double t_Deadband[NO_OF_DEADBANDS] = { -1, 0, 1.5 }; +/* Value sequences for each of the 16 tests */ +static double t_SetValues[NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE]; +/* Expected updates (1=yes) for each sequence of each test of each deadband */ +static int t_ExpectedUpdates[NO_OF_DEADBANDS][NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE] = { + { /* deadband = -1 */ + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 1}, + }, + { /* deadband = 0 */ + {1, 1}, {0, 1}, {0, 0}, {0, 0}, + {1, 1}, {1, 0}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 0}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 0}, + }, + { /* deadband = 1.5 */ + {0, 1}, {0, 1}, {0, 0}, {0, 0}, + {1, 1}, {1, 0}, {1, 1}, {1, 1}, + {1, 1}, {1, 1}, {1, 0}, {1, 1}, + {1, 1}, {1, 1}, {1, 1}, {1, 0}, + }, +}; +static int t_ReceivedUpdates[NO_OF_PATTERNS][NO_OF_VALUES_PER_SEQUENCE]; + +/* Dummy subroutine needed for sub record */ + +static long myTestSub(void *p) { + return 0; +} + + +/* Minimal pre-chain plugin to divert all monitors back into the test (before they hit the queue) */ + +static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { + double val = *((double*)chan->addr.pfield); + + /* iseq == -1 is the value reset before the test pattern -> do not count */ + if (iseq >= 0) { + t_ReceivedUpdates[itest][iseq] = 1; + testOk((val == t_SetValues[itest][iseq]) || (isnan(val) && isnan(t_SetValues[itest][iseq])), + "update %d pattern %2d with %s = %2.1f (expected %f, got %f)", + iseq, itest, (ityp==0?"MDEL":"ADEL"), t_Deadband[idbnd], t_SetValues[itest][iseq], val); + } + db_delete_field_log(pfl); + return NULL; +} + +static void channelRegisterPre(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) +{ + *cb_out = filter; +} + +static chfPluginIf pif = { + NULL, /* allocPvt, */ + NULL, /* freePvt, */ + NULL, /* parse_error, */ + NULL, /* parse_ok, */ + NULL, /* channel_open, */ + channelRegisterPre, + NULL, /* channelRegisterPost, */ + NULL, /* channel_report, */ + NULL /* channel_close */ +}; + + +MAIN(analogMonitorTest) +{ + dbChannel *pch; + const chFilterPlugin *plug; + const char test[] = "test"; + dbEventCtx evtctx; + dbEventSubscription subscr; + unsigned mask; + struct dbAddr vaddr, daddr; + double val; + char chan[50]; /* Channel name */ + char cval[50]; /* Name for test values */ + char cdel[50]; /* Name for deadband values */ + + /* Test patterns: + * 0: step less than deadband (of 1.5) + * 1: step larger than deadband (of 1.5) + * 2: no change + * 3: -0.0 -> +0.0 + * ... all possible combinations of steps + * between: finite / NaN / -inf / +inf + */ + t_SetValues[ 0][0] = 1.0; t_SetValues[ 0][1] = 2.0; + t_SetValues[ 1][0] = 0.0; t_SetValues[ 1][1] = 2.0; + t_SetValues[ 2][0] = 0.0; t_SetValues[ 2][1] = 0.0; + t_SetValues[ 3][0] = -0.0; t_SetValues[ 3][1] = 0.0; + t_SetValues[ 4][0] = epicsNAN; t_SetValues[ 4][1] = 1.0; + t_SetValues[ 5][0] = epicsNAN; t_SetValues[ 5][1] = epicsNAN; + t_SetValues[ 6][0] = epicsNAN; t_SetValues[ 6][1] = epicsINF; + t_SetValues[ 7][0] = epicsNAN; t_SetValues[ 7][1] = -epicsINF; + t_SetValues[ 8][0] = epicsINF; t_SetValues[ 8][1] = 1.0; + t_SetValues[ 9][0] = epicsINF; t_SetValues[ 9][1] = epicsNAN; + t_SetValues[10][0] = epicsINF; t_SetValues[10][1] = epicsINF; + t_SetValues[11][0] = epicsINF; t_SetValues[11][1] = -epicsINF; + t_SetValues[12][0] = -epicsINF; t_SetValues[12][1] = 1.0; + t_SetValues[13][0] = -epicsINF; t_SetValues[13][1] = epicsNAN; + t_SetValues[14][0] = -epicsINF; t_SetValues[14][1] = epicsINF; + t_SetValues[15][0] = -epicsINF; t_SetValues[15][1] = -epicsINF; + + registryFunctionAdd("myTestSub", (REGISTRYFUNCTION) myTestSub); + + testPlan(1793); + + if (dbReadDatabase(&pdbbase, "analogMonitorTest.dbd", + "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR + "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL)) + testAbort("Error reading database description 'analogMonitorTest.dbd'"); + + analogMonitorTest_registerRecordDeviceDriver(pdbbase); + + if (dbReadDatabase(&pdbbase, "analogMonitorTest.db", + "." OSI_PATH_LIST_SEPARATOR "..", NULL)) + testAbort("Error reading test database 'analogMonitorTest.db'"); + + /* Start the core IOC (no CA) */ + iocBuildIsolated(); + + evtctx = db_init_events(); + chfPluginRegister(test, &pif, NULL); + + plug = dbFindFilter(test, strlen(test)); + testOk(!!plug, "interceptor plugin registered"); + + /* Loop over all analog record types (one instance each) */ + for (irec = 0; irec < NO_OF_RECORD_TYPES; irec++) { + strcpy(cval, t_Record[irec]); + strcpy(chan, cval); + strcat(chan, ".VAL{\"test\":{}}"); + if ((strcmp(t_Record[irec], "sel") == 0) + || (strcmp(t_Record[irec], "calc") == 0) + || (strcmp(t_Record[irec], "calcout") == 0)) { + strcat(cval, ".A"); + } else { + strcat(cval, ".VAL"); + } + + testDiag("--------------------------------------------------------"); + testDiag("Testing the %s record", t_Record[irec]); + testDiag("--------------------------------------------------------"); + + pch = dbChannelCreate(chan); + testOk(!!pch, "dbChannel with test plugin created"); + testOk(!dbChannelOpen(pch), "dbChannel opened"); + + dbNameToAddr(cval, &vaddr); + + /* Loop over both tested deadband types */ + for (ityp = 0; ityp < 2; ityp++) { + strcpy(cdel, t_Record[irec]); + strcat(cdel, t_DbndType[ityp]); + dbNameToAddr(cdel, &daddr); + mask = (ityp==0?DBE_VALUE:DBE_ARCHIVE); + subscr = db_add_event(evtctx, pch, NULL, NULL, mask); + db_event_enable(subscr); + + /* Loop over all tested deadband values */ + for (idbnd = 0; idbnd < NO_OF_DEADBANDS; idbnd++) { + testDiag("Test %s%s = %g", t_Record[irec], t_DbndType[ityp], t_Deadband[idbnd]); + dbPutField(&daddr, DBR_DOUBLE, (void*) &t_Deadband[idbnd], 1); + memset(t_ReceivedUpdates, 0, sizeof(t_ReceivedUpdates)); + + /* Loop over all test patterns */ + for (itest = 0; itest < NO_OF_PATTERNS; itest++) { + iseq = -1; + val = 0.0; + dbPutField(&vaddr, DBR_DOUBLE, (void*) &val, 1); + + /* Loop over the test sequence */ + for (iseq = 0; iseq < NO_OF_VALUES_PER_SEQUENCE; iseq++) { + dbPutField(&vaddr, DBR_DOUBLE, (void*) &t_SetValues[itest][iseq], 1); + } + /* Check expected vs. actual monitors */ + testOk( (t_ExpectedUpdates[idbnd][itest][0] == t_ReceivedUpdates[itest][0]) && + (t_ExpectedUpdates[idbnd][itest][1] == t_ReceivedUpdates[itest][1]), + "pattern %2d with %s = %2.1f (expected %d-%d, got %d-%d)", + itest, (ityp==0?"MDEL":"ADEL"), t_Deadband[idbnd], + t_ExpectedUpdates[idbnd][itest][0], t_ExpectedUpdates[idbnd][itest][1], + t_ReceivedUpdates[itest][0], t_ReceivedUpdates[itest][1]); + } + } + db_cancel_event(subscr); + } + } + iocShutdown(); + return testDone(); +} diff --git a/src/std/rec/test/analogMonitorTest.db b/src/std/rec/test/analogMonitorTest.db new file mode 100644 index 000000000..5b3f4a2d7 --- /dev/null +++ b/src/std/rec/test/analogMonitorTest.db @@ -0,0 +1,13 @@ +record(ai, "ai") {} +record(ao, "ao") {} +record(calc, "calc") { + field(CALC, "A") +} +record(calcout, "calcout") { + field(CALC, "A") +} +record(dfanout, "dfanout") {} +record(sel, "sel") {} +record(sub, "sub") { + field(SNAM, "myTestSub") +} diff --git a/src/std/rec/test/epicsRunTests.c b/src/std/rec/test/epicsRunTests.c new file mode 100644 index 000000000..610ee8364 --- /dev/null +++ b/src/std/rec/test/epicsRunTests.c @@ -0,0 +1,24 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Run tests as a batch. + */ + +#include "epicsUnitTest.h" +#include "epicsExit.h" + +int analogMonitorTest(void); + +void epicsRunTests(void) +{ + testHarness(); + + runTest(analogMonitorTest); + + epicsExit(0); /* Trigger test harness */ +} diff --git a/src/std/rec/test/rtemsTestHarness.c b/src/std/rec/test/rtemsTestHarness.c new file mode 100644 index 000000000..dd25941ef --- /dev/null +++ b/src/std/rec/test/rtemsTestHarness.c @@ -0,0 +1,14 @@ +/*************************************************************************\ +* Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +extern void epicsRunTests(void); + +int main(int argc, char **argv) +{ + epicsRunTests(); /* calls epicsExit(0) */ + return 0; +}