diff --git a/src/std/dev/Makefile b/src/std/dev/Makefile index 8e4e242f9..ec3713bd6 100644 --- a/src/std/dev/Makefile +++ b/src/std/dev/Makefile @@ -28,6 +28,8 @@ dbRecStd_SRCS += devBoDbState.c dbRecStd_SRCS += devCalcoutSoft.c dbRecStd_SRCS += devEventSoft.c dbRecStd_SRCS += devHistogramSoft.c +dbRecStd_SRCS += devI64inSoft.c +dbRecStd_SRCS += devI64outSoft.c dbRecStd_SRCS += devLiSoft.c dbRecStd_SRCS += devLoSoft.c dbRecStd_SRCS += devLsiSoft.c @@ -49,6 +51,7 @@ dbRecStd_SRCS += devGeneralTime.c dbRecStd_SRCS += devAiSoftCallback.c dbRecStd_SRCS += devBiSoftCallback.c +dbRecStd_SRCS += devI64inSoftCallback.c dbRecStd_SRCS += devLiSoftCallback.c dbRecStd_SRCS += devMbbiDirectSoftCallback.c dbRecStd_SRCS += devMbbiSoftCallback.c @@ -57,6 +60,7 @@ dbRecStd_SRCS += devSiSoftCallback.c dbRecStd_SRCS += devAoSoftCallback.c dbRecStd_SRCS += devBoSoftCallback.c dbRecStd_SRCS += devCalcoutSoftCallback.c +dbRecStd_SRCS += devI64outSoftCallback.c dbRecStd_SRCS += devLoSoftCallback.c dbRecStd_SRCS += devLsoSoftCallback.c dbRecStd_SRCS += devMbboSoftCallback.c diff --git a/src/std/dev/devI64inSoft.c b/src/std/dev/devI64inSoft.c new file mode 100644 index 000000000..f4e6bce83 --- /dev/null +++ b/src/std/dev/devI64inSoft.c @@ -0,0 +1,79 @@ +/*************************************************************************\ +* Copyright (c) 2016 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 file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* $Revision-Id$ + * + * Original Author: Janet Anderson + * Date: 09-23-91 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "devSup.h" +#include "int64inRecord.h" +#include "epicsExport.h" + +/* Create the dset for devI64inSoft */ +static long init_record(int64inRecord *prec); +static long read_int64in(int64inRecord *prec); + +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_int64in; +} devI64inSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + read_int64in +}; +epicsExportAddress(dset, devI64inSoft); + +static long init_record(int64inRecord *prec) +{ + /* INP must be CONSTANT, PV_LINK, DB_LINK or CA_LINK*/ + switch (prec->inp.type) { + case CONSTANT: + if (recGblInitConstantLink(&prec->inp, DBF_INT64, &prec->val)) + prec->udf = FALSE; + break; + case PV_LINK: + case DB_LINK: + case CA_LINK: + break; + default: + recGblRecordError(S_db_badField, (void *)prec, + "devI64inSoft (init_record) Illegal INP field"); + return S_db_badField; + } + return 0; +} + +static long read_int64in(int64inRecord *prec) +{ + long status; + + status = dbGetLink(&prec->inp, DBR_INT64, &prec->val, 0, 0); + if (!status && + prec->tsel.type == CONSTANT && + prec->tse == epicsTimeEventDeviceTime) + dbGetTimeStamp(&prec->inp, &prec->time); + return status; +} diff --git a/src/std/dev/devI64inSoftCallback.c b/src/std/dev/devI64inSoftCallback.c new file mode 100644 index 000000000..8c9e77896 --- /dev/null +++ b/src/std/dev/devI64inSoftCallback.c @@ -0,0 +1,222 @@ +/*************************************************************************\ +* Copyright (c) 2016 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 file LICENSE that is included with this distribution. +\*************************************************************************/ +/* devI64inSoftCallback.c */ +/* + * Authors: Marty Kraimer & Andrew Johnson + */ + +#include +#include + +#include "alarm.h" +#include "callback.h" +#include "cantProceed.h" +#include "dbCommon.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "dbChannel.h" +#include "dbNotify.h" +#include "epicsAssert.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "link.h" +#include "int64inRecord.h" +#include "epicsExport.h" + + +#define GET_OPTIONS (DBR_STATUS | DBR_TIME) + +typedef struct devPvt { + processNotify pn; + CALLBACK callback; + long options; + int status; + struct { + DBRstatus + DBRtime + epicsInt32 value; + } buffer; +} devPvt; + + +static void getCallback(processNotify *ppn, notifyGetType type) +{ + int64inRecord *prec = (int64inRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + long no_elements = 1; + + if (ppn->status == notifyCanceled) { + printf("devI64inSoftCallback::getCallback notifyCanceled\n"); + return; + } + + assert(type == getFieldType); + pdevPvt->status = dbChannelGetField(ppn->chan, DBR_INT64, + &pdevPvt->buffer, &pdevPvt->options, &no_elements, 0); +} + +static void doneCallback(processNotify *ppn) +{ + int64inRecord *prec = (int64inRecord *)ppn->usrPvt; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + callbackRequestProcessCallback(&pdevPvt->callback, prec->prio, prec); +} + +static long add_record(dbCommon *pcommon) +{ + int64inRecord *prec = (int64inRecord *)pcommon; + DBLINK *plink = &prec->inp; + dbChannel *chan; + devPvt *pdevPvt; + processNotify *ppn; + + if (plink->type == CONSTANT) return 0; + + if (plink->type != PV_LINK) { + long status = S_db_badField; + + recGblRecordError(status, (void *)prec, + "devI64inSoftCallback (add_record) Illegal INP field"); + return status; + } + + chan = dbChannelCreate(plink->value.pv_link.pvname); + if (!chan) { + long status = S_db_notFound; + + recGblRecordError(status, (void *)prec, + "devI64inSoftCallback (init_record) linked record not found"); + return status; + } + + pdevPvt = calloc(1, sizeof(*pdevPvt)); + if (!pdevPvt) { + long status = S_db_noMemory; + + recGblRecordError(status, (void *)prec, + "devI64inSoftCallback (add_record) out of memory, calloc() failed"); + return status; + } + ppn = &pdevPvt->pn; + + plink->type = PN_LINK; + plink->value.pv_link.pvlMask &= pvlOptMsMode; /* Severity flags only */ + + ppn->usrPvt = prec; + ppn->chan = chan; + ppn->getCallback = getCallback; + ppn->doneCallback = doneCallback; + ppn->requestType = processGetRequest; + + pdevPvt->options = GET_OPTIONS; + + prec->dpvt = pdevPvt; + return 0; +} + +static long del_record(dbCommon *pcommon) { + int64inRecord *prec = (int64inRecord *)pcommon; + DBLINK *plink = &prec->inp; + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (plink->type == CONSTANT) return 0; + assert(plink->type == PN_LINK); + + dbNotifyCancel(&pdevPvt->pn); + dbChannelDelete(pdevPvt->pn.chan); + free(pdevPvt); + + plink->type = PV_LINK; + return 0; +} + +static struct dsxt dsxtSoftCallback = { + add_record, del_record +}; + +static long init(int pass) +{ + if (pass == 0) devExtend(&dsxtSoftCallback); + return 0; +} + +static long init_record(int64inRecord *prec) +{ + /* INP must be CONSTANT or PN_LINK */ + switch (prec->inp.type) { + case CONSTANT: + if (recGblInitConstantLink(&prec->inp, DBR_INT64, &prec->val)) + prec->udf = FALSE; + break; + case PN_LINK: + /* Handled by add_record */ + break; + default: + recGblRecordError(S_db_badField, (void *)prec, + "devI64inSoftCallback (init_record) Illegal INP field"); + prec->pact = TRUE; + return S_db_badField; + } + return 0; +} + +static long read_int64in(int64inRecord *prec) +{ + devPvt *pdevPvt = (devPvt *)prec->dpvt; + + if (!prec->dpvt) + return 0; + + if (!prec->pact) { + dbProcessNotify(&pdevPvt->pn); + prec->pact = TRUE; + return 0; + } + + if (pdevPvt->status) { + recGblSetSevr(prec, READ_ALARM, INVALID_ALARM); + return pdevPvt->status; + } + + prec->val = pdevPvt->buffer.value; + prec->udf = FALSE; + + switch (prec->inp.value.pv_link.pvlMask & pvlOptMsMode) { + case pvlOptNMS: + break; + case pvlOptMSI: + if (pdevPvt->buffer.severity < INVALID_ALARM) + break; + /* else fall through */ + case pvlOptMS: + recGblSetSevr(prec, LINK_ALARM, pdevPvt->buffer.severity); + break; + case pvlOptMSS: + recGblSetSevr(prec, pdevPvt->buffer.status, + pdevPvt->buffer.severity); + break; + } + + if (prec->tsel.type == CONSTANT && + prec->tse == epicsTimeEventDeviceTime) + prec->time = pdevPvt->buffer.time; + return 0; +} + +/* Create the dset for devI64inSoftCallback */ +struct { + dset common; + DEVSUPFUN read_int64in; +} devI64inSoftCallback = { + {5, NULL, init, init_record, NULL}, + read_int64in +}; +epicsExportAddress(dset, devI64inSoftCallback); diff --git a/src/std/dev/devI64outSoft.c b/src/std/dev/devI64outSoft.c new file mode 100644 index 000000000..95f6e266c --- /dev/null +++ b/src/std/dev/devI64outSoft.c @@ -0,0 +1,58 @@ +/*************************************************************************\ +* Copyright (c) 2016 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 file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* $Revision-Id$ */ +/* + * Original Author: Janet Anderson + * Date: 09-23-91 +*/ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "int64outRecord.h" +#include "epicsExport.h" + +/* Create the dset for devI64outSoft */ +static long init_record(int64outRecord *prec); +static long write_int64out(int64outRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_int64out; +} devI64outSoft = { + 5, + NULL, + NULL, + init_record, + NULL, + write_int64out +}; +epicsExportAddress(dset, devI64outSoft); + +static long init_record(int64outRecord *prec) +{ + return 0; +} + +static long write_int64out(int64outRecord *prec) +{ + dbPutLink(&prec->out, DBR_INT64, &prec->val,1); + return 0; +} diff --git a/src/std/dev/devI64outSoftCallback.c b/src/std/dev/devI64outSoftCallback.c new file mode 100644 index 000000000..bfb283523 --- /dev/null +++ b/src/std/dev/devI64outSoftCallback.c @@ -0,0 +1,69 @@ +/*************************************************************************\ +* Copyright (c) 2016 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 file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* + * Original Author: Marty Kraimer + * Date: 04NOV2003 + */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbAccess.h" +#include "recGbl.h" +#include "recSup.h" +#include "devSup.h" +#include "int64outRecord.h" +#include "epicsExport.h" + +/* Create the dset for devI64outSoftCallback */ +static long write_int64out(int64outRecord *prec); +struct { + long number; + DEVSUPFUN report; + DEVSUPFUN init; + DEVSUPFUN init_record; + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_int64out; +} devI64outSoftCallback = { + 5, + NULL, + NULL, + NULL, + NULL, + write_int64out +}; +epicsExportAddress(dset, devI64outSoftCallback); + +static long write_int64out(int64outRecord *prec) +{ + struct link *plink = &prec->out; + long status; + + if (prec->pact) + return 0; + + if (plink->type != CA_LINK) { + status = dbPutLink(plink, DBR_INT64, &prec->val, 1); + return status; + } + + status = dbCaPutLinkCallback(plink, DBR_INT64, &prec->val, 1, + dbCaCallbackProcess, plink); + if (status) { + recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM); + return status; + } + + prec->pact = TRUE; + return 0; +} diff --git a/src/std/dev/devSoft.dbd b/src/std/dev/devSoft.dbd index 1314bf540..2d7a64be3 100644 --- a/src/std/dev/devSoft.dbd +++ b/src/std/dev/devSoft.dbd @@ -9,6 +9,8 @@ device(bo,CONSTANT,devBoSoft,"Soft Channel") device(calcout,CONSTANT,devCalcoutSoft,"Soft Channel") device(event,CONSTANT,devEventSoft,"Soft Channel") device(histogram,CONSTANT,devHistogramSoft,"Soft Channel") +device(int64in,CONSTANT,devI64inSoft,"Soft Channel") +device(int64out,CONSTANT,devI64outSoft,"Soft Channel") device(longin,CONSTANT,devLiSoft,"Soft Channel") device(longout,CONSTANT,devLoSoft,"Soft Channel") device(lsi,CONSTANT,devLsiSoft,"Soft Channel") @@ -37,6 +39,8 @@ device(ao,CONSTANT,devAoSoftCallback,"Async Soft Channel") device(bi,CONSTANT,devBiSoftCallback,"Async Soft Channel") device(bo,CONSTANT,devBoSoftCallback,"Async Soft Channel") device(calcout,CONSTANT,devCalcoutSoftCallback,"Async Soft Channel") +device(int64in,CONSTANT,devI64inSoftCallback,"Async Soft Channel") +device(int64out,CONSTANT,devI64outSoftCallback,"Async Soft Channel") device(longin,CONSTANT,devLiSoftCallback,"Async Soft Channel") device(longout,CONSTANT,devLoSoftCallback,"Async Soft Channel") device(lso,CONSTANT,devLsoSoftCallback,"Async Soft Channel") diff --git a/src/std/rec/Makefile b/src/std/rec/Makefile index 7515d769c..561058620 100644 --- a/src/std/rec/Makefile +++ b/src/std/rec/Makefile @@ -25,6 +25,8 @@ stdRecords += dfanoutRecord stdRecords += eventRecord stdRecords += fanoutRecord stdRecords += histogramRecord +stdRecords += int64inRecord +stdRecords += int64outRecord stdRecords += longinRecord stdRecords += longoutRecord stdRecords += lsiRecord diff --git a/src/std/rec/int64inRecord.c b/src/std/rec/int64inRecord.c new file mode 100644 index 000000000..700798c66 --- /dev/null +++ b/src/std/rec/int64inRecord.c @@ -0,0 +1,416 @@ +/*************************************************************************\ +* 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. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* $Revision-Id$ */ + +/* int64inRecord.c - Record Support Routines for int64in records */ +/* + * Original Author: Janet Anderson + * Date: 9/23/91 + */ + +#include +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "menuYesNo.h" + +#define GEN_SIZE_OFFSET +#include "int64inRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + + /* Hysterisis for alarm filtering: 1-1/e */ +#define THRESHOLD 0.6321 +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(int64inRecord *, int); +static long process(int64inRecord *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +#define get_precision NULL +#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 int64inRSET={ + 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,int64inRSET); + + +struct int64indset { /* int64in input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN read_int64in; /*returns: (-1,0)=>(failure,success)*/ +}; +static void checkAlarms(int64inRecord *prec, epicsTimeStamp *timeLast); +static void monitor(int64inRecord *prec); +static long readValue(int64inRecord *prec); + + +static long init_record(int64inRecord *prec, int pass) +{ + struct int64indset *pdset; + long status; + + if (pass==0) return(0); + + /* int64in.siml must be a CONSTANT or a PV_LINK or a DB_LINK */ + if (prec->siml.type == CONSTANT) { + recGblInitConstantLink(&prec->siml,DBF_USHORT,&prec->simm); + } + + /* int64in.siol must be a CONSTANT or a PV_LINK or a DB_LINK */ + if (prec->siol.type == CONSTANT) { + recGblInitConstantLink(&prec->siol,DBF_LONG,&prec->sval); + } + + if(!(pdset = (struct int64indset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"int64in: init_record"); + return(S_dev_noDSET); + } + /* must have read_int64in function defined */ + if( (pdset->number < 5) || (pdset->read_int64in == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"int64in: init_record"); + return(S_dev_missingSup); + } + if( pdset->init_record ) { + if((status=(*pdset->init_record)(prec))) return(status); + } + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return(0); +} + +static long process(int64inRecord *prec) +{ + struct int64indset *pdset = (struct int64indset *)(prec->dset); + long status; + unsigned char pact=prec->pact; + epicsTimeStamp timeLast; + + if( (pdset==NULL) || (pdset->read_int64in==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"read_int64in"); + return(S_dev_missingSup); + } + timeLast = prec->time; + + status=readValue(prec); /* read the new value */ + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStamp(prec); + if (status==0) prec->udf = FALSE; + + /* check for alarms */ + checkAlarms(prec, &timeLast); + /* check event list */ + monitor(prec); + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +#define indexof(field) int64inRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + int64inRecord *prec=(int64inRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_LONG) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + + +static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) +{ + int64inRecord *prec=(int64inRecord *)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): + case indexof(SVAL): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) +{ + int64inRecord *prec=(int64inRecord *)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): + case indexof(SVAL): + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} + +static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) +{ + int64inRecord *prec=(int64inRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)){ + pad->upper_alarm_limit = prec->hihi; + pad->upper_warning_limit = prec->high; + pad->lower_warning_limit = prec->low; + pad->lower_alarm_limit = prec->lolo; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(int64inRecord *prec, epicsTimeStamp *timeLast) +{ + enum { + range_Lolo = 1, + range_Low, + range_Normal, + range_High, + range_Hihi + } alarmRange; + static const epicsEnum16 range_stat[] = { + SOFT_ALARM, LOLO_ALARM, LOW_ALARM, + NO_ALARM, HIGH_ALARM, HIHI_ALARM + }; + + double aftc, afvl; + epicsInt64 val, hyst, lalm; + epicsInt64 alev; + epicsEnum16 asev; + + if (prec->udf) { + recGblSetSevr(prec, UDF_ALARM, prec->udfs); + prec->afvl = 0; + return; + } + + val = prec->val; + hyst = prec->hyst; + lalm = prec->lalm; + + /* check VAL against alarm limits */ + if ((asev = prec->hhsv) && + (val >= (alev = prec->hihi) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_Hihi; + else + if ((asev = prec->llsv) && + (val <= (alev = prec->lolo) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Lolo; + else + if ((asev = prec->hsv) && + (val >= (alev = prec->high) || + ((lalm == alev) && (val >= alev - hyst)))) + alarmRange = range_High; + else + if ((asev = prec->lsv) && + (val <= (alev = prec->low) || + ((lalm == alev) && (val <= alev + hyst)))) + alarmRange = range_Low; + else { + alev = val; + asev = NO_ALARM; + alarmRange = range_Normal; + } + + aftc = prec->aftc; + afvl = 0; + + if (aftc > 0) { + /* Apply level filtering */ + afvl = prec->afvl; + if (afvl == 0) { + afvl = (double)alarmRange; + } else { + double t = epicsTimeDiffInSeconds(&prec->time, timeLast); + double alpha = aftc / (t + aftc); + + /* The sign of afvl indicates whether the result should be + * rounded up or down. This gives the filter hysteresis. + * If afvl > 0 the floor() function rounds to a lower alarm + * level, otherwise to a higher. + */ + afvl = alpha * afvl + + ((afvl > 0) ? (1 - alpha) : (alpha - 1)) * alarmRange; + if (afvl - floor(afvl) > THRESHOLD) + afvl = -afvl; /* reverse rounding */ + + alarmRange = abs((int)floor(afvl)); + switch (alarmRange) { + case range_Hihi: + asev = prec->hhsv; + alev = prec->hihi; + break; + case range_High: + asev = prec->hsv; + alev = prec->high; + break; + case range_Normal: + asev = NO_ALARM; + break; + case range_Low: + asev = prec->lsv; + alev = prec->low; + break; + case range_Lolo: + asev = prec->llsv; + alev = prec->lolo; + break; + } + } + } + prec->afvl = afvl; + + if (asev) { + /* Report alarm condition, store LALM for future HYST calculations */ + if (recGblSetSevr(prec, range_stat[alarmRange], asev)) + prec->lalm = alev; + } else { + /* No alarm condition, reset LALM */ + prec->lalm = val; + } +} + +/* DELTA calculates the absolute difference between its arguments + * expressed as an unsigned 32-bit integer */ +#define DELTA(last, val) \ + ((epicsUInt32) ((last) > (val) ? (last) - (val) : (val) - (last))) + +static void monitor(int64inRecord *prec) +{ + unsigned short monitor_mask = recGblResetAlarms(prec); + + if (prec->mdel < 0 || + DELTA(prec->mlst, prec->val) > (epicsUInt32) prec->mdel) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + if (prec->adel < 0 || + DELTA(prec->alst, prec->val) > (epicsUInt32) prec->adel) { + /* post events for archive value change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); +} + +static long readValue(int64inRecord *prec) +{ + long status; + struct int64indset *pdset = (struct int64indset *) (prec->dset); + + if (prec->pact == TRUE){ + status=(*pdset->read_int64in)(prec); + return(status); + } + + status=dbGetLink(&(prec->siml),DBR_USHORT, &(prec->simm),0,0); + if (status) + return(status); + + if (prec->simm == menuYesNoNO){ + status=(*pdset->read_int64in)(prec); + return(status); + } + if (prec->simm == menuYesNoYES){ + status=dbGetLink(&(prec->siol),DBR_LONG, + &(prec->sval),0,0); + + if (status==0) { + prec->val=prec->sval; + prec->udf=FALSE; + } + } else { + status=-1; + recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); + return(status); + } + recGblSetSevr(prec,SIMM_ALARM,prec->sims); + + return(status); +} diff --git a/src/std/rec/int64inRecord.dbd b/src/std/rec/int64inRecord.dbd new file mode 100644 index 000000000..b603c709e --- /dev/null +++ b/src/std/rec/int64inRecord.dbd @@ -0,0 +1,161 @@ +#************************************************************************* +# Copyright (c) 2014 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 file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(int64in) { + include "dbCommon.dbd" + field(VAL,DBF_INT64) { + prompt("Current value") + promptgroup(GUI_INPUTS) + asl(ASL0) + pp(TRUE) + } + field(INP,DBF_INLINK) { + prompt("Input Specification") + promptgroup(GUI_INPUTS) + interest(1) + } + field(EGU,DBF_STRING) { + prompt("Units name") + promptgroup(GUI_DISPLAY) + interest(1) + size(16) + prop(YES) + } + field(HOPR,DBF_INT64) { + prompt("High Operating Range") + promptgroup(GUI_DISPLAY) + interest(1) + prop(YES) + } + field(LOPR,DBF_INT64) { + prompt("Low Operating Range") + promptgroup(GUI_DISPLAY) + interest(1) + prop(YES) + } + field(HIHI,DBF_INT64) { + prompt("Hihi Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_INT64) { + prompt("Lolo Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_INT64) { + prompt("High Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_INT64) { + prompt("Low Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_INT64) { + prompt("Alarm Deadband") + promptgroup(GUI_ALARMS) + interest(1) + } + field(AFTC, DBF_DOUBLE) { + prompt("Alarm Filter Time Constant") + promptgroup(GUI_ALARMS) + interest(1) + } + field(AFVL, DBF_DOUBLE) { + prompt("Alarm Filter Value") + special(SPC_NOMOD) + interest(3) + } + field(ADEL,DBF_INT64) { + prompt("Archive Deadband") + promptgroup(GUI_DISPLAY) + interest(1) + } + field(MDEL,DBF_INT64) { + prompt("Monitor Deadband") + promptgroup(GUI_DISPLAY) + interest(1) + } + field(LALM,DBF_INT64) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_INT64) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_INT64) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_INLINK) { + prompt("Sim Input Specifctn") + promptgroup(GUI_INPUTS) + interest(1) + } + field(SVAL,DBF_INT64) { + prompt("Simulation Value") + } + field(SIML,DBF_INLINK) { + prompt("Sim Mode Location") + promptgroup(GUI_INPUTS) + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Sim mode Alarm Svrty") + promptgroup(GUI_INPUTS) + interest(2) + menu(menuAlarmSevr) + } +} diff --git a/src/std/rec/int64outRecord.c b/src/std/rec/int64outRecord.c new file mode 100644 index 000000000..2c44c39c4 --- /dev/null +++ b/src/std/rec/int64outRecord.c @@ -0,0 +1,393 @@ +/*************************************************************************\ +* Copyright (c) 2016 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 file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* $Revision-Id$ */ +/* + * Original Author: Janet Anderson + * Date: 9/23/91 + */ +#include +#include +#include +#include +#include + +#include "dbDefs.h" +#include "epicsPrint.h" +#include "alarm.h" +#include "dbAccess.h" +#include "dbEvent.h" +#include "dbFldTypes.h" +#include "devSup.h" +#include "errMdef.h" +#include "recSup.h" +#include "recGbl.h" +#include "menuYesNo.h" +#include "menuIvoa.h" +#include "menuOmsl.h" + +#define GEN_SIZE_OFFSET +#include "int64outRecord.h" +#undef GEN_SIZE_OFFSET +#include "epicsExport.h" + +/* Create RSET - Record Support Entry Table*/ +#define report NULL +#define initialize NULL +static long init_record(int64outRecord *, int); +static long process(int64outRecord *); +#define special NULL +#define get_value NULL +#define cvt_dbaddr NULL +#define get_array_info NULL +#define put_array_info NULL +static long get_units(DBADDR *, char *); +#define get_precision NULL +#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 int64outRSET={ + 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,int64outRSET); + + +struct int64outdset { /* int64out input dset */ + long number; + DEVSUPFUN dev_report; + DEVSUPFUN init; + DEVSUPFUN init_record; /*returns: (-1,0)=>(failure,success)*/ + DEVSUPFUN get_ioint_info; + DEVSUPFUN write_int64out;/*(-1,0)=>(failure,success*/ +}; +static void checkAlarms(int64outRecord *prec); +static void monitor(int64outRecord *prec); +static long writeValue(int64outRecord *prec); +static void convert(int64outRecord *prec, epicsInt64 value); + + +static long init_record(int64outRecord *prec, int pass) +{ + struct int64outdset *pdset; + long status=0; + + if (pass==0) return(0); + if (prec->siml.type == CONSTANT) { + recGblInitConstantLink(&prec->siml,DBF_USHORT,&prec->simm); + } + if(!(pdset = (struct int64outdset *)(prec->dset))) { + recGblRecordError(S_dev_noDSET,(void *)prec,"int64out: init_record"); + return(S_dev_noDSET); + } + /* must have write_int64out functions defined */ + if( (pdset->number < 5) || (pdset->write_int64out == NULL) ) { + recGblRecordError(S_dev_missingSup,(void *)prec,"int64out: init_record"); + return(S_dev_missingSup); + } + if (prec->dol.type == CONSTANT) { + if(recGblInitConstantLink(&prec->dol,DBF_INT64,&prec->val)) + prec->udf=FALSE; + } + if( pdset->init_record ) { + if((status=(*pdset->init_record)(prec))) return(status); + } + prec->mlst = prec->val; + prec->alst = prec->val; + prec->lalm = prec->val; + return(0); +} + +static long process(int64outRecord *prec) +{ + struct int64outdset *pdset = (struct int64outdset *)(prec->dset); + long status=0; + epicsInt64 value; + unsigned char pact=prec->pact; + + if( (pdset==NULL) || (pdset->write_int64out==NULL) ) { + prec->pact=TRUE; + recGblRecordError(S_dev_missingSup,(void *)prec,"write_int64out"); + return(S_dev_missingSup); + } + if (!prec->pact) { + if((prec->dol.type != CONSTANT) + && (prec->omsl == menuOmslclosed_loop)) { + status = dbGetLink(&(prec->dol),DBR_INT64, + &value,0,0); + if (prec->dol.type!=CONSTANT && RTN_SUCCESS(status)) + prec->udf=FALSE; + } + else { + value = prec->val; + } + if (!status) convert(prec,value); + } + + /* check for alarms */ + checkAlarms(prec); + + if (prec->nsev < INVALID_ALARM ) + status=writeValue(prec); /* write the new value */ + else { + switch (prec->ivoa) { + case (menuIvoaContinue_normally) : + status=writeValue(prec); /* write the new value */ + break; + case (menuIvoaDon_t_drive_outputs) : + break; + case (menuIvoaSet_output_to_IVOV) : + if(prec->pact == FALSE){ + prec->val=prec->ivov; + } + status=writeValue(prec); /* write the new value */ + break; + default : + status=-1; + recGblRecordError(S_db_badField,(void *)prec, + "int64out:process Illegal IVOA field"); + } + } + + /* check if device support set pact */ + if ( !pact && prec->pact ) return(0); + prec->pact = TRUE; + + recGblGetTimeStamp(prec); + + /* check event list */ + monitor(prec); + + /* process the forward scan link record */ + recGblFwdLink(prec); + + prec->pact=FALSE; + return(status); +} + +#define indexof(field) int64outRecord##field + +static long get_units(DBADDR *paddr,char *units) +{ + int64outRecord *prec=(int64outRecord *)paddr->precord; + + if(paddr->pfldDes->field_type == DBF_INT64) { + strncpy(units,prec->egu,DB_UNITS_SIZE); + } + return(0); +} + +static long get_graphic_double(DBADDR *paddr,struct dbr_grDouble *pgd) +{ + int64outRecord *prec=(int64outRecord *)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): + pgd->upper_disp_limit = prec->hopr; + pgd->lower_disp_limit = prec->lopr; + break; + default: + recGblGetGraphicDouble(paddr,pgd); + } + return(0); +} + +static long get_control_double(DBADDR *paddr,struct dbr_ctrlDouble *pcd) +{ + int64outRecord *prec=(int64outRecord *)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): + /* do not change pre drvh/drvl behavior */ + if(prec->drvh > prec->drvl) { + pcd->upper_ctrl_limit = prec->drvh; + pcd->lower_ctrl_limit = prec->drvl; + } else { + pcd->upper_ctrl_limit = prec->hopr; + pcd->lower_ctrl_limit = prec->lopr; + } + break; + default: + recGblGetControlDouble(paddr,pcd); + } + return(0); +} + +static long get_alarm_double(DBADDR *paddr,struct dbr_alDouble *pad) +{ + int64outRecord *prec=(int64outRecord *)paddr->precord; + + if(dbGetFieldIndex(paddr) == indexof(VAL)) { + pad->upper_alarm_limit = prec->hihi; + pad->upper_warning_limit = prec->high; + pad->lower_warning_limit = prec->low; + pad->lower_alarm_limit = prec->lolo; + } else recGblGetAlarmDouble(paddr,pad); + return(0); +} + +static void checkAlarms(int64outRecord *prec) +{ + epicsInt64 val, hyst, lalm; + epicsInt64 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; +} + +/* DELTA calculates the absolute difference between its arguments + * expressed as an unsigned 64-bit integer */ +#define DELTA(last, val) \ + ((epicsUInt64) ((last) > (val) ? (last) - (val) : (val) - (last))) + +static void monitor(int64outRecord *prec) +{ + unsigned short monitor_mask = recGblResetAlarms(prec); + + if (prec->mdel < 0 || + DELTA(prec->mlst, prec->val) > (epicsUInt64) prec->mdel) { + /* post events for value change */ + monitor_mask |= DBE_VALUE; + /* update last value monitored */ + prec->mlst = prec->val; + } + + if (prec->adel < 0 || + DELTA(prec->alst, prec->val) > (epicsUInt64) prec->adel) { + /* post events for archive value change */ + monitor_mask |= DBE_LOG; + /* update last archive value monitored */ + prec->alst = prec->val; + } + + /* send out monitors connected to the value field */ + if (monitor_mask) + db_post_events(prec, &prec->val, monitor_mask); +} + +static long writeValue(int64outRecord *prec) +{ + long status; + struct int64outdset *pdset = (struct int64outdset *) (prec->dset); + + if (prec->pact == TRUE){ + status=(*pdset->write_int64out)(prec); + return(status); + } + + status=dbGetLink(&(prec->siml),DBR_USHORT,&(prec->simm),0,0); + if (!RTN_SUCCESS(status)) + return(status); + + if (prec->simm == menuYesNoNO){ + status=(*pdset->write_int64out)(prec); + return(status); + } + if (prec->simm == menuYesNoYES){ + status=dbPutLink(&prec->siol,DBR_INT64,&prec->val,1); + } else { + status=-1; + recGblSetSevr(prec,SOFT_ALARM,INVALID_ALARM); + return(status); + } + recGblSetSevr(prec,SIMM_ALARM,prec->sims); + + return(status); +} + +static void convert(int64outRecord *prec, epicsInt64 value) +{ + /* 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; +} diff --git a/src/std/rec/int64outRecord.dbd b/src/std/rec/int64outRecord.dbd new file mode 100644 index 000000000..fc088ed69 --- /dev/null +++ b/src/std/rec/int64outRecord.dbd @@ -0,0 +1,184 @@ +#************************************************************************* +# Copyright (c) 2016 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 file LICENSE that is included with this distribution. +#************************************************************************* +recordtype(int64out) { + include "dbCommon.dbd" + field(VAL,DBF_INT64) { + prompt("Desired Output") + promptgroup(GUI_OUTPUT) + asl(ASL0) + pp(TRUE) + } + field(OUT,DBF_OUTLINK) { + prompt("Output Specification") + promptgroup(GUI_OUTPUT) + interest(1) + } + field(DOL,DBF_INLINK) { + prompt("Desired Output Loc") + promptgroup(GUI_OUTPUT) + interest(1) + } + field(OMSL,DBF_MENU) { + prompt("Output Mode Select") + promptgroup(GUI_OUTPUT) + interest(1) + menu(menuOmsl) + } + field(EGU,DBF_STRING) { + prompt("Units name") + promptgroup(GUI_DISPLAY) + interest(1) + size(16) + prop(YES) + } + field(DRVH,DBF_INT64) { + prompt("Drive High Limit") + promptgroup(GUI_OUTPUT) + pp(TRUE) + interest(1) + prop(YES) + } + field(DRVL,DBF_INT64) { + prompt("Drive Low Limit") + promptgroup(GUI_OUTPUT) + pp(TRUE) + interest(1) + prop(YES) + } + field(HOPR,DBF_INT64) { + prompt("High Operating Range") + promptgroup(GUI_DISPLAY) + interest(1) + prop(YES) + } + field(LOPR,DBF_INT64) { + prompt("Low Operating Range") + promptgroup(GUI_DISPLAY) + interest(1) + prop(YES) + } + field(HIHI,DBF_INT64) { + prompt("Hihi Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(LOLO,DBF_INT64) { + prompt("Lolo Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(HIGH,DBF_INT64) { + prompt("High Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(LOW,DBF_INT64) { + prompt("Low Alarm Limit") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + prop(YES) + } + field(HHSV,DBF_MENU) { + prompt("Hihi Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LLSV,DBF_MENU) { + prompt("Lolo Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HSV,DBF_MENU) { + prompt("High Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(LSV,DBF_MENU) { + prompt("Low Severity") + promptgroup(GUI_ALARMS) + pp(TRUE) + interest(1) + menu(menuAlarmSevr) + } + field(HYST,DBF_INT64) { + prompt("Alarm Deadband") + promptgroup(GUI_ALARMS) + interest(1) + } + field(ADEL,DBF_INT64) { + prompt("Archive Deadband") + promptgroup(GUI_DISPLAY) + interest(1) + } + field(MDEL,DBF_INT64) { + prompt("Monitor Deadband") + promptgroup(GUI_DISPLAY) + interest(1) + } + field(LALM,DBF_INT64) { + prompt("Last Value Alarmed") + special(SPC_NOMOD) + interest(3) + } + field(ALST,DBF_INT64) { + prompt("Last Value Archived") + special(SPC_NOMOD) + interest(3) + } + field(MLST,DBF_INT64) { + prompt("Last Val Monitored") + special(SPC_NOMOD) + interest(3) + } + field(SIOL,DBF_OUTLINK) { + prompt("Sim Output Specifctn") + promptgroup(GUI_INPUTS) + interest(1) + } + field(SIML,DBF_INLINK) { + prompt("Sim Mode Location") + promptgroup(GUI_INPUTS) + interest(1) + } + field(SIMM,DBF_MENU) { + prompt("Simulation Mode") + interest(1) + menu(menuYesNo) + } + field(SIMS,DBF_MENU) { + prompt("Sim mode Alarm Svrty") + promptgroup(GUI_INPUTS) + interest(2) + menu(menuAlarmSevr) + } + field(IVOA,DBF_MENU) { + prompt("INVALID output action") + promptgroup(GUI_OUTPUT) + interest(2) + menu(menuIvoa) + } + field(IVOV,DBF_INT64) { + prompt("INVALID output value") + promptgroup(GUI_OUTPUT) + interest(2) + } +}