From c1ae5064b8199fc21f4d9779b6eb58e9fabea75b Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Fri, 14 Feb 2020 10:10:58 +0000 Subject: [PATCH 1/5] Added OOPT to longout record --- modules/database/src/std/rec/longoutRecord.c | 62 ++++++++++++++++++- .../src/std/rec/longoutRecord.dbd.pod | 25 ++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index f0e2252bd..f5bb4a292 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -85,6 +85,7 @@ static void checkAlarms(longoutRecord *prec); static void monitor(longoutRecord *prec); static long writeValue(longoutRecord *prec); static void convert(longoutRecord *prec, epicsInt32 value); +static long conditional_write(longoutRecord *prec); static long init_record(struct dbCommon *pcommon, int pass) { @@ -119,6 +120,7 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->mlst = prec->val; prec->alst = prec->val; prec->lalm = prec->val; + prec->oval = prec->val; return 0; } @@ -210,6 +212,15 @@ static long special(DBADDR *paddr, int after) recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm); return(0); } + + /* If OOPT is "on change" we force a write operation */ + if (dbGetFieldIndex(paddr) == longoutRecordOUT) { + if ((!after) && (prec->oopt == longoutOOPT_On_Change)) + prec->oopt = longoutOOPT_Write_Once_Then_On_Change; + return 0; + } + + default: recGblDbaddrError(S_db_badChoice, paddr, "longout: special"); return(S_db_badChoice); @@ -381,7 +392,10 @@ static void monitor(longoutRecord *prec) static long writeValue(longoutRecord *prec) { +<<<<<<< HEAD longoutdset *pdset = (longoutdset *) prec->dset; +======= +>>>>>>> 2b7ca9598 (Added OOPT to longout record) long status = 0; if (!prec->pact) { @@ -391,7 +405,7 @@ static long writeValue(longoutRecord *prec) switch (prec->simm) { case menuYesNoNO: - status = pdset->write_longout(prec); + status = conditional_write(prec); break; case menuYesNoYES: { @@ -428,3 +442,49 @@ static void convert(longoutRecord *prec, epicsInt32 value) } prec->val = value; } + +/* Evaluate OOPT field to perform the write operation */ +static long conditional_write(longoutRecord *prec) +{ + struct longoutdset *pdset = (struct longoutdset *) prec->dset; + long status = 0; + int doDevSupWrite = 0; + + switch (prec->oopt) + { + case longoutOOPT_On_Change: + doDevSupWrite = (prec->val != prec->oval); + break; + + case longoutOOPT_Write_Once_Then_On_Change: + prec->oopt = longoutOOPT_On_Change; + case longoutOOPT_Every_Time: + doDevSupWrite = 1; + break; + + case longoutOOPT_When_Zero: + doDevSupWrite = (prec->val == 0); + break; + + case longoutOOPT_When_Non_zero: + doDevSupWrite = (prec->val != 0); + break; + + case longoutOOPT_Transition_To_Zero: + doDevSupWrite = ((prec->val == 0)&&(prec->oval != 0)); + break; + + case longoutOOPT_Transition_To_Non_zero: + doDevSupWrite = ((prec->val != 0)&&(prec->oval == 0)); + break; + + default: + break; + } + + if (doDevSupWrite) + status = pdset->write_longout(prec); + + prec->oval = prec->val; + return status; +} \ No newline at end of file diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index 60037a62f..3f11769ba 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -20,6 +20,17 @@ limits. =cut +menu(longoutOOPT) { + choice(longoutOOPT_Every_Time,"Every Time") + choice(longoutOOPT_On_Change,"On Change") + choice(longoutOOPT_When_Zero,"When Zero") + choice(longoutOOPT_When_Non_zero,"When Non-zero") + choice(longoutOOPT_Transition_To_Zero,"Transition To Zero") + choice(longoutOOPT_Transition_To_Non_zero,"Transition To Non-zero") + choice(longoutOOPT_Write_Once_Then_On_Change, "Write Once Then On Change") +} + + recordtype(longout) { =head2 Parameter Fields @@ -93,6 +104,7 @@ and database links. } field(OUT,DBF_OUTLINK) { prompt("Output Specification") + special(SPC_MOD) promptgroup("50 - Output") interest(1) } @@ -373,6 +385,19 @@ for more information on simulation mode and its fields. promptgroup("50 - Output") interest(2) } + field(OVAL,DBF_LONG) { + prompt("Last Value Written") + promptgroup("50 - Output") + asl(ASL1) + special(SPC_NOMOD) + } + field(OOPT,DBF_MENU) { + prompt("Output Execute Opt") + promptgroup("50 - Output") + interest(1) + menu(longoutOOPT) + initial("0") + } =begin html From 4e7a18bfb40be89b848cc00ff3fdc00335b450a6 Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Mon, 17 Feb 2020 22:14:46 +0000 Subject: [PATCH 2/5] Adding test routines for longout record with OOPT field --- modules/database/test/std/rec/Makefile | 7 + modules/database/test/std/rec/longoutTest.c | 214 +++++++++++++++++++ modules/database/test/std/rec/longoutTest.db | 17 ++ 3 files changed, 238 insertions(+) create mode 100644 modules/database/test/std/rec/longoutTest.c create mode 100644 modules/database/test/std/rec/longoutTest.db diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile index e8c546442..7fd6c9dfc 100644 --- a/modules/database/test/std/rec/Makefile +++ b/modules/database/test/std/rec/Makefile @@ -87,6 +87,13 @@ testHarness_SRCS += seqTest.c TESTFILES += ../seqTest.db TESTS += seqTest +TESTPROD_HOST += longoutTest +longoutTest_SRCS += longoutTest.c +longoutTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += longoutTest.c +TESTFILES += ../longoutTest.db +TESTS += longoutTest + TARGETS += $(COMMON_DIR)/asTestIoc.dbd DBDDEPENDS_FILES += asTestIoc.dbd$(DEP) asTestIoc_DBD += base.dbd diff --git a/modules/database/test/std/rec/longoutTest.c b/modules/database/test/std/rec/longoutTest.c new file mode 100644 index 000000000..60da091b2 --- /dev/null +++ b/modules/database/test/std/rec/longoutTest.c @@ -0,0 +1,214 @@ +/*************************************************************************\ +* Copyright (c) 2020 Joao Paulo Martins +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include "dbUnitTest.h" +#include "testMain.h" +#include "dbLock.h" +#include "errlog.h" +#include "dbAccess.h" +#include "epicsMath.h" + +#include "longoutRecord.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void test_oopt_everytime(void){ + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write the same value two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* write two times with different values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was processed 4 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 4.0); + + // number of tests = 6 +} + +static void test_oopt_onchange(void){ + /* change OOPT to On Change */ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write the same value two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was processed only once */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + + /* write two times with different values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was processed 1 + 2 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 3.0); + + //number of tests 8 +} + +static void test_oopt_whenzero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Zero); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write zero two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was processed twice */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + /* write two times with non-zero values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was still processed 2 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + //number of tests 8 +} + +static void test_oopt_whennonzero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Non_zero); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write zero two times */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + + /* write two times with non-zero values*/ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was still processed 2 times */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + //number of tests 8 +} + +static void test_oopt_when_transition_zero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Zero); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write non-zero then zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + + /* write another transition to zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + + /* Test if the counter was processed once more */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + + //number of tests 9 +} + +static void test_oopt_when_transition_nonzero(void){ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Non_zero); + + /* write non-zero to start fresh */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + + /* write non-zero then zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + + /* write a transition to non-zero */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); + + /* Test if the counter was processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + + //number of tests 8 +} + +static void test_changing_out_field(void){ + /* change OOPT to On Change */ + testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change); + + /* write an initial value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* reset rec processing counter */ + testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + testdbPutFieldOk("counter2.VAL", DBF_DOUBLE, 0.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + + /* change the OUT link to another counter */ + testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter2.B PP"); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was processed once */ + testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was not processed again */ + testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); +} + +MAIN(longoutTest) { + + testPlan(6+8+8+8+9+8+11); + + testdbPrepare(); + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("longoutTest.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + test_oopt_everytime(); + test_oopt_onchange(); + test_oopt_whenzero(); + test_oopt_whennonzero(); + test_oopt_when_transition_zero(); + test_oopt_when_transition_nonzero(); + test_changing_out_field(); + + testIocShutdownOk(); + testdbCleanup(); + + return testDone(); +} diff --git a/modules/database/test/std/rec/longoutTest.db b/modules/database/test/std/rec/longoutTest.db new file mode 100644 index 000000000..93428a070 --- /dev/null +++ b/modules/database/test/std/rec/longoutTest.db @@ -0,0 +1,17 @@ +record(calc, "counter") { + field(INPA, "counter") + field(CALC, "A+1") + field(SCAN, "Passive") +} + +record(calc, "counter2") { + field(INPA, "counter2") + field(CALC, "A+1") + field(SCAN, "Passive") +} + +record(longout, "longout_rec") { + field(VAL, "0") + field(OUT, "counter.B PP") + field(PINI, "YES") +} From f4d94b9725278f21bcfb0d6a38f7ab670330992c Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Tue, 9 Mar 2021 16:26:53 +0100 Subject: [PATCH 3/5] Longout OOPT field refactoring and updated documentation; Release notes additions --- documentation/RELEASE_NOTES.md | 7 + modules/database/src/std/rec/longoutRecord.c | 37 +++--- .../src/std/rec/longoutRecord.dbd.pod | 62 +++++++-- modules/database/test/std/rec/longoutTest.c | 124 ++++++++++++------ modules/database/test/std/rec/longoutTest.db | 10 +- 5 files changed, 169 insertions(+), 71 deletions(-) diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 587b52448..213370363 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -16,6 +16,13 @@ should also be read to understand what has changed since earlier releases. ## Changes made on the 7.0 branch since 7.0.5 +### Extend longout conditions to write the OUT link (OOPT field) + +The longout record has now the capacity to condition its output write operation to +different options, using the OOPT field (similar to calcout record). This is +the first output record to have such feature as a result of the +[Make output records only write on change](https://bugs.launchpad.net/epics-base/+bug/1398215) +issue on Launchpad. ----- diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index f5bb4a292..e60e01c79 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -81,6 +81,9 @@ rset longoutRSET={ }; epicsExportAddress(rset,longoutRSET); +#define OUT_LINK_UNCHANGED 0 +#define OUT_LINK_CHANGED 1 + static void checkAlarms(longoutRecord *prec); static void monitor(longoutRecord *prec); static long writeValue(longoutRecord *prec); @@ -120,7 +123,8 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->mlst = prec->val; prec->alst = prec->val; prec->lalm = prec->val; - prec->oval = prec->val; + prec->pval = prec->val; + prec->outpvt = OUT_LINK_UNCHANGED; return 0; } @@ -213,14 +217,13 @@ static long special(DBADDR *paddr, int after) return(0); } - /* If OOPT is "on change" we force a write operation */ + /* Detect an output link re-direction (change)*/ if (dbGetFieldIndex(paddr) == longoutRecordOUT) { - if ((!after) && (prec->oopt == longoutOOPT_On_Change)) - prec->oopt = longoutOOPT_Write_Once_Then_On_Change; - return 0; + if (!after) + prec->outpvt = OUT_LINK_CHANGED; + return(0); } - default: recGblDbaddrError(S_db_badChoice, paddr, "longout: special"); return(S_db_badChoice); @@ -392,10 +395,7 @@ static void monitor(longoutRecord *prec) static long writeValue(longoutRecord *prec) { -<<<<<<< HEAD longoutdset *pdset = (longoutdset *) prec->dset; -======= ->>>>>>> 2b7ca9598 (Added OOPT to longout record) long status = 0; if (!prec->pact) { @@ -435,7 +435,7 @@ static long writeValue(longoutRecord *prec) static void convert(longoutRecord *prec, epicsInt32 value) { - /* check drive limits */ + /* check drive limits */ if(prec->drvh > prec->drvl) { if (value > prec->drvh) value = prec->drvh; else if (value < prec->drvl) value = prec->drvl; @@ -453,11 +453,15 @@ static long conditional_write(longoutRecord *prec) switch (prec->oopt) { case longoutOOPT_On_Change: - doDevSupWrite = (prec->val != prec->oval); + /* Forces a write op if a change in the OUT field is detected */ + if ((prec->ooch == menuYesNoYES) && (prec->outpvt == OUT_LINK_CHANGED)) { + doDevSupWrite = 1; + } else { + /* Only write if value is different from the previous one */ + doDevSupWrite = (prec->val != prec->pval); + } break; - case longoutOOPT_Write_Once_Then_On_Change: - prec->oopt = longoutOOPT_On_Change; case longoutOOPT_Every_Time: doDevSupWrite = 1; break; @@ -471,11 +475,11 @@ static long conditional_write(longoutRecord *prec) break; case longoutOOPT_Transition_To_Zero: - doDevSupWrite = ((prec->val == 0)&&(prec->oval != 0)); + doDevSupWrite = ((prec->val == 0)&&(prec->pval != 0)); break; case longoutOOPT_Transition_To_Non_zero: - doDevSupWrite = ((prec->val != 0)&&(prec->oval == 0)); + doDevSupWrite = ((prec->val != 0)&&(prec->pval == 0)); break; default: @@ -485,6 +489,7 @@ static long conditional_write(longoutRecord *prec) if (doDevSupWrite) status = pdset->write_longout(prec); - prec->oval = prec->val; + prec->pval = prec->val; + prec->outpvt = OUT_LINK_UNCHANGED; /* reset status of OUT link */ return status; } \ No newline at end of file diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index 3f11769ba..fe596c118 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -27,10 +27,8 @@ menu(longoutOOPT) { choice(longoutOOPT_When_Non_zero,"When Non-zero") choice(longoutOOPT_Transition_To_Zero,"Transition To Zero") choice(longoutOOPT_Transition_To_Non_zero,"Transition To Non-zero") - choice(longoutOOPT_Write_Once_Then_On_Change, "Write Once Then On Change") } - recordtype(longout) { =head2 Parameter Fields @@ -82,7 +80,45 @@ DTYP field must then specify the C<<< Soft Channel >>> device support routine. See L
for information on the format of hardware addresses and database links. -=fields OUT, DTYP +=fields OUT, DTYP, OOPT, OOCH + +=head4 Menu longoutOOPT + +The OOPT field determines the condition that causes the output link to be +written to. It's a menu field that has six choices: + +=menu longoutOOPT + +=over + +=item * +C -- write output every time record is processed. (DEFAULT) + +=item * +C -- write output every time VAL changes, i.e., every time the +result of the expression changes. + +=item * +C -- when record is processed, write output if VAL is zero. + +=item * +C -- when record is processed, write output if VAL is +non-zero. + +=item * +C -- when record is processed, write output only if VAL +is zero and the last value was non-zero. + +=item * +C -- when record is processed, write output only if +VAL is non-zero and last value was zero. + +=back + +=head4 Changes in OUT field when OOPT = On Change + +The OOCH field determines if a change in OUT field should cause a write operation +even when the value is the same and OOPT = On Change. By default, OOCH is set to YES. =cut @@ -372,7 +408,7 @@ for more information on simulation mode and its fields. prompt("Sim. Mode Private") special(SPC_NOMOD) interest(4) - extra("epicsCallback *simpvt") + extra("epicsCallback *simpvt") } field(IVOA,DBF_MENU) { prompt("INVALID output action") @@ -385,11 +421,21 @@ for more information on simulation mode and its fields. promptgroup("50 - Output") interest(2) } - field(OVAL,DBF_LONG) { - prompt("Last Value Written") - promptgroup("50 - Output") - asl(ASL1) + field(PVAL,DBF_LONG) { + prompt("Previous Value") + } + field(OUTPVT,DBF_NOACCESS) { + prompt("Output Link Changed Private") special(SPC_NOMOD) + interest(4) + extra("epicsEnum16 outpvt") + } + field(OOCH,DBF_MENU) { + prompt("Output Execute On Change") + promptgroup("50 - Output") + interest(1) + menu(menuYesNo) + initial("1") } field(OOPT,DBF_MENU) { prompt("Output Execute Opt") diff --git a/modules/database/test/std/rec/longoutTest.c b/modules/database/test/std/rec/longoutTest.c index 60da091b2..c69a6b386 100644 --- a/modules/database/test/std/rec/longoutTest.c +++ b/modules/database/test/std/rec/longoutTest.c @@ -10,14 +10,15 @@ #include "errlog.h" #include "dbAccess.h" #include "epicsMath.h" +#include "menuYesNo.h" #include "longoutRecord.h" void recTestIoc_registerRecordDeviceDriver(struct dbBase *); static void test_oopt_everytime(void){ - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write the same value two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); @@ -27,8 +28,8 @@ static void test_oopt_everytime(void){ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was processed 4 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 4.0); + /* Test if the counter_a was processed 4 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 4.0); // number of tests = 6 } @@ -37,22 +38,22 @@ static void test_oopt_onchange(void){ /* change OOPT to On Change */ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_On_Change); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write the same value two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); - /* Test if the counter was processed only once */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + /* Test if the counter_a was processed only once */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); /* write two times with different values*/ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was processed 1 + 2 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 3.0); + /* Test if the counter_a was processed 1 + 2 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 3.0); //number of tests 8 } @@ -60,22 +61,22 @@ static void test_oopt_onchange(void){ static void test_oopt_whenzero(void){ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Zero); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write zero two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was processed twice */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was processed twice */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); /* write two times with non-zero values*/ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was still processed 2 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was still processed 2 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); //number of tests 8 } @@ -83,22 +84,22 @@ static void test_oopt_whenzero(void){ static void test_oopt_whennonzero(void){ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_When_Non_zero); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write zero two times */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was never processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + /* Test if the counter_a was never processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); /* write two times with non-zero values*/ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was still processed 2 times */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was still processed 2 times */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); //number of tests 8 } @@ -106,23 +107,23 @@ static void test_oopt_whennonzero(void){ static void test_oopt_when_transition_zero(void){ testdbPutFieldOk("longout_rec.OOPT", DBF_ENUM, longoutOOPT_Transition_To_Zero); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write non-zero then zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + /* Test if the counter_a was processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); /* write another transition to zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); - /* Test if the counter was processed once more */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 2.0); + /* Test if the counter_a was processed once more */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 2.0); //number of tests 9 } @@ -133,21 +134,21 @@ static void test_oopt_when_transition_nonzero(void){ /* write non-zero to start fresh */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counter_a */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); /* write non-zero then zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 0); - /* Test if the counter was never processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + /* Test if the counter_a was never processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); /* write a transition to non-zero */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18); - /* Test if the counter was processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 1.0); + /* Test if the counter_a was processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); //number of tests 8 } @@ -159,35 +160,74 @@ static void test_changing_out_field(void){ /* write an initial value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); - /* reset rec processing counter */ - testdbPutFieldOk("counter.VAL", DBF_DOUBLE, 0.0); - testdbPutFieldOk("counter2.VAL", DBF_DOUBLE, 0.0); + /* reset rec processing counters */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); + testdbPutFieldOk("counter_b.VAL", DBF_DOUBLE, 0.0); /* write the same value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); /* Test if the counter was never processed */ - testdbGetFieldEqual("counter", DBF_DOUBLE, 0.0); + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); /* change the OUT link to another counter */ - testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter2.B PP"); + testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter_b.B PP"); /* write the same value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); /* Test if the counter was processed once */ - testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); + testdbGetFieldEqual("counter_b", DBF_DOUBLE, 1.0); /* write the same value */ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); /* Test if the counter was not processed again */ - testdbGetFieldEqual("counter2", DBF_DOUBLE, 1.0); + testdbGetFieldEqual("counter_b", DBF_DOUBLE, 1.0); + + /* Set option to write ON CHANGE even when the OUT link was changed */ + testdbPutFieldOk("longout_rec.OOCH", DBF_ENUM, menuYesNoNO); + + /* write an initial value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* reset rec processing counters */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); + testdbPutFieldOk("counter_b.VAL", DBF_DOUBLE, 0.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter_b was never processed */ + testdbGetFieldEqual("counter_b", DBF_DOUBLE, 0.0); + + /* change back the OUT link to counter_a */ + testdbPutFieldOk("longout_rec.OUT", DBF_STRING, "counter_a.B PP"); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was never processed */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 16); + + /* Test if the counter was not processed again */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 0.0); + + /* write new value */ + testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17); + + /* Test if the counter was processed once */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); + + //number of tests 24 } MAIN(longoutTest) { - testPlan(6+8+8+8+9+8+11); + testPlan(6+8+8+8+9+8+24); testdbPrepare(); testdbReadDatabase("recTestIoc.dbd", NULL, NULL); diff --git a/modules/database/test/std/rec/longoutTest.db b/modules/database/test/std/rec/longoutTest.db index 93428a070..b988792cd 100644 --- a/modules/database/test/std/rec/longoutTest.db +++ b/modules/database/test/std/rec/longoutTest.db @@ -1,17 +1,17 @@ -record(calc, "counter") { - field(INPA, "counter") +record(calc, "counter_a") { + field(INPA, "counter_a") field(CALC, "A+1") field(SCAN, "Passive") } -record(calc, "counter2") { - field(INPA, "counter2") +record(calc, "counter_b") { + field(INPA, "counter_b") field(CALC, "A+1") field(SCAN, "Passive") } record(longout, "longout_rec") { field(VAL, "0") - field(OUT, "counter.B PP") + field(OUT, "counter_a.B PP") field(PINI, "YES") } From 1d85bc7424576fb1f7eb6c890d6a42c8d54254f4 Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Wed, 10 Mar 2021 11:07:30 +0100 Subject: [PATCH 4/5] longout record: detect OUT link change using special function AFTER put, better documentation --- modules/database/src/std/rec/longoutRecord.c | 6 +++--- modules/database/src/std/rec/longoutRecord.dbd.pod | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index e60e01c79..6ceeff1a7 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -217,9 +217,9 @@ static long special(DBADDR *paddr, int after) return(0); } - /* Detect an output link re-direction (change)*/ + /* Detect an output link re-direction (change) */ if (dbGetFieldIndex(paddr) == longoutRecordOUT) { - if (!after) + if (after) prec->outpvt = OUT_LINK_CHANGED; return(0); } @@ -492,4 +492,4 @@ static long conditional_write(longoutRecord *prec) prec->pval = prec->val; prec->outpvt = OUT_LINK_UNCHANGED; /* reset status of OUT link */ return status; -} \ No newline at end of file +} diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index fe596c118..1a85cf533 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -95,8 +95,7 @@ written to. It's a menu field that has six choices: C -- write output every time record is processed. (DEFAULT) =item * -C -- write output every time VAL changes, i.e., every time the -result of the expression changes. +C -- write output every time VAL changes. =item * C -- when record is processed, write output if VAL is zero. @@ -117,8 +116,10 @@ VAL is non-zero and last value was zero. =head4 Changes in OUT field when OOPT = On Change -The OOCH field determines if a change in OUT field should cause a write operation -even when the value is the same and OOPT = On Change. By default, OOCH is set to YES. +If OOCH is C (its default value) and the OOPT field is C, +the record will write to the device support the first time the record gets +processed after its OUT link is modified, even when the output value has +not actually changed. =cut From 6c573b496a2d387583e6ef603530a504b68aaa0a Mon Sep 17 00:00:00 2001 From: Joao Paulo Martins Date: Wed, 10 Mar 2021 18:50:42 +0100 Subject: [PATCH 5/5] longout rec: fix behaviour when record is processed for the first time and OOPT is On Change --- modules/database/src/std/rec/longoutRecord.c | 17 ++++++++-------- .../src/std/rec/longoutRecord.dbd.pod | 4 ++-- modules/database/test/std/rec/longoutTest.c | 20 +++++++++++++++++-- modules/database/test/std/rec/longoutTest.db | 8 ++++++++ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c index 6ceeff1a7..9f29d15fa 100644 --- a/modules/database/src/std/rec/longoutRecord.c +++ b/modules/database/src/std/rec/longoutRecord.c @@ -81,8 +81,8 @@ rset longoutRSET={ }; epicsExportAddress(rset,longoutRSET); -#define OUT_LINK_UNCHANGED 0 -#define OUT_LINK_CHANGED 1 +#define DONT_EXEC_OUTPUT 0 +#define EXEC_OUTPUT 1 static void checkAlarms(longoutRecord *prec); static void monitor(longoutRecord *prec); @@ -124,7 +124,8 @@ static long init_record(struct dbCommon *pcommon, int pass) prec->alst = prec->val; prec->lalm = prec->val; prec->pval = prec->val; - prec->outpvt = OUT_LINK_UNCHANGED; + prec->outpvt = EXEC_OUTPUT; + return 0; } @@ -219,8 +220,8 @@ static long special(DBADDR *paddr, int after) /* Detect an output link re-direction (change) */ if (dbGetFieldIndex(paddr) == longoutRecordOUT) { - if (after) - prec->outpvt = OUT_LINK_CHANGED; + if ((after) && (prec->ooch == menuYesNoYES)) + prec->outpvt = EXEC_OUTPUT; return(0); } @@ -453,8 +454,8 @@ static long conditional_write(longoutRecord *prec) switch (prec->oopt) { case longoutOOPT_On_Change: - /* Forces a write op if a change in the OUT field is detected */ - if ((prec->ooch == menuYesNoYES) && (prec->outpvt == OUT_LINK_CHANGED)) { + /* Forces a write op if a change in the OUT field is detected OR is first process */ + if (prec->outpvt == EXEC_OUTPUT) { doDevSupWrite = 1; } else { /* Only write if value is different from the previous one */ @@ -490,6 +491,6 @@ static long conditional_write(longoutRecord *prec) status = pdset->write_longout(prec); prec->pval = prec->val; - prec->outpvt = OUT_LINK_UNCHANGED; /* reset status of OUT link */ + prec->outpvt = DONT_EXEC_OUTPUT; /* reset status */ return status; } diff --git a/modules/database/src/std/rec/longoutRecord.dbd.pod b/modules/database/src/std/rec/longoutRecord.dbd.pod index 1a85cf533..131ba8ff6 100644 --- a/modules/database/src/std/rec/longoutRecord.dbd.pod +++ b/modules/database/src/std/rec/longoutRecord.dbd.pod @@ -426,13 +426,13 @@ for more information on simulation mode and its fields. prompt("Previous Value") } field(OUTPVT,DBF_NOACCESS) { - prompt("Output Link Changed Private") + prompt("Output Write Control Private") special(SPC_NOMOD) interest(4) extra("epicsEnum16 outpvt") } field(OOCH,DBF_MENU) { - prompt("Output Execute On Change") + prompt("Output Exec. On Change (Opt)") promptgroup("50 - Output") interest(1) menu(menuYesNo) diff --git a/modules/database/test/std/rec/longoutTest.c b/modules/database/test/std/rec/longoutTest.c index c69a6b386..35f161341 100644 --- a/modules/database/test/std/rec/longoutTest.c +++ b/modules/database/test/std/rec/longoutTest.c @@ -222,12 +222,28 @@ static void test_changing_out_field(void){ /* Test if the counter was processed once */ testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); - //number of tests 24 + /* reset rec processing counters */ + testdbPutFieldOk("counter_a.VAL", DBF_DOUBLE, 0.0); + + /* test if record with OOPT == On Change will + write to output at its first process */ + testdbPutFieldOk("longout_rec2.VAL", DBF_LONG, 16); + + /* Test if the counter was processed once */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); + + /* write the same value */ + testdbPutFieldOk("longout_rec2.VAL", DBF_LONG, 16); + + /* Test if the counter was not processed again */ + testdbGetFieldEqual("counter_a", DBF_DOUBLE, 1.0); + + //number of tests 29 } MAIN(longoutTest) { - testPlan(6+8+8+8+9+8+24); + testPlan(6+8+8+8+9+8+29); testdbPrepare(); testdbReadDatabase("recTestIoc.dbd", NULL, NULL); diff --git a/modules/database/test/std/rec/longoutTest.db b/modules/database/test/std/rec/longoutTest.db index b988792cd..6b41b9883 100644 --- a/modules/database/test/std/rec/longoutTest.db +++ b/modules/database/test/std/rec/longoutTest.db @@ -15,3 +15,11 @@ record(longout, "longout_rec") { field(OUT, "counter_a.B PP") field(PINI, "YES") } + +record(longout, "longout_rec2") { + field(VAL, "16") + field(OUT, "counter_a.B PP") + field(PINI, "NO") + field(OOPT, "On Change") +} +