Merge PR #63, longout.OOPT

This commit is contained in:
Andrew Johnson
2022-12-29 16:31:42 -06:00
6 changed files with 451 additions and 4 deletions

View File

@ -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
<!-- Insert new items immediately below here ... -->
### 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

View File

@ -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;
}

View File

@ -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<Address
Specification|https://docs.epics-controls.org/en/latest/guides/EPICS_Process_Database_Concepts.html#address-specification>
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<Every Time> -- write output every time record is processed. (DEFAULT)
=item *
C<On Change> -- write output every time VAL changes.
=item *
C<When Zero> -- when record is processed, write output if VAL is zero.
=item *
C<When Non-zero> -- when record is processed, write output if VAL is
non-zero.
=item *
C<Transition To Zero> -- when record is processed, write output only if VAL
is zero and the last value was non-zero.
=item *
C<Transition To Non-zero> -- 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<YES> (its default value) and the OOPT field is C<On Change>,
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

View File

@ -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

View File

@ -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();
}

View File

@ -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")
}