Files
epics-base/modules/database/src/std/rec/subRecord.c
T
Brendan Chandler 832abbd3b1 Return an error if subrecord processing fails due to bad INP links
If a sub record has an invalid INPx link, it was silently failing (and
not running the proc function).  This change plumbs in the error, so
the put fails and the user knows something went wrong.
2023-03-10 10:56:58 +00:00

436 lines
11 KiB
C

/*************************************************************************\
* Copyright (c) 2008 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.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* Record Support Routines for Subroutine records */
/*
* Original Author: Bob Dalesio
* Date: 01-25-90
*/
#include <stddef.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "dbDefs.h"
#include "epicsPrint.h"
#include "epicsMath.h"
#include "registryFunction.h"
#include "alarm.h"
#include "cantProceed.h"
#include "dbAccess.h"
#include "epicsPrint.h"
#include "dbEvent.h"
#include "dbFldTypes.h"
#include "errMdef.h"
#include "recSup.h"
#include "recGbl.h"
#include "special.h"
#define GEN_SIZE_OFFSET
#include "subRecord.h"
#undef GEN_SIZE_OFFSET
#include "epicsExport.h"
/* Create RSET - Record Support Entry Table*/
#define report NULL
#define initialize NULL
static long init_record(struct dbCommon *, int);
static long process(struct dbCommon *);
static long special(DBADDR *, int);
#define get_value NULL
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
static long get_units(DBADDR *, char *);
static long get_precision(const DBADDR *, long *);
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
static long get_graphic_double(DBADDR *, struct dbr_grDouble *);
static long get_control_double(DBADDR *, struct dbr_ctrlDouble *);
static long get_alarm_double(DBADDR *, struct dbr_alDouble *);
rset subRSET={
RSETNUMBER,
report,
initialize,
init_record,
process,
special,
get_value,
cvt_dbaddr,
get_array_info,
put_array_info,
get_units,
get_precision,
get_enum_str,
get_enum_strs,
put_enum_str,
get_graphic_double,
get_control_double,
get_alarm_double
};
epicsExportAddress(rset, subRSET);
static void checkAlarms(subRecord *);
static long do_sub(subRecord *);
static long fetch_values(subRecord *);
static void monitor(subRecord *);
#define INP_ARG_MAX 12
static long init_record(struct dbCommon *pcommon, int pass)
{
struct subRecord *prec = (struct subRecord *)pcommon;
SUBFUNCPTR psubroutine;
struct link *plink;
int i;
double *pvalue;
if (pass==0) return(0);
plink = &prec->inpa;
pvalue = &prec->a;
for (i = 0; i < INP_ARG_MAX; i++, plink++, pvalue++) {
recGblInitConstantLink(plink, DBF_DOUBLE, pvalue);
}
if (prec->inam[0]) {
/* convert the initialization subroutine name */
psubroutine = (SUBFUNCPTR)registryFunctionFind(prec->inam);
if (psubroutine == 0) {
recGblRecordError(S_db_BadSub, (void *)prec, "Init subroutine (INAM)");
return S_db_BadSub;
}
/* invoke the initialization subroutine */
(*psubroutine)(prec);
}
if (prec->snam[0] == 0) {
epicsPrintf("%s.SNAM is empty\n", prec->name);
prec->pact = TRUE;
return 0;
}
prec->sadr = (SUBFUNCPTR)registryFunctionFind(prec->snam);
if (prec->sadr == NULL) {
recGblRecordError(S_db_BadSub, (void *)prec, "Proc subroutine (SNAM)");
return S_db_BadSub;
}
prec->mlst = prec->val;
prec->alst = prec->val;
prec->lalm = prec->val;
return 0;
}
static long process(struct dbCommon *pcommon)
{
struct subRecord *prec = (struct subRecord *)pcommon;
long status = 0;
int pact = prec->pact;
if (!pact) {
prec->pact = TRUE;
status = fetch_values(prec);
prec->pact = FALSE;
}
if (status == 0) status = do_sub(prec);
/* Is subroutine asynchronous? */
if (!pact && prec->pact) return 0;
prec->pact = TRUE;
/* Asynchronous function (documented API!) */
if (status == 1) return 0;
recGblGetTimeStamp(prec);
/* check for alarms */
checkAlarms(prec);
/* publish changes */
monitor(prec);
recGblFwdLink(prec);
prec->pact = FALSE;
return status;
}
static long special(DBADDR *paddr, int after)
{
subRecord *prec = (subRecord *)paddr->precord;
if (!after) {
if (prec->snam[0] == 0 && prec->pact) {
prec->pact = FALSE;
prec->rpro = FALSE;
}
return 0;
}
if (prec->snam[0] == 0) {
epicsPrintf("%s.SNAM is empty\n", prec->name);
prec->pact = TRUE;
return 0;
}
prec->sadr = (SUBFUNCPTR)registryFunctionFind(prec->snam);
if (prec->sadr) return 0;
recGblRecordError(S_db_BadSub, (void *)prec,
"subRecord(special) registryFunctionFind failed");
return S_db_BadSub;
}
#define indexof(field) subRecord##field
static long get_linkNumber(int fieldIndex) {
if (fieldIndex >= indexof(A) && fieldIndex <= indexof(L))
return fieldIndex - indexof(A);
if (fieldIndex >= indexof(LA) && fieldIndex <= indexof(LL))
return fieldIndex - indexof(LA);
return -1;
}
static long get_units(DBADDR *paddr, char *units)
{
subRecord *prec = (subRecord *)paddr->precord;
int linkNumber;
if(paddr->pfldDes->field_type == DBF_DOUBLE) {
linkNumber = get_linkNumber(dbGetFieldIndex(paddr));
if (linkNumber >= 0)
dbGetUnits(&prec->inpa + linkNumber, units, DB_UNITS_SIZE);
else
strncpy(units,prec->egu,DB_UNITS_SIZE);
}
return 0;
}
static long get_precision(const DBADDR *paddr, long *pprecision)
{
subRecord *prec = (subRecord *)paddr->precord;
int fieldIndex = dbGetFieldIndex(paddr);
int linkNumber;
*pprecision = prec->prec;
if (fieldIndex == indexof(VAL))
return 0;
linkNumber = get_linkNumber(fieldIndex);
if (linkNumber >= 0) {
short precision;
if (dbGetPrecision(&prec->inpa + linkNumber, &precision) == 0)
*pprecision = precision;
} else
recGblGetPrec(paddr, pprecision);
return 0;
}
static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd)
{
subRecord *prec = (subRecord *)paddr->precord;
int fieldIndex = dbGetFieldIndex(paddr);
int linkNumber;
switch (fieldIndex) {
case indexof(VAL):
case indexof(HIHI):
case indexof(HIGH):
case indexof(LOW):
case indexof(LOLO):
case indexof(LALM):
case indexof(ALST):
case indexof(MLST):
pgd->lower_disp_limit = prec->lopr;
pgd->upper_disp_limit = prec->hopr;
break;
default:
linkNumber = get_linkNumber(fieldIndex);
if (linkNumber >= 0) {
dbGetGraphicLimits(&prec->inpa + linkNumber,
&pgd->lower_disp_limit,
&pgd->upper_disp_limit);
} else
recGblGetGraphicDouble(paddr,pgd);
}
return 0;
}
static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd)
{
subRecord *prec = (subRecord *)paddr->precord;
switch (dbGetFieldIndex(paddr)) {
case indexof(VAL):
case indexof(HIHI):
case indexof(HIGH):
case indexof(LOW):
case indexof(LOLO):
case indexof(LALM):
case indexof(ALST):
case indexof(MLST):
pcd->lower_ctrl_limit = prec->lopr;
pcd->upper_ctrl_limit = prec->hopr;
break;
default:
recGblGetControlDouble(paddr,pcd);
}
return 0;
}
static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad)
{
subRecord *prec = (subRecord *)paddr->precord;
int fieldIndex = dbGetFieldIndex(paddr);
int linkNumber;
if (fieldIndex == subRecordVAL) {
pad->upper_alarm_limit = prec->hhsv ? prec->hihi : epicsNAN;
pad->upper_warning_limit = prec->hsv ? prec->high : epicsNAN;
pad->lower_warning_limit = prec->lsv ? prec->low : epicsNAN;
pad->lower_alarm_limit = prec->llsv ? prec->lolo : epicsNAN;
} else {
linkNumber = get_linkNumber(fieldIndex);
if (linkNumber >= 0) {
dbGetAlarmLimits(&prec->inpa + linkNumber,
&pad->lower_alarm_limit,
&pad->lower_warning_limit,
&pad->upper_warning_limit,
&pad->upper_alarm_limit);
} else
recGblGetAlarmDouble(paddr, pad);
}
return 0;
}
static void checkAlarms(subRecord *prec)
{
double val, hyst, lalm;
double alev;
epicsEnum16 asev;
if (prec->udf) {
recGblSetSevr(prec, UDF_ALARM, prec->udfs);
return;
}
val = prec->val;
hyst = prec->hyst;
lalm = prec->lalm;
/* alarm condition hihi */
asev = prec->hhsv;
alev = prec->hihi;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIHI_ALARM, asev))
prec->lalm = alev;
return;
}
/* alarm condition lolo */
asev = prec->llsv;
alev = prec->lolo;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOLO_ALARM, asev))
prec->lalm = alev;
return;
}
/* alarm condition high */
asev = prec->hsv;
alev = prec->high;
if (asev && (val >= alev || ((lalm == alev) && (val >= alev - hyst)))) {
if (recGblSetSevr(prec, HIGH_ALARM, asev))
prec->lalm = alev;
return;
}
/* alarm condition low */
asev = prec->lsv;
alev = prec->low;
if (asev && (val <= alev || ((lalm == alev) && (val <= alev + hyst)))) {
if (recGblSetSevr(prec, LOW_ALARM, asev))
prec->lalm = alev;
return;
}
/* we get here only if val is out of alarm by at least hyst */
prec->lalm = val;
return;
}
static void monitor(subRecord *prec)
{
unsigned monitor_mask;
double *pnew;
double *pold;
int i;
/* get alarm mask */
monitor_mask = recGblResetAlarms(prec);
/* 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, pold = &prec->la;
i < INP_ARG_MAX; i++, pnew++, pold++) {
if (*pnew != *pold) {
db_post_events(prec, pnew, monitor_mask | DBE_VALUE | DBE_LOG);
*pold = *pnew;
}
}
return;
}
static long fetch_values(subRecord *prec)
{
struct link *plink = &prec->inpa;
double *pvalue = &prec->a;
int i;
for (i = 0; i < INP_ARG_MAX; i++, plink++, pvalue++) {
if (dbGetLink(plink, DBR_DOUBLE, pvalue, 0, 0))
return -1;
}
return 0;
}
static long do_sub(subRecord *prec)
{
SUBFUNCPTR psubroutine = prec->sadr;
long status;
if (psubroutine == NULL) {
recGblSetSevr(prec, BAD_SUB_ALARM, INVALID_ALARM);
return 0;
}
status = (*psubroutine)(prec);
if (status < 0) {
recGblSetSevr(prec, SOFT_ALARM, prec->brsv);
} else {
prec->udf = isnan(prec->val);
}
return status;
}