diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md
index 870be17a4..9dd64cbf7 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.7
+### 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.
### Tab completion for IOC shell
diff --git a/modules/database/src/std/rec/longoutRecord.c b/modules/database/src/std/rec/longoutRecord.c
index faab03083..2e608cdfb 100644
--- a/modules/database/src/std/rec/longoutRecord.c
+++ b/modules/database/src/std/rec/longoutRecord.c
@@ -81,10 +81,14 @@ rset longoutRSET={
};
epicsExportAddress(rset,longoutRSET);
+#define DONT_EXEC_OUTPUT 0
+#define EXEC_OUTPUT 1
+
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 +123,9 @@ static long init_record(struct dbCommon *pcommon, int pass)
prec->mlst = prec->val;
prec->alst = prec->val;
prec->lalm = prec->val;
+ prec->pval = prec->val;
+ prec->outpvt = EXEC_OUTPUT;
+
return 0;
}
@@ -210,6 +217,14 @@ static long special(DBADDR *paddr, int after)
recGblCheckSimm((dbCommon *)prec, &prec->sscn, prec->oldsimm, prec->simm);
return(0);
}
+
+ /* Detect an output link re-direction (change) */
+ if (dbGetFieldIndex(paddr) == longoutRecordOUT) {
+ if ((after) && (prec->ooch == menuYesNoYES))
+ prec->outpvt = EXEC_OUTPUT;
+ return(0);
+ }
+
default:
recGblDbaddrError(S_db_badChoice, paddr, "longout: special");
return(S_db_badChoice);
@@ -391,7 +406,7 @@ static long writeValue(longoutRecord *prec)
switch (prec->simm) {
case menuYesNoNO:
- status = pdset->write_longout(prec);
+ status = conditional_write(prec);
break;
case menuYesNoYES: {
@@ -421,10 +436,61 @@ 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;
}
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:
+ /* 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 */
+ doDevSupWrite = (prec->val != prec->pval);
+ }
+ break;
+
+ 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->pval != 0));
+ break;
+
+ case longoutOOPT_Transition_To_Non_zero:
+ doDevSupWrite = ((prec->val != 0)&&(prec->pval == 0));
+ break;
+
+ default:
+ break;
+ }
+
+ if (doDevSupWrite)
+ status = pdset->write_longout(prec);
+
+ prec->pval = prec->val;
+ 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 f3738f620..4ba2c621b 100644
--- a/modules/database/src/std/rec/longoutRecord.dbd.pod
+++ b/modules/database/src/std/rec/longoutRecord.dbd.pod
@@ -20,6 +20,15 @@ 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")
+}
+
recordtype(longout) {
=head2 Parameter Fields
@@ -72,7 +81,46 @@ 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.
+
+=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
+
+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
@@ -94,6 +142,7 @@ for information on the format of hardware addresses and database links.
}
field(OUT,DBF_OUTLINK) {
prompt("Output Specification")
+ special(SPC_MOD)
promptgroup("50 - Output")
interest(1)
}
@@ -365,7 +414,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")
@@ -378,6 +427,29 @@ for more information on simulation mode and its fields.
promptgroup("50 - Output")
interest(2)
}
+ field(PVAL,DBF_LONG) {
+ prompt("Previous Value")
+ }
+ field(OUTPVT,DBF_NOACCESS) {
+ prompt("Output Write Control Private")
+ special(SPC_NOMOD)
+ interest(4)
+ extra("epicsEnum16 outpvt")
+ }
+ field(OOCH,DBF_MENU) {
+ prompt("Output Exec. On Change (Opt)")
+ promptgroup("50 - Output")
+ interest(1)
+ menu(menuYesNo)
+ initial("1")
+ }
+ field(OOPT,DBF_MENU) {
+ prompt("Output Execute Opt")
+ promptgroup("50 - Output")
+ interest(1)
+ menu(longoutOOPT)
+ initial("0")
+ }
=begin html
diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile
index dc87d23f2..effbbd1cc 100644
--- a/modules/database/test/std/rec/Makefile
+++ b/modules/database/test/std/rec/Makefile
@@ -86,6 +86,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..35f161341
--- /dev/null
+++ b/modules/database/test/std/rec/longoutTest.c
@@ -0,0 +1,270 @@
+/*************************************************************************\
+* 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 "menuYesNo.h"
+
+#include "longoutRecord.h"
+
+void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
+
+static void test_oopt_everytime(void){
+ /* 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);
+
+ /* write two times with different values*/
+ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 17);
+ testdbPutFieldOk("longout_rec.VAL", DBF_LONG, 18);
+
+ /* Test if the counter_a was processed 4 times */
+ testdbGetFieldEqual("counter_a", 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_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_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_a was processed 1 + 2 times */
+ testdbGetFieldEqual("counter_a", 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_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_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_a was still processed 2 times */
+ testdbGetFieldEqual("counter_a", 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_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_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_a was still processed 2 times */
+ testdbGetFieldEqual("counter_a", 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_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_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_a was processed once more */
+ testdbGetFieldEqual("counter_a", 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_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_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_a was processed */
+ testdbGetFieldEqual("counter_a", 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 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_a", DBF_DOUBLE, 0.0);
+
+ /* change the OUT link to another counter */
+ 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("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("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);
+
+ /* 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+29);
+
+ 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..6b41b9883
--- /dev/null
+++ b/modules/database/test/std/rec/longoutTest.db
@@ -0,0 +1,25 @@
+record(calc, "counter_a") {
+ field(INPA, "counter_a")
+ field(CALC, "A+1")
+ field(SCAN, "Passive")
+}
+
+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_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")
+}
+