/*************************************************************************\ * 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. \*************************************************************************/ /* calcout.c - Record Support Routines for calc with output records */ /* * Author : Ned Arnold * Based on recCalc.c by Julie Sander and Bob Dalesio * Date: 7-27-87 */ #include #include #include #include #include #include #include "alarm.h" #include "dbDefs.h" #include "dbAccess.h" #include "dbEvent.h" #include "dbScan.h" #include "cantProceed.h" #include "epicsMath.h" #include "errMdef.h" #include "errlog.h" #include "recSup.h" #include "devSup.h" #include "recGbl.h" #include "special.h" #include "callback.h" #include "taskwd.h" #define GEN_SIZE_OFFSET #include "calcoutRecord.h" #undef GEN_SIZE_OFFSET #include "menuIvoa.h" #include "epicsExport.h" /* Create RSET - Record Support Entry Table*/ #define report NULL #define initialize NULL static long init_record(calcoutRecord *, int); static long process(calcoutRecord *); 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(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_ctrl_double(DBADDR *, struct dbr_ctrlDouble *); static long get_alarm_double(DBADDR *, struct dbr_alDouble *); rset calcoutRSET = { 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_ctrl_double, get_alarm_double }; epicsExportAddress(rset, calcoutRSET); typedef struct calcoutDSET { long number; DEVSUPFUN dev_report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN write; }calcoutDSET; /* To provide feedback to the user as to the connection status of the * links (.INxV and .OUTV), the following algorithm has been implemented ... * * A new PV_LINK [either in init() or special()] is searched for using * dbNameToAddr. If local, it is so indicated. If not, a checkLinkCb * callback is scheduled to check the connectivity later using * dbCaIsLinkConnected(). Anytime there are unconnected CA_LINKs, another * callback is scheduled. Once all connections are established, the CA_LINKs * are checked whenever the record processes. * */ #define NO_CA_LINKS 0 #define CA_LINKS_ALL_OK 1 #define CA_LINKS_NOT_OK 2 typedef struct rpvtStruct { CALLBACK doOutCb; CALLBACK checkLinkCb; short cbScheduled; short caLinkStat; /* NO_CA_LINKS, CA_LINKS_ALL_OK, CA_LINKS_NOT_OK */ } rpvtStruct; static void checkAlarms(calcoutRecord *prec); static void monitor(calcoutRecord *prec); static int fetch_values(calcoutRecord *prec); static void execOutput(calcoutRecord *prec); static void checkLinks(calcoutRecord *prec); static void checkLinksCallback(CALLBACK *arg); static long writeValue(calcoutRecord *prec); int calcoutRecDebug; static long init_record(calcoutRecord *prec, int pass) { DBLINK *plink; int i; double *pvalue; epicsEnum16 *plinkValid; short error_number; calcoutDSET *pcalcoutDSET; DBADDR dbaddr; DBADDR *pAddr = &dbaddr; rpvtStruct *prpvt; if (pass == 0) { prec->rpvt = (rpvtStruct *) callocMustSucceed(1, sizeof(rpvtStruct), "calcoutRecord"); return 0; } if (!(pcalcoutDSET = (calcoutDSET *)prec->dset)) { recGblRecordError(S_dev_noDSET, (void *)prec, "calcout:init_record"); return S_dev_noDSET; } /* must have write defined */ if ((pcalcoutDSET->number < 5) || (pcalcoutDSET->write ==NULL)) { recGblRecordError(S_dev_missingSup, (void *)prec, "calcout:init_record"); return S_dev_missingSup; } prpvt = prec->rpvt; plink = &prec->inpa; pvalue = &prec->a; plinkValid = &prec->inav; for (i = 0; i <= CALCPERFORM_NARGS; i++, plink++, pvalue++, plinkValid++) { if (plink->type == CONSTANT) { /* Don't InitConstantLink the .OUT link */ if (i < CALCPERFORM_NARGS) { recGblInitConstantLink(plink, DBF_DOUBLE, pvalue); } *plinkValid = calcoutINAV_CON; } else if (!dbNameToAddr(plink->value.pv_link.pvname, pAddr)) { /* PV resides on this ioc */ *plinkValid = calcoutINAV_LOC; } else { /* pv is not on this ioc. Callback later for connection stat */ *plinkValid = calcoutINAV_EXT_NC; prpvt->caLinkStat = CA_LINKS_NOT_OK; } } prec->clcv = postfix(prec->calc, prec->rpcl, &error_number); if (prec->clcv){ recGblRecordError(S_db_badField, (void *)prec, "calcout: init_record: Illegal CALC field"); errlogPrintf("%s.CALC: %s in expression \"%s\"\n", prec->name, calcErrorStr(error_number), prec->calc); } prec->oclv = postfix(prec->ocal, prec->orpc, &error_number); if (prec->dopt == calcoutDOPT_Use_OVAL && prec->oclv){ recGblRecordError(S_db_badField, (void *)prec, "calcout: init_record: Illegal OCAL field"); errlogPrintf("%s.OCAL: %s in expression \"%s\"\n", prec->name, calcErrorStr(error_number), prec->ocal); } prpvt = prec->rpvt; callbackSetCallback(checkLinksCallback, &prpvt->checkLinkCb); callbackSetPriority(0, &prpvt->checkLinkCb); callbackSetUser(prec, &prpvt->checkLinkCb); prpvt->cbScheduled = 0; if (pcalcoutDSET->init_record) pcalcoutDSET->init_record(prec); prec->pval = prec->val; prec->mlst = prec->val; prec->alst = prec->val; prec->lalm = prec->val; prec->povl = prec->oval; return 0; } static long process(calcoutRecord *prec) { rpvtStruct *prpvt = prec->rpvt; int doOutput = 0; if (!prec->pact) { prec->pact = TRUE; /* if some links are CA, check connections */ if (prpvt->caLinkStat != NO_CA_LINKS) { checkLinks(prec); } if (fetch_values(prec) == 0) { if (calcPerform(&prec->a, &prec->val, prec->rpcl)) { recGblSetSevr(prec, CALC_ALARM, INVALID_ALARM); } else { prec->udf = isnan(prec->val); } } checkAlarms(prec); /* check for output link execution */ switch (prec->oopt) { case calcoutOOPT_Every_Time: doOutput = 1; break; case calcoutOOPT_On_Change: if (fabs(prec->pval - prec->val) > prec->mdel) doOutput = 1; break; case calcoutOOPT_Transition_To_Zero: if ((prec->pval != 0.0) && (prec->val == 0.0)) doOutput = 1; break; case calcoutOOPT_Transition_To_Non_zero: if ((prec->pval == 0.0) && (prec->val != 0.0)) doOutput = 1; break; case calcoutOOPT_When_Zero: if (prec->val == 0.0) doOutput = 1; break; case calcoutOOPT_When_Non_zero: if (prec->val != 0.0) doOutput = 1; break; default: break; } prec->pval = prec->val; if (doOutput) { if (prec->odly > 0.0) { prec->dlya = 1; db_post_events(prec, &prec->dlya, DBE_VALUE); callbackRequestProcessCallbackDelayed(&prpvt->doOutCb, prec->prio, prec, (double)prec->odly); return 0; } else { prec->pact = FALSE; execOutput(prec); if (prec->pact) return 0; prec->pact = TRUE; } } } else { /* pact == TRUE */ if (prec->dlya) { prec->dlya = 0; db_post_events(prec, &prec->dlya, DBE_VALUE); /* Make pact FALSE for asynchronous device support*/ prec->pact = FALSE; execOutput(prec); if (prec->pact) return 0; prec->pact = TRUE; } else {/*Device Support is asynchronous*/ writeValue(prec); } } recGblGetTimeStamp(prec); monitor(prec); recGblFwdLink(prec); prec->pact = FALSE; return 0; } static long special(DBADDR *paddr, int after) { calcoutRecord *prec = (calcoutRecord *)paddr->precord; rpvtStruct *prpvt = prec->rpvt; DBADDR dbaddr; DBADDR *pAddr = &dbaddr; short error_number; int fieldIndex = dbGetFieldIndex(paddr); int lnkIndex; DBLINK *plink; double *pvalue; epicsEnum16 *plinkValid; if (!after) return 0; switch(fieldIndex) { case(calcoutRecordCALC): prec->clcv = postfix(prec->calc, prec->rpcl, &error_number); if (prec->clcv){ recGblRecordError(S_db_badField, (void *)prec, "calcout: special(): Illegal CALC field"); errlogPrintf("%s.CALC: %s in expression \"%s\"\n", prec->name, calcErrorStr(error_number), prec->calc); } db_post_events(prec, &prec->clcv, DBE_VALUE); return 0; case(calcoutRecordOCAL): prec->oclv = postfix(prec->ocal, prec->orpc, &error_number); if (prec->dopt == calcoutDOPT_Use_OVAL && prec->oclv){ recGblRecordError(S_db_badField, (void *)prec, "calcout: special(): Illegal OCAL field"); errlogPrintf("%s.OCAL: %s in expression \"%s\"\n", prec->name, calcErrorStr(error_number), prec->ocal); } db_post_events(prec, &prec->oclv, DBE_VALUE); return 0; case(calcoutRecordINPA): case(calcoutRecordINPB): case(calcoutRecordINPC): case(calcoutRecordINPD): case(calcoutRecordINPE): case(calcoutRecordINPF): case(calcoutRecordINPG): case(calcoutRecordINPH): case(calcoutRecordINPI): case(calcoutRecordINPJ): case(calcoutRecordINPK): case(calcoutRecordINPL): case(calcoutRecordOUT): lnkIndex = fieldIndex - calcoutRecordINPA; plink = &prec->inpa + lnkIndex; pvalue = &prec->a + lnkIndex; plinkValid = &prec->inav + lnkIndex; if (plink->type == CONSTANT) { if (fieldIndex != calcoutRecordOUT) { recGblInitConstantLink(plink, DBF_DOUBLE, pvalue); db_post_events(prec, pvalue, DBE_VALUE); } *plinkValid = calcoutINAV_CON; } else if (!dbNameToAddr(plink->value.pv_link.pvname, pAddr)) { /* if the PV resides on this ioc */ *plinkValid = calcoutINAV_LOC; } else { /* pv is not on this ioc. Callback later for connection stat */ *plinkValid = calcoutINAV_EXT_NC; /* DO_CALLBACK, if not already scheduled */ if (!prpvt->cbScheduled) { callbackRequestDelayed(&prpvt->checkLinkCb, .5); prpvt->cbScheduled = 1; prpvt->caLinkStat = CA_LINKS_NOT_OK; } } db_post_events(prec, plinkValid, DBE_VALUE); return 0; default: recGblDbaddrError(S_db_badChoice, paddr, "calc: special"); return(S_db_badChoice); } } static long get_units(DBADDR *paddr, char *units) { calcoutRecord *prec = (calcoutRecord *)paddr->precord; strncpy(units, prec->egu, DB_UNITS_SIZE); return 0; } static long get_precision(DBADDR *paddr, long *pprecision) { calcoutRecord *prec = (calcoutRecord *)paddr->precord; int fieldIndex = dbGetFieldIndex(paddr); *pprecision = prec->prec; if (fieldIndex != calcoutRecordVAL) recGblGetPrec(paddr, pprecision); return 0; } static long get_graphic_double(DBADDR *paddr, struct dbr_grDouble *pgd) { calcoutRecord *prec = (calcoutRecord *)paddr->precord; if (paddr->pfield == (void *)&prec->val || paddr->pfield == (void *)&prec->hihi || paddr->pfield == (void *)&prec->high || paddr->pfield == (void *)&prec->low || paddr->pfield == (void *)&prec->lolo) { pgd->upper_disp_limit = prec->hopr; pgd->lower_disp_limit = prec->lopr; return 0; } if (paddr->pfield >= (void *)&prec->a && paddr->pfield <= (void *)&prec->l) { pgd->upper_disp_limit = prec->hopr; pgd->lower_disp_limit = prec->lopr; return 0; } if (paddr->pfield >= (void *)&prec->la && paddr->pfield <= (void *)&prec->ll) { pgd->upper_disp_limit = prec->hopr; pgd->lower_disp_limit = prec->lopr; return 0; } recGblGetGraphicDouble(paddr, pgd); return 0; } static long get_ctrl_double(DBADDR *paddr, struct dbr_ctrlDouble *pcd) { calcoutRecord *prec = (calcoutRecord *)paddr->precord; if (paddr->pfield == (void *)&prec->val || paddr->pfield == (void *)&prec->hihi || paddr->pfield == (void *)&prec->high || paddr->pfield == (void *)&prec->low || paddr->pfield == (void *)&prec->lolo) { pcd->upper_ctrl_limit = prec->hopr; pcd->lower_ctrl_limit = prec->lopr; return 0; } if (paddr->pfield >= (void *)&prec->a && paddr->pfield <= (void *)&prec->l) { pcd->upper_ctrl_limit = prec->hopr; pcd->lower_ctrl_limit = prec->lopr; return 0; } if (paddr->pfield >= (void *)&prec->la && paddr->pfield <= (void *)&prec->ll) { pcd->upper_ctrl_limit = prec->hopr; pcd->lower_ctrl_limit = prec->lopr; return 0; } recGblGetControlDouble(paddr, pcd); return 0; } static long get_alarm_double(DBADDR *paddr, struct dbr_alDouble *pad) { calcoutRecord *prec = (calcoutRecord *)paddr->precord; if (paddr->pfield == (void *)&prec->val) { 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 { recGblGetAlarmDouble(paddr, pad); } return 0; } static void checkAlarms(calcoutRecord *prec) { double val, hyst, lalm; double alev; epicsEnum16 asev; if (prec->udf) { recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM); 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 execOutput(calcoutRecord *prec) { long status; /* Determine output data */ switch(prec->dopt) { case calcoutDOPT_Use_VAL: prec->oval = prec->val; break; case calcoutDOPT_Use_OVAL: if (calcPerform(&prec->a, &prec->oval, prec->orpc)) { recGblSetSevr(prec, CALC_ALARM, INVALID_ALARM); } else { prec->udf = isnan(prec->oval); } break; } if (prec->udf){ recGblSetSevr(prec, UDF_ALARM, INVALID_ALARM); } /* Check to see what to do if INVALID */ if (prec->nsev < INVALID_ALARM ) { /* Output the value */ status = writeValue(prec); /* post event if output event != 0 */ if (prec->oevt > 0) { post_event((int)prec->oevt); } } else switch (prec->ivoa) { case menuIvoaContinue_normally: status = writeValue(prec); /* post event if output event != 0 */ if (prec->oevt > 0) { post_event((int)prec->oevt); } break; case menuIvoaDon_t_drive_outputs: break; case menuIvoaSet_output_to_IVOV: prec->oval = prec->ivov; status = writeValue(prec); /* post event if output event != 0 */ if (prec->oevt > 0) { post_event((int)prec->oevt); } break; default: status = -1; recGblRecordError(S_db_badField, (void *)prec, "calcout:process Illegal IVOA field"); } } static void monitor(calcoutRecord *prec) { unsigned monitor_mask; double delta; double *pnew; double *pprev; int i; monitor_mask = recGblResetAlarms(prec); /* check for value change */ delta = prec->mlst - prec->val; if (delta<0.0) delta = -delta; if (delta > prec->mdel) { /* post events for value change */ monitor_mask |= DBE_VALUE; /* update last value monitored */ prec->mlst = prec->val; } /* check for archive change */ delta = prec->alst - prec->val; if (delta<0.0) delta = -delta; if (delta > prec->adel) { /* post events on value field for archive 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); } /* check all input fields for changes*/ for (i = 0, pnew = &prec->a, pprev = &prec->la; ipovl != prec->oval) { db_post_events(prec, &prec->oval, monitor_mask|DBE_VALUE|DBE_LOG); prec->povl = prec->oval; } return; } static int fetch_values(calcoutRecord *prec) { DBLINK *plink; /* structure of the link field */ double *pvalue; long status = 0; int i; for (i = 0, plink = &prec->inpa, pvalue = &prec->a; irpvt; dbScanLock((dbCommon *)prec); prpvt->cbScheduled = 0; checkLinks(prec); dbScanUnlock((dbCommon *)prec); } static void checkLinks(calcoutRecord *prec) { DBLINK *plink; rpvtStruct *prpvt = prec->rpvt; int i; int stat; int caLink = 0; int caLinkNc = 0; epicsEnum16 *plinkValid; if (calcoutRecDebug) printf("checkLinks() for %p\n", prec); plink = &prec->inpa; plinkValid = &prec->inav; for (i = 0; itype == CA_LINK) { caLink = 1; stat = dbCaIsLinkConnected(plink); if (!stat && (*plinkValid == calcoutINAV_EXT_NC)) { caLinkNc = 1; } else if (!stat && (*plinkValid == calcoutINAV_EXT)) { *plinkValid = calcoutINAV_EXT_NC; db_post_events(prec, plinkValid, DBE_VALUE); caLinkNc = 1; } else if (stat && (*plinkValid == calcoutINAV_EXT_NC)) { *plinkValid = calcoutINAV_EXT; db_post_events(prec, plinkValid, DBE_VALUE); } } } if (caLinkNc) prpvt->caLinkStat = CA_LINKS_NOT_OK; else if (caLink) prpvt->caLinkStat = CA_LINKS_ALL_OK; else prpvt->caLinkStat = NO_CA_LINKS; if (!prpvt->cbScheduled && caLinkNc) { /* Schedule another CALLBACK */ prpvt->cbScheduled = 1; callbackRequestDelayed(&prpvt->checkLinkCb, .5); } } static long writeValue(calcoutRecord *prec) { calcoutDSET *pcalcoutDSET = (calcoutDSET *)prec->dset; if (!pcalcoutDSET || !pcalcoutDSET->write) { errlogPrintf("%s DSET write does not exist\n", prec->name); recGblSetSevr(prec, SOFT_ALARM, INVALID_ALARM); prec->pact = TRUE; return(-1); } return pcalcoutDSET->write(prec); }