From 97ea68d40c65d4d1e5ec2e36050475204ca69c27 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 9 Mar 2018 22:42:59 -0600 Subject: [PATCH] Added DBF_OUTLINK support to lnkCalc New out key specifying a child link to put the calculated result to. --- src/std/link/links.dbd.pod | 35 +++++++- src/std/link/lnkCalc.c | 168 ++++++++++++++++++++++++++++++++----- 2 files changed, 180 insertions(+), 23 deletions(-) diff --git a/src/std/link/links.dbd.pod b/src/std/link/links.dbd.pod index 6eb8b77d3..6b48c13fb 100644 --- a/src/std/link/links.dbd.pod +++ b/src/std/link/links.dbd.pod @@ -80,10 +80,30 @@ variable(lnkCalc_debug, int) =head3 Calculation Link C<"calc"> -Calculation links are input links that can evaluate mathematical expressions on -scalar (double- precision floating-point) values obtained from child links, and -return a double-precision floating-point result. The expressions are evaluated -by the EPICS Calc engine, and up to 12 inputs can be provided. +A calculation link is an input link that can evaluate mathematical expressions +on scalar (double-precision floating-point) values obtained from up to 12 child +input links, and returns a double-precision floating-point result. The +expression is evaluated by the EPICS Calc engine, and the result is returned as +the value of the link. + +Two additional expressions may also be provided and are evaluated to determine +whether the record owning the link should be placed in alarm state. In both +cases the result of the main calculation is available to these expressions as +C (attempts to assign to C inside either expression will have no +lasting effect). If the C expression evaluates to a non-zero value the +record will be placed in C alarm. If not and the C expression +evaluates to non-zero the record will be placed in C alarm state. + +A calculation link can also be an output link, with the scalar output value +being converted to a double and provided to the expression as C. Up to 12 +additional input links can also be read and provided to the expression as above. +The result of the calculation is forwarded to a child output link specified in +the link's C parameter. + +For an output link the main expression is actually optional; if not provided the +converted value will be forwarded to the output link unchanged. The two alarm +expressions may still be used to put the output link into alarm state as +described above. =head4 Parameters @@ -94,6 +114,7 @@ The link address is a JSON map with the following keys: =item expr The primary expression to be evaluated, given as a string. +This is optional for output links, required for input links. =item major @@ -110,6 +131,12 @@ to the inputs C, C, C, ... C. Each input argument may be either a numeric literal or an embedded JSON link inside C<{}> braces. The same input values are provided to the two alarm expressions as to the primary expression. +=item out + +A JSON link inside C<{}> braces which specifies the destination of C +operations after any expressions have been evaluated. +This key is required for output links, not used by input links. + =item units An optional string specifying the engineering units for the result of the diff --git a/src/std/link/lnkCalc.c b/src/std/link/lnkCalc.c index cc08e84f8..de76948a9 100644 --- a/src/std/link/lnkCalc.c +++ b/src/std/link/lnkCalc.c @@ -6,13 +6,9 @@ \*************************************************************************/ /* lnkCalc.c */ -/* Current usage - * {calc:{expr:"A", args:[{...}, ...]}} +/* Usage + * {calc:{expr:"A*B", args:[{...}, ...], units:"mm"}} * First link in 'args' is 'A', second is 'B', and so forth. - * - * TODO: - * Support setting individual input links instead of the args list. - * {calc:{expr:"K", K:{...}}} */ #include @@ -49,10 +45,11 @@ epicsExportAddress(int, lnkCalc_debug); typedef struct calc_link { jlink jlink; /* embedded object */ int nArgs; + short dbfType; enum { ps_init, ps_expr, ps_major, ps_minor, - ps_args, + ps_args, ps_out, ps_prec, ps_units, ps_time, @@ -70,6 +67,7 @@ typedef struct calc_link { char *units; short tinp; struct link inp[CALCPERFORM_NARGS]; + struct link out; double arg[CALCPERFORM_NARGS]; epicsTimeStamp time; double val; @@ -87,8 +85,8 @@ static jlink* lnkCalc_alloc(short dbfType) IFDEBUG(10) printf("lnkCalc_alloc(%d)\n", dbfType); - if (dbfType != DBF_INLINK) { - errlogPrintf("lnkCalc: Only works with input links\n"); + if (dbfType == DBF_FWDLINK) { + errlogPrintf("lnkCalc: No support for forward links\n"); return NULL; } @@ -99,6 +97,7 @@ static jlink* lnkCalc_alloc(short dbfType) } clink->nArgs = 0; + clink->dbfType = dbfType; clink->pstate = ps_init; clink->prec = 15; /* standard value for a double */ clink->tinp = -1; @@ -120,6 +119,8 @@ static void lnkCalc_free(jlink *pjlink) 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); @@ -253,7 +254,7 @@ static jlif_key_result lnkCalc_start_map(jlink *pjlink) IFDEBUG(10) printf("lnkCalc_start_map(calc@%p)\n", clink); - if (clink->pstate == ps_args) + if (clink->pstate == ps_args || clink->pstate == ps_out) return jlif_key_child_link; if (clink->pstate != ps_init) { @@ -271,7 +272,21 @@ static jlif_result lnkCalc_map_key(jlink *pjlink, const char *key, size_t len) IFDEBUG(10) printf("lnkCalc_map_key(calc@%p, \"%.*s\")\n", pjlink, (int) len, key); - if (len == 4) { + /* 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) @@ -314,8 +329,14 @@ static jlif_result lnkCalc_end_map(jlink *pjlink) if (clink->pstate == ps_error) return jlif_stop; - else if (!clink->post_expr) { - errlogPrintf("lnkCalc: no expression ('expr' key)\n"); + 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; } @@ -355,15 +376,27 @@ 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); + 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 = &clink->inp[clink->nArgs++]; plink->type = JSON_LINK; plink->value.json.string = NULL; plink->value.json.jlink = child; @@ -421,6 +454,12 @@ static void lnkCalc_report(const jlink *pjlink, int level, int indent) 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); + } } } @@ -439,6 +478,10 @@ long lnkCalc_map_children(jlink *pjlink, jlink_map_fn rtn, void *ctx) if (status) return status; } + + if (clink->out.type == JSON_LINK) { + return dbJLinkMapChildren(&clink->out, rtn, ctx); + } return 0; } @@ -460,6 +503,10 @@ static void lnkCalc_open(struct link *plink) 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) @@ -477,6 +524,10 @@ static void lnkCalc_remove(struct dbLocker *locker, struct link *plink) dbRemoveLink(locker, child); } + if (clink->out.type == JSON_LINK) { + dbRemoveLink(locker, &clink->out); + } + free(clink->expr); free(clink->major); free(clink->minor); @@ -506,6 +557,14 @@ static int lnkCalc_isConn(const struct link *plink) connected = 0; } + if (clink->out.type == JSON_LINK) { + struct link *child = &clink->out; + + if (dbLinkIsVolatile(child) && + !dbIsLinkConnected(child)) + connected = 0; + } + return connected; } @@ -626,6 +685,77 @@ static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer, 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; + FASTCONVERT conv = dbFastGetConvertRoutine[dbrType][DBR_DOUBLE]; + + IFDEBUG(10) + printf("lnkCalc_putValue(calc@%p, %d, ...)\n", clink, 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}; + + 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; + } + } + else + dbGetLink(child, DBR_DOUBLE, &clink->arg[i], NULL, &nReq); + } + clink->stat = 0; + clink->sevr = 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; + recGblSetSevr(prec, 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(prec, clink->stat, clink->sevr); + } + } + + 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, @@ -705,7 +835,7 @@ static lset lnkCalc_lset = { NULL, NULL, NULL, lnkCalc_getPrecision, lnkCalc_getUnits, lnkCalc_getAlarm, lnkCalc_getTimestamp, - NULL, NULL, + lnkCalc_putValue, NULL, NULL, doLocked };