/*************************************************************************\ * Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne * 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. \*************************************************************************/ /* lnkCalc.c */ /* Usage * {calc:{expr:"A*B", args:[{...}, ...], units:"mm"}} * First link in 'args' is 'A', second is 'B', and so forth. */ #include #include #include #include #include "alarm.h" #include "dbDefs.h" #include "errlog.h" #include "epicsAssert.h" #include "epicsString.h" #include "epicsTypes.h" #include "epicsTime.h" #include "dbAccessDefs.h" #include "dbCommon.h" #include "dbConvertFast.h" #include "dbLink.h" #include "dbJLink.h" #include "dbStaticLib.h" #include "dbStaticPvt.h" #include "postfix.h" #include "recGbl.h" #include "epicsExport.h" typedef struct calc_link { jlink jlink; /* embedded object */ int nArgs; short dbfType; enum { ps_init, ps_expr, ps_major, ps_minor, ps_args, ps_out, ps_prec, ps_units, ps_time, ps_error } pstate; epicsEnum16 stat; epicsEnum16 sevr; char amsg[DB_AMSG_SIZE]; short prec; char *expr; char *major; char *minor; char *post_expr; char *post_major; char *post_minor; char *units; short tinp; struct link inp[CALCPERFORM_NARGS]; struct link out; double arg[CALCPERFORM_NARGS]; epicsTimeStamp time; epicsUTag utag; double val; } calc_link; static lset lnkCalc_lset; /*************************** jlif Routines **************************/ static jlink* lnkCalc_alloc(short dbfType) { calc_link *clink; if (dbfType == DBF_FWDLINK) { errlogPrintf("lnkCalc: No support for forward links\n"); return NULL; } clink = calloc(1, sizeof(struct calc_link)); if (!clink) { errlogPrintf("lnkCalc: calloc() failed.\n"); return NULL; } clink->nArgs = 0; clink->dbfType = dbfType; clink->pstate = ps_init; clink->prec = 15; /* standard value for a double */ clink->tinp = -1; return &clink->jlink; } static void lnkCalc_free(jlink *pjlink) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); int i; for (i = 0; i < clink->nArgs; i++) dbJLinkFree(clink->inp[i].value.json.jlink); dbJLinkFree(clink->out.value.json.jlink); free(clink->expr); free(clink->major); free(clink->minor); free(clink->post_expr); free(clink->post_major); free(clink->post_minor); free(clink->units); free(clink); } static jlif_result lnkCalc_integer(jlink *pjlink, long long num) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); if (clink->pstate == ps_prec) { clink->prec = num; return jlif_continue; } if (clink->pstate != ps_args) { errlogPrintf("lnkCalc: Unexpected integer %lld\n", num); return jlif_stop; } if (clink->nArgs == CALCPERFORM_NARGS) { errlogPrintf("lnkCalc: Too many input args, limit is %d\n", CALCPERFORM_NARGS); return jlif_stop; } clink->arg[clink->nArgs++] = num; return jlif_continue; } static jlif_result lnkCalc_double(jlink *pjlink, double num) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); if (clink->pstate != ps_args) { errlogPrintf("lnkCalc: Unexpected double %g\n", num); return jlif_stop; } if (clink->nArgs == CALCPERFORM_NARGS) { errlogPrintf("lnkCalc: Too many input args, limit is %d\n", CALCPERFORM_NARGS); return jlif_stop; } clink->arg[clink->nArgs++] = num; return jlif_continue; } static jlif_result lnkCalc_string(jlink *pjlink, const char *val, size_t len) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); char *inbuf, *postbuf; short err; if (clink->pstate == ps_units) { clink->units = epicsStrnDup(val, len); return jlif_continue; } if (clink->pstate == ps_time) { char tinp; if (len != 1 || (tinp = toupper((int) val[0])) < 'A' || tinp > 'L') { errlogPrintf("lnkCalc: Bad 'time' parameter \"%.*s\"\n", (int) len, val); return jlif_stop; } clink->tinp = tinp - 'A'; return jlif_continue; } if (clink->pstate < ps_expr || clink->pstate > ps_minor) { errlogPrintf("lnkCalc: Unexpected string \"%.*s\"\n", (int) len, val); return jlif_stop; } postbuf = malloc(INFIX_TO_POSTFIX_SIZE(len+1)); if (!postbuf) { errlogPrintf("lnkCalc: Out of memory\n"); return jlif_stop; } inbuf = malloc(len+1); if(!inbuf) { errlogPrintf("lnkCalc: Out of memory\n"); free(postbuf); return jlif_stop; } memcpy(inbuf, val, len); inbuf[len] = '\0'; if (clink->pstate == ps_major) { clink->major = inbuf; clink->post_major = postbuf; } else if (clink->pstate == ps_minor) { clink->minor = inbuf; clink->post_minor = postbuf; } else { clink->expr = inbuf; clink->post_expr = postbuf; } if (postfix(inbuf, postbuf, &err) < 0) { errlogPrintf("lnkCalc: Error in calc expression, %s\n", calcErrorStr(err)); return jlif_stop; } return jlif_continue; } static jlif_key_result lnkCalc_start_map(jlink *pjlink) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); if (clink->pstate == ps_args) return jlif_key_child_inlink; if (clink->pstate == ps_out) return jlif_key_child_outlink; if (clink->pstate != ps_init) { errlogPrintf("lnkCalc: Unexpected map\n"); return jlif_key_stop; } return jlif_key_continue; } static jlif_result lnkCalc_map_key(jlink *pjlink, const char *key, size_t len) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); /* FIXME: These errors messages are wrong when a key is duplicated. * The key is known, we just don't allow it more than once. */ if (len == 3) { if (!strncmp(key, "out", len) && clink->dbfType == DBF_OUTLINK && clink->out.type == 0) clink->pstate = ps_out; else { errlogPrintf("lnkCalc: Unknown key \"%.3s\"\n", key); return jlif_stop; } } else if (len == 4) { if (!strncmp(key, "expr", len) && !clink->post_expr) clink->pstate = ps_expr; else if (!strncmp(key, "args", len) && !clink->nArgs) clink->pstate = ps_args; else if (!strncmp(key, "prec", len)) clink->pstate = ps_prec; else if (!strncmp(key, "time", len)) clink->pstate = ps_time; else { errlogPrintf("lnkCalc: Unknown key \"%.4s\"\n", key); return jlif_stop; } } else if (len == 5) { if (!strncmp(key, "major", len) && !clink->post_major) clink->pstate = ps_major; else if (!strncmp(key, "minor", len) && !clink->post_minor) clink->pstate = ps_minor; else if (!strncmp(key, "units", len) && !clink->units) clink->pstate = ps_units; else { errlogPrintf("lnkCalc: Unknown key \"%.5s\"\n", key); return jlif_stop; } } else { errlogPrintf("lnkCalc: Unknown key \"%.*s\"\n", (int) len, key); return jlif_stop; } return jlif_continue; } static jlif_result lnkCalc_end_map(jlink *pjlink) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); if (clink->pstate == ps_error) return jlif_stop; else if (clink->dbfType == DBF_INLINK && !clink->post_expr) { errlogPrintf("lnkCalc: No expression ('expr' key)\n"); return jlif_stop; } else if (clink->dbfType == DBF_OUTLINK && clink->out.type != JSON_LINK) { errlogPrintf("lnkCalc: No output link ('out' key)\n"); return jlif_stop; } return jlif_continue; } static jlif_result lnkCalc_start_array(jlink *pjlink) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); if (clink->pstate != ps_args) { errlogPrintf("lnkCalc: Unexpected array\n"); return jlif_stop; } return jlif_continue; } static jlif_result lnkCalc_end_array(jlink *pjlink) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); if (clink->pstate == ps_error) return jlif_stop; return jlif_continue; } static void lnkCalc_end_child(jlink *parent, jlink *child) { calc_link *clink = CONTAINER(parent, struct calc_link, jlink); struct link *plink; if (clink->pstate == ps_args) { if (clink->nArgs == CALCPERFORM_NARGS) { errlogPrintf("lnkCalc: Too many input args, limit is %d\n", CALCPERFORM_NARGS); goto errOut; } plink = &clink->inp[clink->nArgs++]; } else if (clink->pstate == ps_out) { plink = &clink->out; } else { errlogPrintf("lnkCalc: Unexpected child link, parser state = %d\n", clink->pstate); errOut: clink->pstate = ps_error; dbJLinkFree(child); return; } plink->type = JSON_LINK; plink->value.json.string = NULL; plink->value.json.jlink = child; } static struct lset* lnkCalc_get_lset(const jlink *pjlink) { return &lnkCalc_lset; } static void lnkCalc_report(const jlink *pjlink, int level, int indent) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); int i; printf("%*s'calc': \"%s\" = %.*g %s\n", indent, "", clink->expr, clink->prec, clink->val, clink->units ? clink->units : ""); if (level > 0) { if (clink->sevr) printf("%*s Alarm: %s, %s, \"%s\"\n", indent, "", epicsAlarmSeverityStrings[clink->sevr], epicsAlarmConditionStrings[clink->stat], clink->amsg); if (clink->post_major) printf("%*s Major expression: \"%s\"\n", indent, "", clink->major); if (clink->post_minor) printf("%*s Minor expression: \"%s\"\n", indent, "", clink->minor); if (clink->tinp >= 0) { char timeStr[40]; epicsTimeToStrftime(timeStr, 40, "%Y-%m-%d %H:%M:%S.%09f", &clink->time); printf("%*s Timestamp input %c: %s\n", indent, "", clink->tinp + 'A', timeStr); } for (i = 0; i < clink->nArgs; i++) { struct link *plink = &clink->inp[i]; jlink *child = plink->type == JSON_LINK ? plink->value.json.jlink : NULL; printf("%*s Input %c: %g\n", indent, "", i + 'A', clink->arg[i]); if (child) dbJLinkReport(child, level - 1, indent + 4); } if (clink->out.type == JSON_LINK) { printf("%*s Output:\n", indent, ""); dbJLinkReport(clink->out.value.json.jlink, level - 1, indent + 4); } } } static long lnkCalc_map_children(jlink *pjlink, jlink_map_fn rtn, void *ctx) { calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); int i; for (i = 0; i < clink->nArgs; i++) { struct link *child = &clink->inp[i]; long status = dbJLinkMapChildren(child, rtn, ctx); if (status) return status; } if (clink->out.type == JSON_LINK) { return dbJLinkMapChildren(&clink->out, rtn, ctx); } return 0; } /*************************** lset Routines **************************/ static void lnkCalc_open(struct link *plink) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); int i; for (i = 0; i < clink->nArgs; i++) { struct link *child = &clink->inp[i]; child->precord = plink->precord; dbJLinkInit(child); dbLoadLink(child, DBR_DOUBLE, &clink->arg[i]); } if (clink->out.type == JSON_LINK) { dbJLinkInit(&clink->out); } } static void lnkCalc_remove(struct dbLocker *locker, struct link *plink) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); int i; for (i = 0; i < clink->nArgs; i++) { struct link *child = &clink->inp[i]; dbRemoveLink(locker, child); } if (clink->out.type == JSON_LINK) { dbRemoveLink(locker, &clink->out); } free(clink->expr); free(clink->major); free(clink->minor); free(clink->post_expr); free(clink->post_major); free(clink->post_minor); free(clink->units); free(clink); plink->value.json.jlink = NULL; } static int lnkCalc_isConn(const struct link *plink) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); int connected = 1; int i; for (i = 0; i < clink->nArgs; i++) { struct link *child = &clink->inp[i]; if (dbLinkIsVolatile(child) && !dbIsLinkConnected(child)) connected = 0; } if (clink->out.type == JSON_LINK) { struct link *child = &clink->out; if (dbLinkIsVolatile(child) && !dbIsLinkConnected(child)) connected = 0; } return connected; } static int lnkCalc_getDBFtype(const struct link *plink) { return DBF_DOUBLE; } static long lnkCalc_getElements(const struct link *plink, long *nelements) { *nelements = 1; return 0; } /* Get value and timestamp atomically for link indicated by time */ struct lcvt { double *pval; epicsTimeStamp *ptime; epicsUTag *ptag; }; static long readLocked(struct link *pinp, void *vvt) { struct lcvt *pvt = (struct lcvt *) vvt; long nReq = 1; long status = dbGetLink(pinp, DBR_DOUBLE, pvt->pval, NULL, &nReq); if (!status && pvt->ptime) dbGetTimeStampTag(pinp, pvt->ptime, pvt->ptag); return status; } static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer, long *pnRequest) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); dbCommon *prec = plink->precord; int i; long status; FASTCONVERTFUNC conv; if(INVALID_DB_REQ(dbrType)) return S_db_badDbrtype; conv = dbFastPutConvertRoutine[DBR_DOUBLE][dbrType]; /* Any link errors will trigger a LINK/INVALID alarm in the child link */ for (i = 0; i < clink->nArgs; i++) { struct link *child = &clink->inp[i]; long nReq = 1; if (i == clink->tinp) { struct lcvt vt = {&clink->arg[i], &clink->time, &clink->utag}; status = dbLinkDoLocked(child, readLocked, &vt); if (status == S_db_noLSET) status = readLocked(child, &vt); if (dbLinkIsConstant(&prec->tsel) && prec->tse == epicsTimeEventDeviceTime) { prec->time = clink->time; prec->utag = clink->utag; } } else dbGetLink(child, DBR_DOUBLE, &clink->arg[i], NULL, &nReq); } clink->stat = 0; clink->sevr = 0; clink->amsg[0] = '\0'; if (clink->post_expr) { status = calcPerform(clink->arg, &clink->val, clink->post_expr); if (!status) status = conv(&clink->val, pbuffer, NULL); if (!status && pnRequest) *pnRequest = 1; } else { status = 0; if (pnRequest) *pnRequest = 0; } if (!status && clink->post_major) { double alval = clink->val; status = calcPerform(clink->arg, &alval, clink->post_major); if (!status && alval) { clink->stat = LINK_ALARM; clink->sevr = MAJOR_ALARM; strcpy(clink->amsg, "post_major error"); recGblSetSevrMsg(prec, clink->stat, clink->sevr, "post_major error"); } } if (!status && !clink->sevr && clink->post_minor) { double alval = clink->val; status = calcPerform(clink->arg, &alval, clink->post_minor); if (!status && alval) { clink->stat = LINK_ALARM; clink->sevr = MINOR_ALARM; strcpy(clink->amsg, "post_minor error"); recGblSetSevrMsg(prec, clink->stat, clink->sevr, "post_minor error"); } } return status; } static long lnkCalc_putValue(struct link *plink, short dbrType, const void *pbuffer, long nRequest) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); dbCommon *prec = plink->precord; int i; long status; FASTCONVERTFUNC conv; if(INVALID_DB_REQ(dbrType)) return S_db_badDbrtype; conv = dbFastGetConvertRoutine[dbrType][DBR_DOUBLE]; /* Any link errors will trigger a LINK/INVALID alarm in the child link */ for (i = 0; i < clink->nArgs; i++) { struct link *child = &clink->inp[i]; long nReq = 1; if (i == clink->tinp) { struct lcvt vt = {&clink->arg[i], &clink->time, &clink->utag}; status = dbLinkDoLocked(child, readLocked, &vt); if (status == S_db_noLSET) status = readLocked(child, &vt); if (dbLinkIsConstant(&prec->tsel) && prec->tse == epicsTimeEventDeviceTime) { prec->time = clink->time; prec->utag = clink->utag; } } else dbGetLink(child, DBR_DOUBLE, &clink->arg[i], NULL, &nReq); } clink->stat = 0; clink->sevr = 0; clink->amsg[0] = '\0'; /* Get the value being output as VAL */ status = conv(pbuffer, &clink->val, NULL); if (!status && clink->post_expr) status = calcPerform(clink->arg, &clink->val, clink->post_expr); if (!status && clink->post_major) { double alval = clink->val; status = calcPerform(clink->arg, &alval, clink->post_major); if (!status && alval) { clink->stat = LINK_ALARM; clink->sevr = MAJOR_ALARM; strcpy(clink->amsg, "post_major error"); recGblSetSevrMsg(prec, clink->stat, clink->sevr, "post_major error"); } } if (!status && !clink->sevr && clink->post_minor) { double alval = clink->val; status = calcPerform(clink->arg, &alval, clink->post_minor); if (!status && alval) { clink->stat = LINK_ALARM; clink->sevr = MINOR_ALARM; strcpy(clink->amsg, "post_major error"); recGblSetSevrMsg(prec, clink->stat, clink->sevr, "post_minor error"); } } if (!status) { status = dbPutLink(&clink->out, DBR_DOUBLE, &clink->val, 1); } return status; } static long lnkCalc_getPrecision(const struct link *plink, short *precision) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); *precision = clink->prec; return 0; } static long lnkCalc_getUnits(const struct link *plink, char *units, int len) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); if (clink->units) { strncpy(units, clink->units, --len); units[len] = '\0'; } else units[0] = '\0'; return 0; } static long lnkCalc_getAlarmMsg(const struct link *plink, epicsEnum16 *status, epicsEnum16 *severity, char *msgbuf, size_t msgbuflen) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); if (status) *status = clink->stat; if (severity) *severity = clink->sevr; if (msgbuf && msgbuflen) { strncpy(msgbuf, clink->amsg, msgbuflen-1); msgbuf[msgbuflen-1] = '\0'; } return 0; } static long lnkCalc_getTimestampTag(const struct link *plink, epicsTimeStamp *pstamp, epicsUTag *ptag) { calc_link *clink = CONTAINER(plink->value.json.jlink, struct calc_link, jlink); if (clink->tinp >= 0) { *pstamp = clink->time; if(ptag) *ptag = clink->utag; return 0; } return -1; } static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv) { return rtn(plink, priv); } /************************* Interface Tables *************************/ static lset lnkCalc_lset = { 0, 1, /* not Constant, Volatile */ lnkCalc_open, lnkCalc_remove, NULL, NULL, NULL, lnkCalc_isConn, lnkCalc_getDBFtype, lnkCalc_getElements, lnkCalc_getValue, NULL, NULL, NULL, lnkCalc_getPrecision, lnkCalc_getUnits, NULL, NULL, lnkCalc_putValue, NULL, NULL, doLocked, lnkCalc_getAlarmMsg, lnkCalc_getTimestampTag, }; static jlif lnkCalcIf = { "calc", lnkCalc_alloc, lnkCalc_free, NULL, NULL, lnkCalc_integer, lnkCalc_double, lnkCalc_string, lnkCalc_start_map, lnkCalc_map_key, lnkCalc_end_map, lnkCalc_start_array, lnkCalc_end_array, lnkCalc_end_child, lnkCalc_get_lset, lnkCalc_report, lnkCalc_map_children, NULL }; epicsExportAddress(jlif, lnkCalcIf);