diff --git a/src/std/link/Makefile b/src/std/link/Makefile index 56b56b8be..31d14b825 100644 --- a/src/std/link/Makefile +++ b/src/std/link/Makefile @@ -12,6 +12,7 @@ SRC_DIRS += $(STDDIR)/link DBD += links.dbd dbRecStd_SRCS += lnkConst.c +dbRecStd_SRCS += lnkCalc.c HTMLS += links.html diff --git a/src/std/link/links.dbd.pod b/src/std/link/links.dbd.pod index 335808097..9cff283d0 100644 --- a/src/std/link/links.dbd.pod +++ b/src/std/link/links.dbd.pod @@ -94,18 +94,53 @@ promoted to doubles. Mixing strings and numbers in an array will result in an er =cut -#link(calc, lnkCalcIf) +link(calc, lnkCalcIf) =head3 Calculation Link C<"calc"> -... +Calculation links can perform simple mathematical expressions on scalar +(double-precision floating-point) values obtained from other link types and +return a single double-precision floating-point result. The expressions are +evaluated by the EPICS Calc engine, and up to 12 input links can be used. =head4 Parameters -... +The link value is a map with the following keys: + +=over + +=item expr + +The expression to be evaluated, provided as a string. + +=item major + +An optional expression that returns non-zero to raise a major alarm. + +=item minor + +An optional expression that returns non-zero to raise a minor alarm. + +=item args + +A JSON list of up to 12 input arguments for the expression, which are assigned +to the inputs C, C, C, ... C. Each input argument may be either a +numeric literal or an embedded link inside C<{}> braces. + +=item units + +An optional string specifying the engineering units for the result of the +expression. Equivalent to the C field of a record. + +item prec + +An optional integer specifying the numeric precision with which the calculation +result should be displayed. Equivalent to the E field of a record. + +=back =head4 Example - ... + {calc: {expr:"A+B", args:[1, 2]}} =cut diff --git a/src/std/link/lnkCalc.c b/src/std/link/lnkCalc.c new file mode 100644 index 000000000..015dd0311 --- /dev/null +++ b/src/std/link/lnkCalc.c @@ -0,0 +1,545 @@ +/*************************************************************************\ +* Copyright (c) 2016 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ +/* lnkCalc.c */ + +#include +#include +#include + +#include "alarm.h" +#include "dbDefs.h" +#include "dbmf.h" +#include "errlog.h" +#include "epicsAssert.h" +#include "epicsString.h" +#include "epicsTypes.h" +#include "dbAccessDefs.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 long (*FASTCONVERT)(); + +/* Change 'undef' to 'define' to turn on debug statements: */ +#define DEBUG_LINK + +#ifdef DEBUG_LINK + int lnkCalcDebug = 10; +# define IFDEBUG(n) \ + if (lnkCalcDebug >= n) /* block or statement */ +#else +# define IFDEBUG(n) \ + if(0) /* Compiler will elide the block or statement */ +#endif + +typedef struct calc_link { + jlink jlink; /* embedded object */ + int nArgs; + enum {ps_init, + ps_expr, ps_major, ps_minor, + ps_args, + ps_prec, + ps_units, + ps_error} + pstate; + epicsEnum16 stat; + epicsEnum16 sevr; + short prec; + char *post_expr; + char *post_major; + char *post_minor; + char *units; + struct link inp[CALCPERFORM_NARGS]; + double arg[CALCPERFORM_NARGS]; + double val; +} calc_link; + +static lset lnkCalc_lset; + + +/*************************** jlif Routines **************************/ + +static jlink* lnkCalc_alloc(short dbfType) { + calc_link *clink = calloc(1, sizeof(struct calc_link)); + + IFDEBUG(10) + printf("lnkCalc_alloc()\n"); + + clink->nArgs = 0; + clink->pstate = ps_init; + clink->prec = 15; /* standard value for a double */ + + IFDEBUG(10) + printf("lnkCalc_alloc -> calc@%p\n", clink); + + return &clink->jlink; +} + +static void lnkCalc_free(jlink *pjlink) { + calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); + int i; + + IFDEBUG(10) + printf("lnkCalc_free(calc@%p)\n", pjlink); + + for (i = 0; i < clink->nArgs; i++) + dbJLinkFree(clink->inp[i].value.json.jlink); + + 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 num) { + calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_integer(calc@%p, %ld)\n", pjlink, num); + + if (clink->pstate == ps_prec) { + clink->prec = num; + return jlif_continue; + } + + if (clink->pstate != ps_args) { + return jlif_stop; + errlogPrintf("lnkCalc: Unexpected integer %ld\n", num); + } + + 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); + + IFDEBUG(10) + printf("lnkCalc_double(calc@%p, %g)\n", pjlink, num); + + if (clink->pstate != ps_args) { + return jlif_stop; + errlogPrintf("lnkCalc: Unexpected double %g\n", num); + } + + 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; + + IFDEBUG(10) + printf("lnkCalc_string(calc@%p, \"%.*s\")\n", clink, (int) len, val); + + if (clink->pstate == ps_units) { + clink->units = epicsStrnDup(val, len); + 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)); + if (!postbuf) { + errlogPrintf("lnkCalc: Out of memory\n"); + return jlif_stop; + } + + if (clink->pstate == ps_major) + clink->post_major = postbuf; + else if (clink->pstate == ps_minor) + clink->post_minor = postbuf; + else + clink->post_expr = postbuf; + + inbuf = dbmfStrndup(val, len); + + if (postfix(inbuf, postbuf, &err) < 0) { + errlogPrintf("lnkCalc: Error in calc expression, %s\n", + calcErrorStr(err)); + dbmfFree(inbuf); + return jlif_stop; + } + + dbmfFree(inbuf); + return jlif_continue; +} + +static jlif_key_result lnkCalc_start_map(jlink *pjlink) { + calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_start_array(calc@%p)\n", pjlink); + + if (clink->pstate == ps_args) + return jlif_key_child_link; + + if (clink->pstate != ps_init) { + errlogPrintf("lnkCalc: Unexpected map\n"); + return jlif_stop; + } + + return jlif_continue; +} + +static jlif_result lnkCalc_map_key(jlink *pjlink, const char *key, size_t len) { + calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_map_key(calc@%p, \"%.*s\")\n", + pjlink, (int) len, key); + + 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 { + 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); + + IFDEBUG(10) + printf("lnkCalc_end_array(calc@%p)\n", pjlink); + + if (clink->pstate == ps_error) + return jlif_stop; + + return jlif_continue; +} + +static jlif_result lnkCalc_start_array(jlink *pjlink) { + calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_start_array(calc@%p)\n", pjlink); + + 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); + + IFDEBUG(10) + printf("lnkCalc_end_array(calc@%p)\n", pjlink); + + 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->nArgs == CALCPERFORM_NARGS) { + dbJLinkFree(child); + errlogPrintf("lnkCalc: Too many input args, limit is %d\n", + CALCPERFORM_NARGS); + clink->pstate = ps_error; + return; + } + + plink = &clink->inp[clink->nArgs++]; + plink->type = JSON_LINK; + plink->value.json.string = NULL; + plink->value.json.jlink = child; +} + +static struct lset* lnkCalc_get_lset(const jlink *pjlink) { + IFDEBUG(10) + printf("lnkCalc_get_lset(calc@%p)\n", pjlink); + + return &lnkCalc_lset; +} + +static void lnkCalc_report(const jlink *pjlink) { + calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_report(calc@%p)\n", clink); + + /* FIXME Implement! */ +} + +/*************************** lset Routines **************************/ + +static void lnkCalc_open(struct link *plink) +{ + calc_link *clink = CONTAINER(plink->value.json.jlink, + struct calc_link, jlink); + int i; + + IFDEBUG(10) + printf("lnkCalc_open(calc@%p)\n", clink); + + for (i = 0; i < clink->nArgs; i++) { + struct link *child = &clink->inp[i]; + + child->precord = plink->precord; + dbJLinkInit(child); + } +} + +static void lnkCalc_remove(struct dbLocker *locker, struct link *plink) +{ + calc_link *clink = CONTAINER(plink->value.json.jlink, + struct calc_link, jlink); + int i; + + IFDEBUG(10) + printf("lnkCalc_remove(calc@%p)\n", clink); + + for (i = 0; i < clink->nArgs; i++) { + struct link *child = &clink->inp[i]; + + dbRemoveLink(locker, child); + } + + 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; + + IFDEBUG(10) + printf("lnkCalc_isConn(calc@%p)\n", clink); + + for (i = 0; i < clink->nArgs; i++) { + struct link *child = &clink->inp[i]; + + if (dbLinkIsVolatile(child) > 0 && + !dbIsLinkConnected(child)) + connected = 0; + } + + return connected; +} + +static int lnkCalc_getDBFtype(const struct link *plink) +{ + IFDEBUG(10) { + calc_link *clink = CONTAINER(plink->value.json.jlink, + struct calc_link, jlink); + + printf("lnkCalc_getDBFtype(calc@%p)\n", clink); + } + + return DBF_DOUBLE; +} + +static long lnkCalc_getElements(const struct link *plink, long *nelements) +{ + IFDEBUG(10) { + calc_link *clink = CONTAINER(plink->value.json.jlink, + struct calc_link, jlink); + + printf("lnkCalc_getElements(calc@%p, (%ld))\n", + clink, *nelements); + } + + *nelements = 1; + return 0; +} + +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); + int i; + long status; + FASTCONVERT conv = dbFastPutConvertRoutine[DBR_DOUBLE][dbrType]; + + IFDEBUG(10) + printf("lnkCalc_getValue(calc@%p, %d, ...)\n", + clink, dbrType); + + for (i = 0; i < clink->nArgs; i++) { + struct link *child = &clink->inp[i]; + long nReq = 1; + + dbGetLink(child, DBR_DOUBLE, &clink->arg[i], NULL, &nReq); + } + clink->stat = 0; + clink->sevr = 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; + recGblSetSevr(plink->precord, clink->stat, clink->sevr); + } + } + + if (!status && 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; + recGblSetSevr(plink->precord, clink->stat, clink->sevr); + } + } + + return status; +} + +static long lnkCalc_getPrecision(const struct link *plink, short *precision) +{ + calc_link *clink = CONTAINER(plink->value.json.jlink, + struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_getPrecision(calc@%p)\n", clink); + + *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); + + IFDEBUG(10) + printf("lnkCalc_getUnits(calc@%p)\n", clink); + + if (clink->units) { + strncpy(units, clink->units, --len); + units[len] = '\0'; + } + else + units[0] = '\0'; + return 0; +} + +static long lnkCalc_getAlarm(const struct link *plink, epicsEnum16 *status, + epicsEnum16 *severity) +{ + calc_link *clink = CONTAINER(plink->value.json.jlink, + struct calc_link, jlink); + + IFDEBUG(10) + printf("lnkCalc_getAlarm(calc@%p)\n", clink); + + if (status) + *status = clink->stat; + if (severity) + *severity = clink->sevr; + + return 0; +} + + +/************************* 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, + lnkCalc_getAlarm, NULL, + NULL, NULL, + NULL +}; + +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 +}; +epicsExportAddress(jlif, lnkCalcIf); +