diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html
index e0eb7ef39..3f98c6f06 100644
--- a/documentation/RELEASE_NOTES.html
+++ b/documentation/RELEASE_NOTES.html
@@ -19,6 +19,38 @@
-->
+
Link type enhancements
+
+This release adds three new link types: "state", "debug" and "trace". The
+"state" link type gets and puts boolean values from/to the dbState library that
+was added in the 3.15.1 release. The "debug" link type sets the
+jlink::debug flag in its child link, while the "trace" link type
+also causes the arguments and return values for all calls to the child link's
+jlif and lset routines to be printed on stdout. The debug flag can no longer be
+set using an info tag. The addition of the "trace" link type has allowed over
+200 lines of conditional diagnostic printf() calls to be removed from the other
+link types.
+
+The "calc" link type can now be used for output links as well as input links.
+This allows modification of the output value and even combining it with values
+from other input links. See the separate JSON Link types document for
+details.
+
+A new start_child() method was added to the end of the jlif
+interface table.
+
+The lset methods have now been properly documented in the
+dbLink.h header file using Doxygen annotations, although we do not run Doxygen
+on the source tree yet to generate API documentation.
+
+Link types that utilize child links must now indicate whether the child will
+be used for input, output or forward linking by the return value from its
+parse_start_map() method. The jlif_key_result enum now
+contains 3 values jlif_key_child_inlink,
+jlif_key_child_outlink and jlif_key_child_fwdlink
+instead of the single jlif_key_child_link that was previously used
+for this.
+
GNUmake targets for debugging
Some additional build rules have been added to help debug configuration
diff --git a/src/Makefile b/src/Makefile
index 257099840..482e14843 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -4,7 +4,7 @@
# 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 the file LICENSE that is included with this distribution.
+# in the file LICENSE that is included with this distribution.
#*************************************************************************
TOP = ..
@@ -72,9 +72,11 @@ std_DEPEND_DIRS = ioc libCom/RTEMS
DIRS += std/filters/test
std/filters/test_DEPEND_DIRS = std
+DIRS += std/link/test
+std/link/test_DEPEND_DIRS = std
+
DIRS += std/rec/test
std/rec/test_DEPEND_DIRS = std
include $(TOP)/configure/RULES_DIRS
-
diff --git a/src/ioc/db/dbAccess.c b/src/ioc/db/dbAccess.c
index 44a31ef86..e255fff55 100644
--- a/src/ioc/db/dbAccess.c
+++ b/src/ioc/db/dbAccess.c
@@ -1029,7 +1029,7 @@ static long dbPutFieldLink(DBADDR *paddr,
return S_db_badDbrtype;
}
- status = dbParseLink(pstring, pfldDes->field_type, &link_info, 0);
+ status = dbParseLink(pstring, pfldDes->field_type, &link_info);
if (status)
return status;
@@ -1336,4 +1336,3 @@ done:
paddr->pfield = pfieldsave;
return status;
}
-
diff --git a/src/ioc/db/dbJLink.c b/src/ioc/db/dbJLink.c
index ea054eee9..3acf75660 100644
--- a/src/ioc/db/dbJLink.c
+++ b/src/ioc/db/dbJLink.c
@@ -2,7 +2,7 @@
* 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.
+* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* dbJLink.c */
@@ -25,19 +25,33 @@
#include "dbLock.h"
#include "dbStaticLib.h"
#include "link.h"
+#include "epicsExport.h"
-#define IFDEBUG(n) if(parser->parse_debug)
+epicsShareDef int dbJLinkDebug = 0;
+epicsExportAddress(int, dbJLinkDebug);
+
+#define IFDEBUG(n) if (dbJLinkDebug >= (n))
typedef struct parseContext {
jlink *pjlink;
jlink *product;
short dbfType;
short jsonDepth;
- unsigned key_is_link:1;
- unsigned parse_debug:1;
- unsigned lset_debug:1;
} parseContext;
+epicsShareDef const char *jlif_result_name[2] = {
+ "jlif_stop",
+ "jlif_continue",
+};
+
+epicsShareDef const char *jlif_key_result_name[5] = {
+ "jlif_key_stop",
+ "jlif_key_continue",
+ "jlif_key_child_inlink",
+ "jlif_key_child_outlink",
+ "jlif_key_child_fwdlink"
+};
+
#define CALL_OR_STOP(routine) !(routine) ? jlif_stop : (routine)
static int dbjl_return(parseContext *parser, jlif_result result) {
@@ -45,8 +59,8 @@ static int dbjl_return(parseContext *parser, jlif_result result) {
IFDEBUG(10) {
printf("dbjl_return(%s@%p, %d)\t", pjlink ? pjlink->pif->name : "", pjlink, result);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->dbfType);
}
if (result == jlif_stop && pjlink) {
@@ -59,6 +73,9 @@ static int dbjl_return(parseContext *parser, jlif_result result) {
pjlink->pif->free_jlink(pjlink);
}
+ IFDEBUG(10)
+ printf(" returning %d %s\n", result,
+ result == jlif_stop ? "*** STOP ***" : "Continue");
return result;
}
@@ -68,8 +85,8 @@ static int dbjl_value(parseContext *parser, jlif_result result) {
IFDEBUG(10) {
printf("dbjl_value(%s@%p, %d)\t", pjlink ? pjlink->pif->name : "", pjlink, result);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->dbfType);
}
if (result == jlif_stop || pjlink->parseDepth > 0)
@@ -81,7 +98,6 @@ static int dbjl_value(parseContext *parser, jlif_result result) {
} else if (parent->pif->end_child) {
parent->pif->end_child(parent, pjlink);
}
- pjlink->debug = 0;
parser->pjlink = parent;
@@ -159,29 +175,46 @@ static int dbjl_start_map(void *ctx) {
if (!pjlink) {
IFDEBUG(10) {
printf("dbjl_start_map(NULL)\t");
- printf(" jsonDepth=%d, parseDepth=00, key_is_link=%d\n",
- parser->jsonDepth, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=00, dbfType=%d\n",
+ parser->jsonDepth, parser->dbfType);
}
assert(parser->jsonDepth == 0);
parser->jsonDepth++;
- parser->key_is_link = 1;
return jlif_continue; /* Opening '{' */
}
IFDEBUG(10) {
printf("dbjl_start_map(%s@%p)\t", pjlink ? pjlink->pif->name : "", pjlink);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->dbfType);
}
pjlink->parseDepth++;
parser->jsonDepth++;
result = CALL_OR_STOP(pjlink->pif->parse_start_map)(pjlink);
- if (result == jlif_key_child_link) {
- parser->key_is_link = 1;
+ switch (result) {
+ case jlif_key_child_inlink:
+ parser->dbfType = DBF_INLINK;
result = jlif_continue;
+ break;
+ case jlif_key_child_outlink:
+ parser->dbfType = DBF_OUTLINK;
+ result = jlif_continue;
+ break;
+ case jlif_key_child_fwdlink:
+ parser->dbfType = DBF_FWDLINK;
+ result = jlif_continue;
+ break;
+ case jlif_key_stop:
+ case jlif_key_continue:
+ break;
+ default:
+ errlogPrintf("dbJLinkInit: Bad return %d from '%s'::parse_start_map()\n",
+ result, pjlink->pif->name);
+ result = jlif_stop;
+ break;
}
IFDEBUG(10)
@@ -196,8 +229,9 @@ static int dbjl_map_key(void *ctx, const unsigned char *key, size_t len) {
char *link_name;
linkSup *linkSup;
jlif *pjlif;
+ jlink *child;
- if (!parser->key_is_link) {
+ if (parser->dbfType == 0) {
if (!pjlink) {
errlogPrintf("dbJLinkInit: Illegal second link key '%.*s'\n",
(int) len, key);
@@ -207,8 +241,8 @@ static int dbjl_map_key(void *ctx, const unsigned char *key, size_t len) {
IFDEBUG(10) {
printf("dbjl_map_key(%s@%p, \"%.*s\")\t",
pjlink->pif->name, pjlink, (int) len, key);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->dbfType);
}
assert(pjlink->parseDepth > 0);
@@ -219,8 +253,8 @@ static int dbjl_map_key(void *ctx, const unsigned char *key, size_t len) {
IFDEBUG(10) {
printf("dbjl_map_key(NULL, \"%.*s\")\t", (int) len, key);
- printf(" jsonDepth=%d, parseDepth=00, key_is_link=%d\n",
- parser->jsonDepth, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=00, dbfType=%d\n",
+ parser->jsonDepth, parser->dbfType);
}
link_name = dbmfStrndup((const char *) key, len);
@@ -241,27 +275,35 @@ static int dbjl_map_key(void *ctx, const unsigned char *key, size_t len) {
return dbjl_return(parser, jlif_stop);
}
- dbmfFree(link_name);
-
- pjlink = pjlif->alloc_jlink(parser->dbfType);
- if (!pjlink) {
- errlogPrintf("dbJLinkInit: Out of memory\n");
+ child = pjlif->alloc_jlink(parser->dbfType);
+ if (!child) {
+ errlogPrintf("dbJLinkInit: Link type '%s' allocation failed. \n",
+ link_name);
+ dbmfFree(link_name);
return dbjl_return(parser, jlif_stop);
}
- pjlink->pif = pjlif;
- pjlink->parent = NULL;
- pjlink->parseDepth = 0;
- pjlink->debug = !!parser->lset_debug;
+
+ child->pif = pjlif;
+ child->parseDepth = 0;
+ child->debug = 0;
if (parser->pjlink) {
/* We're starting a child link, save its parent */
- pjlink->parent = parser->pjlink;
+ child->parent = pjlink;
+
+ if (pjlink->pif->start_child)
+ pjlink->pif->start_child(pjlink, child);
}
- parser->pjlink = pjlink;
- parser->key_is_link = 0;
+ else
+ child->parent = NULL;
+
+ parser->pjlink = child;
+ parser->dbfType = 0;
+
+ dbmfFree(link_name);
IFDEBUG(8)
- printf("dbjl_map_key: New %s@%p\n", pjlink ? pjlink->pif->name : "", pjlink);
+ printf("dbjl_map_key: New %s@%p\n", child ? child->pif->name : "", child);
return jlif_continue;
}
@@ -274,9 +316,9 @@ static int dbjl_end_map(void *ctx) {
IFDEBUG(10) {
printf("dbjl_end_map(%s@%p)\t",
pjlink ? pjlink->pif->name : "NULL", pjlink);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
parser->jsonDepth, pjlink ? pjlink->parseDepth : 0,
- parser->key_is_link);
+ parser->dbfType);
}
parser->jsonDepth--;
@@ -298,8 +340,8 @@ static int dbjl_start_array(void *ctx) {
IFDEBUG(10) {
printf("dbjl_start_array(%s@%p)\t", pjlink ? pjlink->pif->name : "", pjlink);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->dbfType);
}
assert(pjlink);
@@ -316,8 +358,8 @@ static int dbjl_end_array(void *ctx) {
IFDEBUG(10) {
printf("dbjl_end_array(%s@%p)\t", pjlink ? pjlink->pif->name : "", pjlink);
- printf(" jsonDepth=%d, parseDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->key_is_link);
+ printf(" jsonDepth=%d, parseDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, pjlink ? pjlink->parseDepth : 0, parser->dbfType);
}
assert(pjlink);
@@ -335,7 +377,7 @@ static yajl_callbacks dbjl_callbacks = {
};
long dbJLinkParse(const char *json, size_t jlen, short dbfType,
- jlink **ppjlink, unsigned opts)
+ jlink **ppjlink)
{
parseContext context, *parser = &context;
yajl_alloc_funcs dbjl_allocs;
@@ -347,17 +389,14 @@ long dbJLinkParse(const char *json, size_t jlen, short dbfType,
parser->product = NULL;
parser->dbfType = dbfType;
parser->jsonDepth = 0;
- parser->key_is_link = 0;
- parser->parse_debug = !!(opts&LINK_DEBUG_JPARSE);
- parser->lset_debug = !!(opts&LINK_DEBUG_LSET);
IFDEBUG(10)
printf("dbJLinkInit(\"%.*s\", %d, %p)\n",
(int) jlen, json, dbfType, ppjlink);
IFDEBUG(10)
- printf("dbJLinkInit: jsonDepth=%d, key_is_link=%d\n",
- parser->jsonDepth, parser->key_is_link);
+ printf("dbJLinkInit: jsonDepth=%d, dbfType=%d\n",
+ parser->jsonDepth, parser->dbfType);
yajl_set_default_alloc_funcs(&dbjl_allocs);
yh = yajl_alloc(&dbjl_callbacks, &dbjl_allocs, parser);
@@ -365,8 +404,14 @@ long dbJLinkParse(const char *json, size_t jlen, short dbfType,
return S_db_noMemory;
ys = yajl_parse(yh, (const unsigned char *) json, jlen);
- if (ys == yajl_status_ok)
+ IFDEBUG(10)
+ printf("dbJLinkInit: yajl_parse() returned %d\n", ys);
+
+ if (ys == yajl_status_ok) {
ys = yajl_complete_parse(yh);
+ IFDEBUG(10)
+ printf("dbJLinkInit: yajl_complete_parse() returned %d\n", ys);
+ }
switch (ys) {
unsigned char *err;
@@ -378,6 +423,9 @@ long dbJLinkParse(const char *json, size_t jlen, short dbfType,
break;
case yajl_status_error:
+ IFDEBUG(10)
+ printf(" jsonDepth=%d, product=%p, pjlink=%p\n",
+ parser->jsonDepth, parser->product, parser->pjlink);
err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen);
errlogPrintf("dbJLinkInit: %s\n", err);
yajl_free_error(yh, err);
@@ -389,18 +437,24 @@ long dbJLinkParse(const char *json, size_t jlen, short dbfType,
}
yajl_free(yh);
+
+ IFDEBUG(10)
+ printf("dbJLinkInit: returning status=0x%lx\n\n",
+ status);
+
return status;
}
long dbJLinkInit(struct link *plink)
{
- jlink *pjlink;
-
assert(plink);
- pjlink = plink->value.json.jlink;
- if (pjlink)
- plink->lset = pjlink->pif->get_lset(pjlink);
+ if (plink->type == JSON_LINK) {
+ jlink *pjlink = plink->value.json.jlink;
+
+ if (pjlink)
+ plink->lset = pjlink->pif->get_lset(pjlink);
+ }
dbLinkOpen(plink);
return 0;
diff --git a/src/ioc/db/dbJLink.h b/src/ioc/db/dbJLink.h
index 61b59670b..bd1a6c8a2 100644
--- a/src/ioc/db/dbJLink.h
+++ b/src/ioc/db/dbJLink.h
@@ -2,7 +2,7 @@
* 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.
+* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* dbJLink.h */
@@ -21,12 +21,16 @@ typedef enum {
jlif_continue = 1
} jlif_result;
+epicsShareExtern const char *jlif_result_name[2];
+
typedef enum {
jlif_key_stop = jlif_stop,
jlif_key_continue = jlif_continue,
- jlif_key_child_link
+ jlif_key_child_inlink, jlif_key_child_outlink, jlif_key_child_fwdlink
} jlif_key_result;
+epicsShareExtern const char *jlif_key_result_name[5];
+
struct link;
struct lset;
struct jlif;
@@ -35,7 +39,7 @@ typedef struct jlink {
struct jlif *pif; /* Link methods */
struct jlink *parent; /* NULL for top-level links */
int parseDepth; /* Used by parser, unused afterwards */
- unsigned debug:1; /* set by caller of jlif operations to request debug output to console */
+ unsigned debug:1; /* Set to request debug output to console */
/* Link types extend or embed this structure for private storage */
} jlink;
@@ -72,8 +76,9 @@ typedef struct jlif {
/* Optional, parser saw a string value */
jlif_key_result (*parse_start_map)(jlink *);
- /* Optional, parser saw an open-brace '{'. Return jlif_key_child_link
- * to expect a child link next (extra key/value pairs may follow).
+ /* Optional, parser saw an open-brace '{'. Return jlif_key_child_inlink,
+ * jlif_key_child_outlink, or jlif_key_child_fwdlink to expect a child
+ * link next (extra key/value pairs may follow)
*/
jlif_result (*parse_map_key)(jlink *, const char *key, size_t len);
@@ -90,7 +95,8 @@ typedef struct jlif {
void (*end_child)(jlink *parent, jlink *child);
/* Optional, called with pointer to the new child link after
- * parse_start_map() returned jlif_key_child_link */
+ * the child link has finished parsing successfully
+ */
struct lset* (*get_lset)(const jlink *);
/* Required, return lset for this link instance */
@@ -98,7 +104,7 @@ typedef struct jlif {
void (*report)(const jlink *, int level, int indent);
/* Optional, print status information about this link instance, then
* if (level > 0) print a link identifier (at indent+2) and call
- * dbJLinkReport(child, level-1, indent+4)
+ * dbJLinkReport(child, level-1, indent+4)
* for each child.
*/
@@ -107,13 +113,19 @@ typedef struct jlif {
* Stop immediately and return status if non-zero.
*/
+ void (*start_child)(jlink *parent, jlink *child);
+ /* Optional, called with pointer to the new child link after
+ * parse_start_map() returned a jlif_key_child_link value and
+ * the child link has been allocated (but not parsed yet)
+ */
+
/* Link types must NOT extend this table with their own routines,
* this space is reserved for extensions to the jlink interface.
*/
} jlif;
epicsShareFunc long dbJLinkParse(const char *json, size_t len, short dbfType,
- jlink **ppjlink, unsigned opts);
+ jlink **ppjlink);
epicsShareFunc long dbJLinkInit(struct link *plink);
epicsShareFunc void dbJLinkFree(jlink *);
@@ -130,4 +142,3 @@ epicsShareFunc long dbJLinkMapAll(char *recname, jlink_map_fn rtn, void *ctx);
#endif
#endif /* INC_dbJLink_H */
-
diff --git a/src/ioc/db/dbLink.c b/src/ioc/db/dbLink.c
index f87a3eb09..dfde00e63 100644
--- a/src/ioc/db/dbLink.c
+++ b/src/ioc/db/dbLink.c
@@ -265,8 +265,19 @@ int dbIsLinkConnected(const struct link *plink)
{
lset *plset = plink->lset;
- if (!plset || !plset->isConnected)
+ if (!plset)
return FALSE;
+ if (!plset->isVolatile)
+ return TRUE;
+
+ if (!plset->isConnected) {
+ struct dbCommon *precord = plink->precord;
+
+ errlogPrintf("dbLink: Link type for '%s.%s' is volatile but has no"
+ " lset::isConnected() method\n",
+ precord->name, dbLinkFieldName(plink));
+ return FALSE;
+ }
return plset->isConnected(plink);
}
diff --git a/src/ioc/db/dbLink.h b/src/ioc/db/dbLink.h
index 1fa80411a..ffeebd486 100644
--- a/src/ioc/db/dbLink.h
+++ b/src/ioc/db/dbLink.h
@@ -27,54 +27,337 @@ extern "C" {
struct dbLocker;
+/** @file dbLink.h
+ * @brief Link Support API
+ *
+ * Link support run-time API, all link types provide an lset which is used by
+ * the IOC database to control and operate the link. This file also declares the
+ * dbLink routines that IOC, record and device code can call to perform link
+ * operations.
+ */
+
+/** @brief callback routine for locked link operations
+ *
+ * Called by the lset::doLocked method to permit multiple link operations
+ * while the link instance is locked.
+ *
+ * @param plink the link
+ * @param priv context for the callback routine
+ */
typedef long (*dbLinkUserCallback)(struct link *plink, void *priv);
+/** @brief Link Support Entry Table
+ *
+ * This structure provides information about and methods for an individual link
+ * type. A pointer to this structure is included in every link's lset field, and
+ * is used to perform operations on the link. For JSON links the pointer is
+ * obtained by calling pjlink->pif->get_lset() at link initialization time,
+ * immediately before calling dbLinkOpen() to activate the link.
+ */
typedef struct lset {
/* Characteristics of the link type */
+
+ /** @brief link constancy
+ *
+ * 1 means this is a constant link type whose value doesn't change.
+ * The link's value will be obtained using one of the methods loadScalar,
+ * loadLS or loadArray.
+ */
const unsigned isConstant:1;
+
+ /** @brief link volatility
+ *
+ * 0 means the link is always connected.
+ */
const unsigned isVolatile:1;
- /* Activation */
+ /** @brief activate link
+ *
+ * Optional, called whenever a JSON link is initialized or added at runtime.
+ *
+ * @param plink the link
+ */
void (*openLink)(struct link *plink);
- /* Destructor */
+ /** @brief deactivate link
+ *
+ * Optional, called whenever a link address is changed at runtime, or the
+ * IOC is shutting down.
+ *
+ * @param locker
+ * @param plink the link
+ */
void (*removeLink)(struct dbLocker *locker, struct link *plink);
- /* Const init, data type hinting */
+ /* Constant link initialization and data type hinting */
+
+ /** @brief load constant scalar from link type
+ *
+ * Usually called during IOC initialization, constant link types must copy a
+ * scalar value of the indicated data type to the buffer provided and return
+ * 0. A non-constant link type can use this method call as an early hint
+ * that subsequent calls to dbGetLink() will request scalar data of the
+ * indicated type, although the type might change.
+ *
+ * @param plink the link
+ * @param dbrType data type code
+ * @param pbuffer where to put the value
+ * @returns 0 if a value was loaded, non-zero otherwise
+ */
long (*loadScalar)(struct link *plink, short dbrType, void *pbuffer);
+
+ /** @brief load constant long string from link type
+ *
+ * Usually called during IOC initialization, constant link types must copy a
+ * nil-terminated string up to size characters long to the buffer provided,
+ * and write the length of that string to the plen location. A non-constant
+ * link type can use this as an early hint that subsequent calls to
+ * dbGetLink() will request long string data, although this might change.
+ *
+ * @param plink the link
+ * @param pbuffer where to put the string
+ * @param size length of pbuffer in chars
+ * @param plen set to number of chars written
+ * @returns status value
+ */
long (*loadLS)(struct link *plink, char *pbuffer, epicsUInt32 size,
epicsUInt32 *plen);
+
+ /** @brief load constant array from link type
+ *
+ * Usually called during IOC initialization, constant link types must copy
+ * an array value of the indicated data type to the buffer provided, update
+ * the pnRequest location to indicate how many elements were loaded, and
+ * return 0. A non-constant link type can use this method call as an early
+ * hint that subsequent calls to dbGetLink() will request array data of the
+ * indicated type and max size, although the request might change.
+ *
+ * @param plink the link
+ * @param dbrType data type code
+ * @param pbuffer where to put the value
+ * @param pnRequest Max elements on entry, actual on exit
+ * @returns 0 if elements were loaded, non-zero otherwise
+ */
long (*loadArray)(struct link *plink, short dbrType, void *pbuffer,
long *pnRequest);
/* Metadata */
+
+ /** @brief return link connection status
+ *
+ * Return an indication whether this link is connected or not. This routine
+ * is polled by the calcout and some external record types. Not required for
+ * non-volatile link types, which are by definition always connected.
+ *
+ * @param plink the link
+ * @returns 1 if connected, 0 if disconnected
+ */
int (*isConnected)(const struct link *plink);
+
+ /** @brief get data type of link destination
+ *
+ * Called on both input and output links by long string support code to
+ * decide whether to use DBR_CHAR/DBR_UCHAR or DBR_STRING for a subsequent
+ * dbPutLink() or dbGetLink() call. Optional, but if not provided long
+ * strings cannot be transported over this link type, and no warning or
+ * error will appear to explain why. Not required for constant link types.
+ *
+ * @param plink the link
+ * @returns DBF_* type code, or -1 on error/disconnected link
+ */
int (*getDBFtype)(const struct link *plink);
- long (*getElements)(const struct link *plink, long *nelements);
/* Get data */
+
+ /** @brief get array size of an input link
+ *
+ * Called on input links by the compress record type for memory allocation
+ * purposes, before using the dbGetLink() routine to fetch the actual
+ * array data.
+ *
+ * @param plink the link
+ * @param pnElements where to put the answer
+ * @returns status value
+ */
+ long (*getElements)(const struct link *plink, long *pnElements);
+
+ /** @brief get value from an input link
+ *
+ * Called to fetch data from the link, which must be converted into the
+ * given data type and placed in the buffer indicated. The actual number of
+ * elements retrieved should be updated in the pnRequest location. If this
+ * method returns an error status value, the link's record will be placed
+ * into an Invalid severity / Link Alarm state by the dbGetLink() routine
+ * that calls this method.
+ *
+ * @param plink the link
+ * @param dbrType data type code
+ * @param pbuffer where to put the value
+ * @param pnRequest max elements on entry, actual on exit
+ * @returns status value
+ */
long (*getValue)(struct link *plink, short dbrType, void *pbuffer,
long *pnRequest);
+
+ /** @brief get the control range for an output link
+ *
+ * Called to fetch the control range for the link target, as a pair of
+ * double values for the lowest and highest values that the target will
+ * accept. This method is not used at all by the IOC or built-in record
+ * types, although external record types may require it.
+ *
+ * @param plink the link
+ * @param lo lowest accepted value
+ * @param hi highest accepted value
+ * @returns status value
+ */
long (*getControlLimits)(const struct link *plink, double *lo, double *hi);
+
+ /** @brief get the display range from an input link
+ *
+ * Called to fetch the display range for an input link target, as a pair of
+ * double values for the lowest and highest values that the PV expects to
+ * return. This method is used by several built-in record types to obtain
+ * the display range for their generic input links.
+ *
+ * @param plink the link
+ * @param lo lowest accepted value
+ * @param hi highest accepted value
+ * @returns status value
+ */
long (*getGraphicLimits)(const struct link *plink, double *lo, double *hi);
+
+ /** @brief get the alarm limits from an input link
+ *
+ * Called to fetch the alarm limits for an input link target, as four
+ * double values for the warning and alarm levels that the PV checks its
+ * value against. This method is used by several built-in record types to
+ * obtain the alarm limits for their generic input links.
+ *
+ * @param plink the link
+ * @param lolo low alarm value
+ * @param lo low warning value
+ * @param hi high warning value
+ * @param hihi high alarm value
+ * @returns status value
+ */
long (*getAlarmLimits)(const struct link *plink, double *lolo, double *lo,
double *hi, double *hihi);
+
+ /** @brief get the precision from an input link
+ *
+ * Called to fetch the precision for an input link target. This method is
+ * used by several built-in record types to obtain the precision for their
+ * generic input links.
+ *
+ * @param plink the link
+ * @param precision where to put the answer
+ * @returns status value
+ */
long (*getPrecision)(const struct link *plink, short *precision);
+
+ /** @brief get the units string from an input link
+ *
+ * Called to fetch the units string for an input link target. This method is
+ * used by several built-in record types to obtain the units string for
+ * their generic input links.
+ *
+ * @param plink the link
+ * @param units where to put the answer
+ * @param unitsSize buffer size for the answer
+ * @returns status value
+ */
long (*getUnits)(const struct link *plink, char *units, int unitsSize);
+
+ /** @brief get the alarm condition from an input link
+ *
+ * Called to fetch the alarm status and severity for an input link target.
+ * Either status or severity pointers may be NULL when that value is not
+ * needed by the calling code. This method is used by several built-in
+ * record types to obtain the alarm condition for their generic input links.
+ *
+ * @param plink the link
+ * @param status where to put the alarm status (or NULL)
+ * @param severity where to put the severity (or NULL)
+ * @returns status value
+ */
long (*getAlarm)(const struct link *plink, epicsEnum16 *status,
epicsEnum16 *severity);
+
+ /** @brief get the time-stamp from an input link
+ *
+ * Called to fetch the time-stamp for an input link target. This method is
+ * used by many built-in device supports to obtain the precision for their
+ * generic input links.
+ *
+ * @param plink the link
+ * @param pstamp where to put the answer
+ * @returns status value
+ */
long (*getTimeStamp)(const struct link *plink, epicsTimeStamp *pstamp);
/* Put data */
+
+ /** @brief put a value to an output link
+ *
+ * Called to send nRequest elements of type dbrType found at pbuffer to an
+ * output link target.
+ *
+ * @param plink the link
+ * @param dbrType data type code
+ * @param pbuffer where to put the value
+ * @param nRequest number of elements to send
+ * @returns status value
+ */
long (*putValue)(struct link *plink, short dbrType,
const void *pbuffer, long nRequest);
+
+ /** @brief put a value to an output link with asynchronous completion
+ *
+ * Called to send nRequest elements of type dbrType found at pbuffer to an
+ * output link target. If the return status is zero, the link type will
+ * later indicate the put has completed by calling dbLinkAsyncComplete()
+ * from a background thread, which will be used to continue the record
+ * process operation from where it left off.
+ *
+ * @param plink the link
+ * @param dbrType data type code
+ * @param pbuffer where to put the value
+ * @param nRequest number of elements to send
+ * @returns status value
+ */
long (*putAsync)(struct link *plink, short dbrType,
const void *pbuffer, long nRequest);
/* Process */
+
+ /** @brief trigger processing of a forward link
+ *
+ * Called to trigger processing of the record pointed to by a forward link.
+ * This routine is optional, but if not provided no warning message will be
+ * shown when called by dbScanFwdLink(). JSON link types that do not support
+ * this operation should return NULL from their jlif::alloc_jlink() method
+ * if it gets called with a dbfType of DBF_FWDLINK.
+ *
+ * @param plink the link
+ */
void (*scanForward)(struct link *plink);
/* Atomicity */
+
+ /** @brief execute a callback routine with link locked
+ *
+ * Called on an input link when multiple link attributes need to be fetched
+ * in an atomic fashion. The link type must call the callback routine and
+ * prevent any background I/O from updating any cached link data until that
+ * routine returns. This method is used by most input device support to
+ * fetch the timestamp along with the value when the record's TSE field is
+ * set to epicsTimeEventDeviceTime.
+ *
+ * @param plink the link
+ * @param rtn routine to execute
+ * @returns status value
+ */
long (*doLocked)(struct link *plink, dbLinkUserCallback rtn, void *priv);
} lset;
@@ -99,9 +382,10 @@ epicsShareFunc long dbLoadLink(struct link *plink, short dbrType,
epicsShareFunc long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer,
long *pnRequest);
-epicsShareFunc long dbGetNelements(const struct link *plink, long *nelements);
+epicsShareFunc long dbGetNelements(const struct link *plink, long *pnElements);
epicsShareFunc int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */
epicsShareFunc int dbGetLinkDBFtype(const struct link *plink);
+
epicsShareFunc long dbGetLink(struct link *, short dbrType, void *pbuffer,
long *options, long *nRequest);
epicsShareFunc long dbGetControlLimits(const struct link *plink, double *low,
diff --git a/src/ioc/db/dbState.h b/src/ioc/db/dbState.h
index abd23259e..c7cd81c52 100644
--- a/src/ioc/db/dbState.h
+++ b/src/ioc/db/dbState.h
@@ -15,6 +15,10 @@
#include "shareLib.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/** @file dbState.h
* @brief Generic IOC state facility
*
@@ -89,4 +93,9 @@ epicsShareFunc void dbStateShow(dbStateId id, unsigned int level);
*/
epicsShareFunc void dbStateShowAll(unsigned int level);
+
+#ifdef __cplusplus
+}
+#endif
+
#endif // INCdbStateH
diff --git a/src/ioc/db/test/dbPutLinkTest.c b/src/ioc/db/test/dbPutLinkTest.c
index bf7cd7ba0..179b913ce 100644
--- a/src/ioc/db/test/dbPutLinkTest.c
+++ b/src/ioc/db/test/dbPutLinkTest.c
@@ -89,7 +89,7 @@ static void testLinkParse(void)
for (;td->str; td++) {
int i, N;
testDiag("Parsing \"%s\"", td->str);
- testOk(dbParseLink(td->str, DBF_INLINK, &info, 0) == 0, "Parser returned OK");
+ testOk(dbParseLink(td->str, DBF_INLINK, &info) == 0, "Parser returned OK");
if (!testOk(info.ltype == td->info.ltype, "Link type value"))
testDiag("Expected %d, got %d", td->info.ltype, info.ltype);
if (td->info.target && info.target)
@@ -152,7 +152,7 @@ static void testLinkFailParse(void)
eltc(1);
for(;*td; td++) {
- testOk(dbParseLink(*td, DBF_INLINK, &info, 0) == S_dbLib_badField,
+ testOk(dbParseLink(*td, DBF_INLINK, &info) == S_dbLib_badField,
"dbParseLink correctly rejected \"%s\"", *td);
}
diff --git a/src/ioc/db/test/jlinkz.c b/src/ioc/db/test/jlinkz.c
index dd6919ffb..fc98ff3b3 100644
--- a/src/ioc/db/test/jlinkz.c
+++ b/src/ioc/db/test/jlinkz.c
@@ -246,7 +246,8 @@ static jlif jlifZ = {
NULL, /* end child */
&z_lset,
NULL, /* report */
- NULL /* map child */
+ NULL, /* map child */
+ NULL /* start child */
};
epicsExportAddress(jlif, jlifZ);
diff --git a/src/ioc/db/test/xLink.c b/src/ioc/db/test/xLink.c
index 12175f44e..2d7c3c043 100644
--- a/src/ioc/db/test/xLink.c
+++ b/src/ioc/db/test/xLink.c
@@ -2,7 +2,7 @@
* 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.
+* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* xLink.c */
@@ -82,7 +82,6 @@ static jlif xlinkIf = {
NULL, NULL, NULL,
NULL, NULL,
NULL, xlink_get_lset,
- NULL, NULL
+ NULL, NULL, NULL
};
epicsExportAddress(jlif, xlinkIf);
-
diff --git a/src/ioc/dbStatic/dbStaticLib.c b/src/ioc/dbStatic/dbStaticLib.c
index c67e849c7..e639e3c48 100644
--- a/src/ioc/dbStatic/dbStaticLib.c
+++ b/src/ioc/dbStatic/dbStaticLib.c
@@ -2241,7 +2241,7 @@ long dbInitRecordLinks(dbRecordType *rtyp, struct dbCommon *prec)
if(!plink->text)
continue;
- if(dbParseLink(plink->text, pflddes->field_type, &link_info, 0)!=0) {
+ if(dbParseLink(plink->text, pflddes->field_type, &link_info)!=0) {
/* This was already parsed once when ->text was set.
* Any syntax error messages were printed at that time.
*/
@@ -2270,7 +2270,7 @@ void dbFreeLinkInfo(dbLinkInfo *pinfo)
pinfo->target = NULL;
}
-long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo, unsigned opts)
+long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo)
{
char *pstr;
size_t len;
@@ -2306,7 +2306,7 @@ long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo, unsigned opts)
/* Check for braces => JSON */
if (*str == '{' && str[len-1] == '}') {
- if (dbJLinkParse(str, len, ftype, &pinfo->jlink, opts))
+ if (dbJLinkParse(str, len, ftype, &pinfo->jlink))
goto fail;
pinfo->ltype = JSON_LINK;
@@ -2645,21 +2645,8 @@ long dbPutString(DBENTRY *pdbentry,const char *pstring)
case DBF_FWDLINK: {
dbLinkInfo link_info;
DBLINK *plink = (DBLINK *)pfield;
- DBENTRY infoentry;
- unsigned opts = 0;
- if(pdbentry->precnode && ellCount(&pdbentry->precnode->infoList)) {
- dbCopyEntryContents(pdbentry, &infoentry);
-
- if(dbFindInfo(&infoentry, "base:lsetDebug")==0 && epicsStrCaseCmp(dbGetInfoString(&infoentry), "YES")==0)
- opts |= LINK_DEBUG_LSET;
- if(dbFindInfo(&infoentry, "base:jlinkDebug")==0 && epicsStrCaseCmp(dbGetInfoString(&infoentry), "YES")==0)
- opts |= LINK_DEBUG_JPARSE;
-
- dbFinishEntry(&infoentry);
- }
-
- status = dbParseLink(pstring, pflddes->field_type, &link_info, opts);
+ status = dbParseLink(pstring, pflddes->field_type, &link_info);
if (status) break;
if (plink->type==CONSTANT && plink->value.constantStr==NULL) {
diff --git a/src/ioc/dbStatic/dbStaticPvt.h b/src/ioc/dbStatic/dbStaticPvt.h
index 58d32c28c..85fc02217 100644
--- a/src/ioc/dbStatic/dbStaticPvt.h
+++ b/src/ioc/dbStatic/dbStaticPvt.h
@@ -5,7 +5,7 @@
* Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
-* in file LICENSE that is included with this distribution.
+* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* dbStaticPvt.h */
/*
@@ -59,13 +59,10 @@ typedef struct dbLinkInfo {
long dbInitRecordLinks(dbRecordType *rtyp, struct dbCommon *prec);
-#define LINK_DEBUG_LSET 1
-#define LINK_DEBUG_JPARSE 2
-
/* Parse link string. no record locks needed.
* on success caller must free pinfo->target
*/
-epicsShareFunc long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo, unsigned opts);
+epicsShareFunc long dbParseLink(const char *str, short ftype, dbLinkInfo *pinfo);
/* Check if link type allow the parsed link value pinfo
* to be assigned to the given link.
* Record containing plink must be locked.
diff --git a/src/ioc/misc/dbCore.dbd b/src/ioc/misc/dbCore.dbd
index 5f970f12d..51f9c9640 100644
--- a/src/ioc/misc/dbCore.dbd
+++ b/src/ioc/misc/dbCore.dbd
@@ -12,6 +12,9 @@ variable(asCaDebug,int)
# CA server debug flag (very verbose) range[0,5]
variable(CASDEBUG,int)
+# Link parsing debug
+variable(dbJLinkDebug,int)
+
# Static database access variables
variable(dbRecordsOnceOnly,int)
variable(dbRecordsAbcSorted,int)
diff --git a/src/std/link/Makefile b/src/std/link/Makefile
index 31d14b825..9b6abd705 100644
--- a/src/std/link/Makefile
+++ b/src/std/link/Makefile
@@ -2,7 +2,7 @@
# 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.
+# in file LICENSE that is included with this distribution.
#*************************************************************************
# This is a Makefile fragment, see src/std/Makefile.
@@ -13,6 +13,7 @@ DBD += links.dbd
dbRecStd_SRCS += lnkConst.c
dbRecStd_SRCS += lnkCalc.c
+dbRecStd_SRCS += lnkState.c
+dbRecStd_SRCS += lnkDebug.c
HTMLS += links.html
-
diff --git a/src/std/link/links.dbd.pod b/src/std/link/links.dbd.pod
index d94e9e8f0..b5ed582f2 100644
--- a/src/std/link/links.dbd.pod
+++ b/src/std/link/links.dbd.pod
@@ -13,6 +13,12 @@ The following additional link types are available in this release:
=item * L
+=item * L
+
+=item * L
+
+=item * L
+
=back
=head2 Using JSON Links
@@ -31,15 +37,16 @@ database file syntax.
=cut
+
link(const, lnkConstIf)
=head3 Constant Link C<"const">
-Constant links provide one or more values at link initalization time, but do not
-return any data when their C routine is called. Most record types
-support the use of constant links by calling C at
-record initialization, which results in the constant value being loaded into the
-target field at that time.
+Constant links are input links that provide literal values at link initalization
+time, but do not return any data when their C routine is called.
+Most record types support the use of constant links on their input links by
+calling C at record initialization, which results in
+the constant value being loaded into the target field at that time.
Note that for most record types (the C and C records are the
main exceptions) it is pointless to set an input link to a constant link at
@@ -70,14 +77,35 @@ converted to the desired double value at initialization, for example:
=cut
+
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 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
@@ -88,6 +116,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
@@ -104,6 +133,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
@@ -129,3 +164,72 @@ atomically with the value of the input argument.
{calc: {expr:"A*B", args:[{db:"record.VAL"}, 1.5], prec:3}}
=cut
+
+
+link(state, lnkStateIf)
+
+=head3 dbState Link C<"state">
+
+A dbState link is one that gets or puts a boolean value from/to a named global
+flag as implemented by the dbState facility in C. The link type can
+invert the sense of the dbState flag during the get or put if desired.
+
+The value of the named flag is read or written at the time of the link I/O
+operation. When reading a flag, the value returned by the link will be zero or
+one converted to the requested data type. When writing to a flag the boolean
+value of the data written is determined in the originating data type. All
+strings are regarded as true other than C<""> and C<"0"> which are both false.
+
+A link can be configured to invert the sense of the flag data by putting an
+exclamation mark C before the first character of the flag's name in the link
+address.
+
+These dbState flags can be accessed from the IOC Shell with various dbState
+commands, and are also used by the C<"sync"> Channel-Access server-side filter
+mechanism.
+
+=head4 Parameters
+
+The link takes a single parameter which must be a string, providing the name of
+the dbState object, with an optional leading C character to indicate that the
+flag's value should be inverted. The dbState object will be created when the
+link is initialized if it doesn't already exist.
+
+=head4 Examples
+
+ {state:"redBeam"}
+ {state:"!simEnable"}
+
+=cut
+
+
+link(debug, lnkDebugIf)
+variable(lnkDebug_debug, int)
+
+=head3 Debug Link C<"debug">
+
+The debug link type exists to enable debugging of other link types; it provides
+no functionality itself other than to turn on the debug flag for the child link
+that is its only parameter and pass all link operations down to that link.
+
+=head4 Example
+
+ {debug:{state:"redBeam"}}
+
+=cut
+
+
+link(trace, lnkTraceIf)
+
+=head3 Trace Link C<"trace">
+
+The trace link type is a relative of the debug link type that also traces the
+operation of its child link. At creation it turns on the debug flag of its child
+link, then it prints the method arguments and return values of all link
+operations before / after passing control down to the child link.
+
+=head4 Example
+
+ {trace:{state:"redBeam"}}
+
+=cut
diff --git a/src/std/link/lnkCalc.c b/src/std/link/lnkCalc.c
index e370c36d2..2ac568840 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
@@ -26,6 +22,7 @@
#include "epicsAssert.h"
#include "epicsString.h"
#include "epicsTypes.h"
+#include "epicsTime.h"
#include "dbAccessDefs.h"
#include "dbCommon.h"
#include "dbConvertFast.h"
@@ -40,15 +37,14 @@
typedef long (*FASTCONVERT)();
-#define IFDEBUG(n) if(clink->jlink.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,
@@ -66,7 +62,9 @@ typedef struct calc_link {
char *units;
short tinp;
struct link inp[CALCPERFORM_NARGS];
+ struct link out;
double arg[CALCPERFORM_NARGS];
+ epicsTimeStamp time;
double val;
} calc_link;
@@ -77,19 +75,25 @@ static lset lnkCalc_lset;
static jlink* lnkCalc_alloc(short dbfType)
{
- calc_link *clink = calloc(1, sizeof(struct calc_link));
+ calc_link *clink;
- IFDEBUG(10)
- printf("lnkCalc_alloc()\n");
+ 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;
- IFDEBUG(10)
- printf("lnkCalc_alloc -> calc@%p\n", clink);
-
return &clink->jlink;
}
@@ -98,12 +102,11 @@ 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", clink);
-
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);
@@ -118,9 +121,6 @@ static jlif_result lnkCalc_integer(jlink *pjlink, long long num)
{
calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink);
- IFDEBUG(10)
- printf("lnkCalc_integer(calc@%p, %lld)\n", clink, num);
-
if (clink->pstate == ps_prec) {
clink->prec = num;
return jlif_continue;
@@ -146,9 +146,6 @@ 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", clink, num);
-
if (clink->pstate != ps_args) {
return jlif_stop;
errlogPrintf("lnkCalc: Unexpected double %g\n", num);
@@ -171,9 +168,6 @@ static jlif_result lnkCalc_string(jlink *pjlink, const char *val, size_t len)
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;
@@ -234,11 +228,10 @@ static jlif_key_result lnkCalc_start_map(jlink *pjlink)
{
calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink);
- IFDEBUG(10)
- printf("lnkCalc_start_map(calc@%p)\n", clink);
-
if (clink->pstate == ps_args)
- return jlif_key_child_link;
+ 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");
@@ -252,10 +245,21 @@ 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);
+ /* 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 == 4) {
+ 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)
@@ -293,13 +297,16 @@ static jlif_result lnkCalc_end_map(jlink *pjlink)
{
calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink);
- IFDEBUG(10)
- printf("lnkCalc_end_map(calc@%p)\n", clink);
-
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;
}
@@ -310,9 +317,6 @@ 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", clink);
-
if (clink->pstate != ps_args) {
errlogPrintf("lnkCalc: Unexpected array\n");
return jlif_stop;
@@ -325,9 +329,6 @@ 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", clink);
-
if (clink->pstate == ps_error)
return jlif_stop;
@@ -339,15 +340,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;
@@ -355,11 +368,6 @@ static void lnkCalc_end_child(jlink *parent, jlink *child)
static struct lset* lnkCalc_get_lset(const jlink *pjlink)
{
- calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink);
-
- IFDEBUG(10)
- printf("lnkCalc_get_lset(calc@%p)\n", pjlink);
-
return &lnkCalc_lset;
}
@@ -368,9 +376,6 @@ static void lnkCalc_report(const jlink *pjlink, int level, int indent)
calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink);
int i;
- IFDEBUG(10)
- printf("lnkCalc_report(calc@%p)\n", clink);
-
printf("%*s'calc': \"%s\" = %.*g %s\n", indent, "",
clink->expr, clink->prec, clink->val,
clink->units ? clink->units : "");
@@ -388,9 +393,13 @@ static void lnkCalc_report(const jlink *pjlink, int level, int indent)
printf("%*s Minor expression: \"%s\"\n", indent, "",
clink->minor);
- if (clink->tinp >= 0 && clink->tinp < clink->nArgs)
- printf("%*s Timestamp input \"%c\"\n", indent, "",
- clink->tinp + 'A');
+ 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];
@@ -403,17 +412,20 @@ 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);
+ }
}
}
-long lnkCalc_map_children(jlink *pjlink, jlink_map_fn rtn, void *ctx)
+static long lnkCalc_map_children(jlink *pjlink, jlink_map_fn rtn, void *ctx)
{
calc_link *clink = CONTAINER(pjlink, struct calc_link, jlink);
int i;
- IFDEBUG(10)
- printf("lnkCalc_map_children(calc@%p)\n", clink);
-
for (i = 0; i < clink->nArgs; i++) {
struct link *child = &clink->inp[i];
long status = dbJLinkMapChildren(child, rtn, ctx);
@@ -421,6 +433,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;
}
@@ -432,9 +448,6 @@ static void lnkCalc_open(struct link *plink)
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];
@@ -442,6 +455,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)
@@ -450,15 +467,16 @@ static void lnkCalc_remove(struct dbLocker *locker, struct link *plink)
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);
}
+ if (clink->out.type == JSON_LINK) {
+ dbRemoveLink(locker, &clink->out);
+ }
+
free(clink->expr);
free(clink->major);
free(clink->minor);
@@ -477,9 +495,6 @@ static int lnkCalc_isConn(const struct link *plink)
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];
@@ -488,37 +503,24 @@ 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;
}
static int lnkCalc_getDBFtype(const struct link *plink)
{
- calc_link *clink = CONTAINER(plink->value.json.jlink,
- struct calc_link, jlink);
-
- 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)
{
- calc_link *clink = CONTAINER(plink->value.json.jlink,
- struct calc_link, jlink);
-
- 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;
}
@@ -551,23 +553,22 @@ static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer,
long status;
FASTCONVERT conv = dbFastPutConvertRoutine[DBR_DOUBLE][dbrType];
- IFDEBUG(10)
- printf("lnkCalc_getValue(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 &&
- dbLinkIsConstant(&prec->tsel) &&
- prec->tse == epicsTimeEventDeviceTime) {
- struct lcvt vt = {&clink->arg[i], &prec->time};
+ 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);
@@ -599,7 +600,7 @@ static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer,
}
}
- if (!status && clink->post_minor) {
+ if (!status && !clink->sevr && clink->post_minor) {
double alval = clink->val;
status = calcPerform(clink->arg, &alval, clink->post_minor);
@@ -613,14 +614,79 @@ 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];
+
+ /* 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->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;
+ 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,
struct calc_link, jlink);
- IFDEBUG(10)
- printf("lnkCalc_getPrecision(calc@%p)\n", clink);
-
*precision = clink->prec;
return 0;
}
@@ -630,9 +696,6 @@ 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';
@@ -648,9 +711,6 @@ static long lnkCalc_getAlarm(const struct link *plink, epicsEnum16 *status,
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)
@@ -659,6 +719,19 @@ static long lnkCalc_getAlarm(const struct link *plink, epicsEnum16 *status,
return 0;
}
+static long lnkCalc_getTimestamp(const struct link *plink, epicsTimeStamp *pstamp)
+{
+ calc_link *clink = CONTAINER(plink->value.json.jlink,
+ struct calc_link, jlink);
+
+ if (clink->tinp >= 0) {
+ *pstamp = clink->time;
+ return 0;
+ }
+
+ return -1;
+}
+
static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv)
{
return rtn(plink, priv);
@@ -675,8 +748,8 @@ static lset lnkCalc_lset = {
lnkCalc_getValue,
NULL, NULL, NULL,
lnkCalc_getPrecision, lnkCalc_getUnits,
- lnkCalc_getAlarm, NULL,
- NULL, NULL,
+ lnkCalc_getAlarm, lnkCalc_getTimestamp,
+ lnkCalc_putValue, NULL,
NULL, doLocked
};
@@ -686,6 +759,6 @@ static jlif lnkCalcIf = {
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
+ lnkCalc_report, lnkCalc_map_children, NULL
};
epicsExportAddress(jlif, lnkCalcIf);
diff --git a/src/std/link/lnkConst.c b/src/std/link/lnkConst.c
index c2eb032c1..cb948fcb3 100644
--- a/src/std/link/lnkConst.c
+++ b/src/std/link/lnkConst.c
@@ -2,7 +2,7 @@
* 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.
+* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* lnkConst.c */
@@ -22,8 +22,6 @@
#include "epicsExport.h"
-#define IFDEBUG(n) if (clink->jlink.debug)
-
typedef long (*FASTCONVERT)();
typedef struct const_link {
@@ -48,18 +46,23 @@ static lset lnkConst_lset;
static jlink* lnkConst_alloc(short dbfType)
{
- const_link *clink = calloc(1, sizeof(*clink));
+ const_link *clink;
- IFDEBUG(10)
- printf("lnkConst_alloc()\n");
+ if (dbfType != DBF_INLINK) {
+ errlogPrintf("lnkConst: Only works with input links\n");
+ return NULL;
+ }
+
+ clink = calloc(1, sizeof(*clink));
+ if (!clink) {
+ errlogPrintf("lnkConst: calloc() failed.\n");
+ return NULL;
+ }
clink->type = s0;
clink->nElems = 0;
clink->value.pmem = NULL;
- IFDEBUG(10)
- printf("lnkConst_alloc -> const@%p\n", clink);
-
return &clink->jlink;
}
@@ -67,9 +70,6 @@ static void lnkConst_free(jlink *pjlink)
{
const_link *clink = CONTAINER(pjlink, const_link, jlink);
- IFDEBUG(10)
- printf("lnkConst_free(const@%p) type=%d\n", pjlink, clink->type);
-
switch (clink->type) {
int i;
case ac40:
@@ -95,16 +95,13 @@ static jlif_result lnkConst_integer(jlink *pjlink, long long num)
const_link *clink = CONTAINER(pjlink, const_link, jlink);
int newElems = clink->nElems + 1;
- IFDEBUG(10)
- printf("lnkConst_integer(const@%p, %lld)\n", pjlink, num);
-
switch (clink->type) {
void *buf;
case s0:
clink->type = si64;
clink->value.scalar_integer = num;
- IFDEBUG(12)
+ if (pjlink->debug)
printf(" si64 := %lld\n", num);
break;
@@ -118,7 +115,7 @@ static jlif_result lnkConst_integer(jlink *pjlink, long long num)
clink->value.pmem = buf;
clink->value.pintegers[clink->nElems] = num;
- IFDEBUG(12)
+ if (pjlink->debug)
printf(" ai64 += %lld\n", num);
break;
@@ -129,7 +126,7 @@ static jlif_result lnkConst_integer(jlink *pjlink, long long num)
clink->value.pmem = buf;
clink->value.pdoubles[clink->nElems] = num;
- IFDEBUG(12)
+ if (pjlink->debug)
printf(" af64 += %lld\n", num);
break;
@@ -146,10 +143,6 @@ static jlif_result lnkConst_integer(jlink *pjlink, long long num)
static jlif_result lnkConst_boolean(jlink *pjlink, int val)
{
- const_link *clink = CONTAINER(pjlink, const_link, jlink);
- IFDEBUG(10)
- printf("lnkConst_boolean(const@%p, %d)\n", pjlink, val);
-
return lnkConst_integer(pjlink, val);
}
@@ -158,9 +151,6 @@ static jlif_result lnkConst_double(jlink *pjlink, double num)
const_link *clink = CONTAINER(pjlink, const_link, jlink);
int newElems = clink->nElems + 1;
- IFDEBUG(10)
- printf("lnkConst_double(const@%p, %g)\n", pjlink, num);
-
switch (clink->type) {
epicsFloat64 *f64buf;
int i;
@@ -212,9 +202,6 @@ static jlif_result lnkConst_string(jlink *pjlink, const char *val, size_t len)
const_link *clink = CONTAINER(pjlink, const_link, jlink);
int newElems = clink->nElems + 1;
- IFDEBUG(10)
- printf("lnkConst_string(const@%p, \"%.*s\")\n", clink, (int) len, val);
-
switch (clink->type) {
char **vec, *str;
@@ -262,9 +249,6 @@ static jlif_result lnkConst_start_array(jlink *pjlink)
{
const_link *clink = CONTAINER(pjlink, const_link, jlink);
- IFDEBUG(10)
- printf("lnkConst_start_array(const@%p)\n", pjlink);
-
if (clink->type != s0) {
errlogPrintf("lnkConst: Embedded array value\n");
return jlif_stop;
@@ -276,21 +260,11 @@ static jlif_result lnkConst_start_array(jlink *pjlink)
static jlif_result lnkConst_end_array(jlink *pjlink)
{
- const_link *clink = CONTAINER(pjlink, const_link, jlink);
-
- IFDEBUG(10)
- printf("lnkConst_end_array(const@%p)\n", pjlink);
-
return jlif_continue;
}
static struct lset* lnkConst_get_lset(const jlink *pjlink)
{
- const_link *clink = CONTAINER(pjlink, const_link, jlink);
-
- IFDEBUG(10)
- printf("lnkConst_get_lset(const@%p)\n", pjlink);
-
return &lnkConst_lset;
}
@@ -318,9 +292,6 @@ static void lnkConst_report(const jlink *pjlink, int level, int indent)
};
const char * const dtype = type_names[clink->type & 3];
- IFDEBUG(10)
- printf("lnkConst_report(const@%p)\n", clink);
-
if (clink->type > a0) {
const char * const plural = clink->nElems > 1 ? "s" : "";
@@ -382,11 +353,6 @@ static void lnkConst_report(const jlink *pjlink, int level, int indent)
static void lnkConst_remove(struct dbLocker *locker, struct link *plink)
{
- const_link *clink = CONTAINER(plink->value.json.jlink, const_link, jlink);
-
- IFDEBUG(10)
- printf("lnkConst_remove(const@%p)\n", clink);
-
lnkConst_free(plink->value.json.jlink);
}
@@ -395,55 +361,51 @@ static long lnkConst_loadScalar(struct link *plink, short dbrType, void *pbuffer
const_link *clink = CONTAINER(plink->value.json.jlink, const_link, jlink);
long status;
- IFDEBUG(10)
- printf("lnkConst_loadScalar(const@%p, %d, %p)\n",
- clink, dbrType, pbuffer);
-
switch (clink->type) {
case si64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" si64 %lld\n", clink->value.scalar_integer);
status = dbFastPutConvertRoutine[DBF_INT64][dbrType]
(&clink->value.scalar_integer, pbuffer, NULL);
break;
case sf64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" sf64 %g\n", clink->value.scalar_double);
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
(&clink->value.scalar_double, pbuffer, NULL);
break;
case sc40:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" sc40 '%s'\n", clink->value.scalar_string);
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
(clink->value.scalar_string, pbuffer, NULL);
break;
case ai64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" ai64 [%lld, ...]\n", clink->value.pintegers[0]);
status = dbFastPutConvertRoutine[DBF_INT64][dbrType]
(clink->value.pintegers, pbuffer, NULL);
break;
case af64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" af64 [%g, ...]\n", clink->value.pdoubles[0]);
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
(clink->value.pdoubles, pbuffer, NULL);
break;
case ac40:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" ac40 ['%s', ...]\n", clink->value.pstrings[0]);
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
(clink->value.pstrings[0], pbuffer, NULL);
break;
default:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" Bad type %d\n", clink->type);
status = S_db_badField;
break;
@@ -458,27 +420,23 @@ static long lnkConst_loadLS(struct link *plink, char *pbuffer, epicsUInt32 size,
const_link *clink = CONTAINER(plink->value.json.jlink, const_link, jlink);
const char *pstr;
- IFDEBUG(10)
- printf("lnkConst_loadLS(const@%p, %p, %d, %d)\n",
- clink, pbuffer, size, *plen);
-
if(!size) return 0;
switch (clink->type) {
case sc40:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" sc40 '%s'\n", clink->value.scalar_string);
pstr = clink->value.scalar_string;
break;
case ac40:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" ac40 ['%s', ...]\n", clink->value.pstrings[0]);
pstr = clink->value.pstrings[0];
break;
default:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" Bad type %d\n", clink->type);
return S_db_badField;
}
@@ -499,10 +457,6 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
FASTCONVERT conv;
long status;
- IFDEBUG(10)
- printf("lnkConst_loadArray(const@%p, %d, %p, (%ld))\n",
- clink, dbrType, pbuffer, *pnReq);
-
if (nElems > *pnReq)
nElems = *pnReq;
@@ -510,28 +464,28 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
int i;
case si64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" si64 %lld\n", clink->value.scalar_integer);
status = dbFastPutConvertRoutine[DBF_INT64][dbrType]
(&clink->value.scalar_integer, pdest, NULL);
break;
case sf64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" sf64 %g\n", clink->value.scalar_double);
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
(&clink->value.scalar_double, pdest, NULL);
break;
case sc40:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" sc40 '%s'\n", clink->value.scalar_string);
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
(clink->value.scalar_string, pbuffer, NULL);
break;
case ai64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" ai64 [%lld, ...]\n", clink->value.pintegers[0]);
conv = dbFastPutConvertRoutine[DBF_INT64][dbrType];
for (i = 0; i < nElems; i++) {
@@ -542,7 +496,7 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
break;
case af64:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" af64 [%g, ...]\n", clink->value.pdoubles[0]);
conv = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType];
for (i = 0; i < nElems; i++) {
@@ -553,7 +507,7 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
break;
case ac40:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" ac40 ['%s', ...]\n", clink->value.pstrings[0]);
conv = dbFastPutConvertRoutine[DBF_STRING][dbrType];
for (i = 0; i < nElems; i++) {
@@ -564,7 +518,7 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
break;
default:
- IFDEBUG(12)
+ if (clink->jlink.debug)
printf(" Bad type %d\n", clink->type);
status = S_db_badField;
}
@@ -574,12 +528,6 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
static long lnkConst_getNelements(const struct link *plink, long *nelements)
{
- const_link *clink = CONTAINER(plink->value.json.jlink, const_link, jlink);
-
- IFDEBUG(10)
- printf("lnkConst_getNelements(const@%p, (%ld))\n",
- plink->value.json.jlink, *nelements);
-
*nelements = 0;
return 0;
}
@@ -587,13 +535,6 @@ static long lnkConst_getNelements(const struct link *plink, long *nelements)
static long lnkConst_getValue(struct link *plink, short dbrType, void *pbuffer,
long *pnRequest)
{
- const_link *clink = CONTAINER(plink->value.json.jlink, const_link, jlink);
-
- IFDEBUG(10)
- printf("lnkConst_getValue(const@%p, %d, %p, ... (%ld))\n",
- plink->value.json.jlink, dbrType, pbuffer,
- pnRequest ? *pnRequest : 0);
-
if (pnRequest)
*pnRequest = 0;
return 0;
@@ -626,7 +567,6 @@ static jlif lnkConstIf = {
NULL, NULL, NULL,
lnkConst_start_array, lnkConst_end_array,
NULL, lnkConst_get_lset,
- lnkConst_report, NULL
+ lnkConst_report, NULL, NULL
};
epicsExportAddress(jlif, lnkConstIf);
-
diff --git a/src/std/link/lnkDebug.c b/src/std/link/lnkDebug.c
new file mode 100644
index 000000000..bd8f84e5a
--- /dev/null
+++ b/src/std/link/lnkDebug.c
@@ -0,0 +1,1045 @@
+/*************************************************************************\
+* Copyright (c) 2018 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.
+\*************************************************************************/
+/* lnkDebug.c */
+
+/* Usage
+ * {debug:{...:...}}
+ */
+
+#include
+#include
+#include
+
+#include "alarm.h"
+#include "dbDefs.h"
+#include "dbAccessDefs.h"
+#include "dbLink.h"
+#include "dbJLink.h"
+#include "dbStaticLib.h"
+#include "errlog.h"
+#include "epicsTime.h"
+
+#include "epicsExport.h"
+
+/* This is for debugging the debug link-type */
+int lnkDebug_debug;
+epicsExportAddress(int, lnkDebug_debug);
+
+#define IFDEBUG(n) if (lnkDebug_debug >= (n))
+
+typedef struct debug_link {
+ jlink jlink; /* embedded object */
+ short dbfType;
+ unsigned trace:1;
+ const jlif *child_jlif;
+ const lset *child_lset;
+ jlif jlif;
+ lset lset;
+ struct link child_link;
+} debug_link;
+
+
+/********************* Delegating jlif Routines *********************/
+
+static void delegate_free(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ struct link *plink = &dlink->child_link;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::free_jlink(%p)\n",
+ pif->name, pjlink);
+
+ pif->free_jlink(pjlink);
+ plink->type = 0;
+ plink->value.json.jlink = NULL;
+
+ if (dlink->trace)
+ printf("Link trace: %s::free_jlink(%p) returned\n",
+ pif->name, pjlink);
+}
+
+static jlif_result delegate_null(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_null(%p)\n",
+ pif->name, pjlink);
+
+ res = pif->parse_null(pjlink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_null(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_boolean(jlink *pjlink, int val)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_boolean(%p, %d)\n",
+ pif->name, pjlink, val);
+
+ res = pif->parse_boolean(pjlink, val);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_boolean(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_integer(jlink *pjlink, long long num)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_integer(%p, %lld)\n",
+ pif->name, pjlink, num);
+
+ res = pif->parse_integer(pjlink, num);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_integer(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_double(jlink *pjlink, double num)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_double(%p, %g)\n",
+ pif->name, pjlink, num);
+
+ res = pif->parse_double(pjlink, num);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_double(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_string(jlink *pjlink, const char *val, size_t len)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_string(%p, \"%.*s\")\n",
+ pif->name, pjlink, (int) len, val);
+
+ res = pif->parse_string(pjlink, val, len);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_string(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_key_result delegate_start_map(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_key_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_start_map(%p)\n",
+ pif->name, pjlink);
+
+ res = pif->parse_start_map(pjlink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_start_map(%p) returned %s\n",
+ pif->name, pjlink, jlif_key_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_map_key(jlink *pjlink, const char *key, size_t len)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_map_key(%p, \"%.*s\")\n",
+ pif->name, pjlink, (int) len, key);
+
+ res = pif->parse_map_key(pjlink, key, len);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_map_key(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_end_map(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_end_map(%p)\n",
+ pif->name, pjlink);
+
+ res = pif->parse_end_map(pjlink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_end_map(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_start_array(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_(%p)\n",
+ pif->name, pjlink);
+
+ res = pif->parse_start_array(pjlink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_start_array(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static jlif_result delegate_end_array(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ jlif_result res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::parse_end_array(%p)\n",
+ pif->name, pjlink);
+
+ res = pif->parse_end_array(pjlink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::parse_end_array(%p) returned %s\n",
+ pif->name, pjlink, jlif_result_name[res]);
+
+ return res;
+}
+
+static void delegate_start_child(jlink *pjlink, jlink *child)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::start_child(%p, %p)\n",
+ pif->name, pjlink, child);
+
+ pif->start_child(pjlink, child);
+
+ if (dlink->trace)
+ printf("Link trace: %s::start_child(%p) returned\n",
+ pif->name, pjlink);
+}
+
+static void delegate_end_child(jlink *pjlink, jlink *child)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::end_child(%p, %p)\n",
+ pif->name, pjlink, child);
+
+ pif->end_child(pjlink, child);
+
+ if (dlink->trace)
+ printf("Link trace: %s::end_child(%p) returned\n",
+ pif->name, pjlink);
+}
+
+static struct lset* delegate_get_lset(const jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+
+ if (dlink->trace)
+ printf("Link trace: NOT calling %s::get_lset(%p)\n",
+ dlink->child_jlif->name, pjlink);
+
+ return &dlink->lset;
+}
+
+static void delegate_report(const jlink *pjlink, int level, int indent)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::report(%p, %d, %d)\n",
+ pif->name, pjlink, level, indent);
+
+ pif->report(pjlink, level, indent);
+
+ if (dlink->trace)
+ printf("Link trace: %s::report(%p) returned\n",
+ pif->name, pjlink);
+}
+
+static long delegate_map_children(jlink *pjlink, jlink_map_fn rtn, void *ctx)
+{
+ debug_link *dlink = CONTAINER(pjlink->parent, struct debug_link, jlink);
+ const jlif *pif = dlink->child_jlif;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::map_children(%p, %p, %p)\n",
+ pif->name, pjlink, rtn, ctx);
+
+ res = pif->map_children(pjlink, rtn, ctx);
+
+ if (dlink->trace)
+ printf("Link trace: %s::map_children(%p) returned %ld\n",
+ pif->name, pjlink, res);
+
+ return res;
+}
+
+
+/********************* Delegating lset Routines *********************/
+
+static void delegate_openLink(struct link *plink)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::openLink(%p = jlink %p)\n",
+ dlink->child_jlif->name, clink, clink->value.json.jlink);
+
+ clink->precord = plink->precord;
+ clset->openLink(clink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::openLink(%p) returned\n",
+ dlink->child_jlif->name, clink);
+}
+
+static void delegate_removeLink(struct dbLocker *locker, struct link *plink)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::removeLink(%p, %p)\n",
+ dlink->child_jlif->name, locker, clink);
+
+ clset->removeLink(locker, clink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::removeLink(%p) returned\n",
+ dlink->child_jlif->name, clink);
+}
+
+static long delegate_loadScalar(struct link *plink, short dbrType, void *pbuffer)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::loadScalar(%p, %s, %p)\n",
+ dlink->child_jlif->name, clink,
+ dbGetFieldTypeString(dbrType), pbuffer);
+
+ res = clset->loadScalar(clink, dbrType, pbuffer);
+
+ if (dlink->trace)
+ printf("Link trace: %s::loadScalar(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+
+ return res;
+}
+
+static long delegate_loadLS(struct link *plink, char *pbuffer, epicsUInt32 size,
+ epicsUInt32 *plen)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::loadLS(%p, %p, %u)\n",
+ dlink->child_jlif->name, clink, pbuffer, size);
+
+ res = clset->loadLS(clink, pbuffer, size, plen);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::loadLS(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Loaded: %u byte(s) \"%s\"\n", *plen, pbuffer);
+ }
+
+ return res;
+}
+
+static long delegate_loadArray(struct link *plink, short dbrType, void *pbuffer,
+ long *pnRequest)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::loadArray(%p, %s, %p, %ld)\n",
+ dlink->child_jlif->name, clink,
+ dbGetFieldTypeString(dbrType), pbuffer, pnRequest ? *pnRequest : 0l);
+
+ res = clset->loadArray(clink, dbrType, pbuffer, pnRequest);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::loadArray(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Loaded: %ld element(s)\n", pnRequest ? *pnRequest : 1l);
+ }
+
+ return res;
+}
+
+static int delegate_isConnected(const struct link *plink)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ int res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::isConnected(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->isConnected(clink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::isConnected(%p) returned %d (%s)\n",
+ dlink->child_jlif->name, clink, res,
+ res == 0 ? "No" : res == 1 ? "Yes" : "Bad value");
+
+ return res;
+}
+
+static int delegate_getDBFtype(const struct link *plink)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ int res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getDBFtype(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getDBFtype(clink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::getDBFtype(%p) returned %d (%s)\n",
+ dlink->child_jlif->name, clink, res,
+ res == -1 ? "Link disconnected" : dbGetFieldTypeString(res));
+
+ return res;
+}
+
+static long delegate_getElements(const struct link *plink, long *pnElements)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getElements(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getElements(clink, pnElements);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getElements(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Result: %ld element(s)\n", *pnElements);
+ }
+
+ return res;
+}
+
+static long delegate_getValue(struct link *plink, short dbrType, void *pbuffer,
+ long *pnRequest)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getValue(%p, %s, %p, %ld)\n",
+ dlink->child_jlif->name, clink,
+ dbGetFieldTypeString(dbrType), pbuffer, pnRequest ? *pnRequest : 0l);
+
+ res = clset->getValue(clink, dbrType, pbuffer, pnRequest);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getValue(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got: %ld element(s)\n", pnRequest ? *pnRequest : 1l);
+ }
+
+ return res;
+}
+
+static long delegate_getControlLimits(const struct link *plink,
+ double *lo, double *hi)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getControlLimits(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getControlLimits(clink, lo, hi);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getControlLimits(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got: Lo = %g, Hi = %g\n", *lo, *hi);
+ }
+
+ return res;
+}
+
+static long delegate_getGraphicLimits(const struct link *plink,
+ double *lo, double *hi)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getGraphicLimits(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getGraphicLimits(clink, lo, hi);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getGraphicLimits(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got: Lo = %g, Hi = %g\n", *lo, *hi);
+ }
+
+ return res;
+}
+
+static long delegate_getAlarmLimits(const struct link *plink,
+ double *lolo, double *lo, double *hi, double *hihi)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getAlarmLimits(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getAlarmLimits(clink, lolo, lo, hi, hihi);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getAlarmLimits(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got: Lolo = %g, Lo = %g, Hi = %g, Hihi = %g\n",
+ *lolo, *lo, *hi, *hihi);
+ }
+
+ return res;
+}
+
+static long delegate_getPrecision(const struct link *plink, short *precision)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getPrecision(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getPrecision(clink, precision);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getPrecision(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got: prec = %d\n", *precision);
+ }
+
+ return res;
+}
+
+static long delegate_getUnits(const struct link *plink, char *units, int unitsSize)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getUnits(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getUnits(clink, units, unitsSize);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getUnits(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got: Units = '%s'\n", units);
+ }
+
+ return res;
+}
+
+static long delegate_getAlarm(const struct link *plink, epicsEnum16 *stat,
+ epicsEnum16 *sevr)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getAlarm(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getAlarm(clink, stat, sevr);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getAlarm(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0)
+ printf(" Got:%s%s%s%s\n",
+ stat ? " Status = " : "",
+ stat && (*stat < ALARM_NSTATUS) ?
+ epicsAlarmConditionStrings[*stat] : "Bad-status",
+ sevr ? " Severity = " : "",
+ sevr && (*sevr < ALARM_NSEV) ?
+ epicsAlarmSeverityStrings[*sevr] : "Bad-severity"
+ );
+ }
+
+ return res;
+}
+
+static long delegate_getTimeStamp(const struct link *plink, epicsTimeStamp *pstamp)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::getTimeStamp(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ res = clset->getTimeStamp(clink, pstamp);
+
+ if (dlink->trace) {
+ printf("Link trace: %s::getTimeStamp(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+ if (res == 0) {
+ char timeStr[32];
+
+ epicsTimeToStrftime(timeStr, sizeof(timeStr),
+ "%Y-%m-%d %H:%M:%S.%09f", pstamp);
+ printf(" Got: Timestamp = '%s'\n", timeStr);
+ }
+ }
+
+ return res;
+}
+
+static long delegate_putValue(struct link *plink, short dbrType,
+ const void *pbuffer, long nRequest)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::putValue(%p, %s, %p, %ld)\n",
+ dlink->child_jlif->name, clink,
+ dbGetFieldTypeString(dbrType), pbuffer, nRequest);
+
+ res = clset->putValue(clink, dbrType, pbuffer, nRequest);
+
+ if (dlink->trace)
+ printf("Link trace: %s::putValue(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+
+ return res;
+}
+
+static long delegate_putAsync(struct link *plink, short dbrType,
+ const void *pbuffer, long nRequest)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::putAsync(%p, %s, %p, %ld)\n",
+ dlink->child_jlif->name, clink,
+ dbGetFieldTypeString(dbrType), pbuffer, nRequest);
+
+ res = clset->putAsync(clink, dbrType, pbuffer, nRequest);
+
+ if (dlink->trace)
+ printf("Link trace: %s::putAsync(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+
+ return res;
+}
+
+static void delegate_scanForward(struct link *plink)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::scanForward(%p)\n",
+ dlink->child_jlif->name, clink);
+
+ clset->scanForward(clink);
+
+ if (dlink->trace)
+ printf("Link trace: %s::scanForward(%p) returned\n",
+ dlink->child_jlif->name, clink);
+}
+
+static long delegate_doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv)
+{
+ debug_link *dlink = CONTAINER(plink->value.json.jlink,
+ struct debug_link, jlink);
+ struct link *clink = &dlink->child_link;
+ const lset *clset = dlink->child_lset;
+ long res;
+
+ if (dlink->trace)
+ printf("Link trace: Calling %s::doLocked(%p, %p, %p)\n",
+ dlink->child_jlif->name, clink, rtn, priv);
+
+ res = clset->doLocked(clink, rtn, priv);
+
+ if (dlink->trace)
+ printf("Link trace: %s::doLocked(%p) returned %ld (0x%lx)\n",
+ dlink->child_jlif->name, clink, res, res);
+
+ return res;
+}
+
+
+/************************ Debug jlif Routines ***********************/
+
+static jlink* lnkDebug_alloc(short dbfType)
+{
+ debug_link *dlink;
+
+ IFDEBUG(10)
+ printf("lnkDebug_alloc(%s)\n", dbGetFieldTypeString(dbfType));
+
+ dlink = calloc(1, sizeof(struct debug_link));
+ if (!dlink) {
+ errlogPrintf("lnkDebug: calloc() failed.\n");
+ return NULL;
+ }
+
+ dlink->dbfType = dbfType;
+
+ IFDEBUG(10)
+ printf("lnkDebug_alloc -> debug@%p\n", dlink);
+
+ return &dlink->jlink;
+}
+
+static jlink* lnkTrace_alloc(short dbfType)
+{
+ debug_link *dlink;
+
+ IFDEBUG(10)
+ printf("lnkTrace_alloc(%s)\n", dbGetFieldTypeString(dbfType));
+
+ dlink = calloc(1, sizeof(struct debug_link));
+ if (!dlink) {
+ errlogPrintf("lnkTrace: calloc() failed.\n");
+ return NULL;
+ }
+
+ dlink->dbfType = dbfType;
+ dlink->trace = 1;
+
+ IFDEBUG(10)
+ printf("lnkTrace_alloc -> debug@%p\n", dlink);
+
+ return &dlink->jlink;
+}
+
+static void lnkDebug_free(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink, struct debug_link, jlink);
+
+ IFDEBUG(10)
+ printf("lnkDebug_free(debug@%p)\n", dlink);
+
+ dbJLinkFree(dlink->child_link.value.json.jlink);
+
+ free(dlink);
+}
+
+static jlif_key_result lnkDebug_start_map(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink, struct debug_link, jlink);
+
+ IFDEBUG(10)
+ printf("lnkDebug_start_map(debug@%p)\n", dlink);
+
+ switch (dlink->dbfType) {
+ case DBF_INLINK:
+ return jlif_key_child_inlink;
+ case DBF_OUTLINK:
+ return jlif_key_child_outlink;
+ case DBF_FWDLINK:
+ return jlif_key_child_outlink;
+ }
+ return jlif_key_stop;
+}
+
+static jlif_result lnkDebug_end_map(jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink, struct debug_link, jlink);
+
+ IFDEBUG(10)
+ printf("lnkDebug_end_map(debug@%p)\n", dlink);
+
+ return jlif_continue;
+}
+
+static void lnkDebug_start_child(jlink *parent, jlink *child)
+{
+ debug_link *dlink = CONTAINER(parent, struct debug_link, jlink);
+ const jlif *pif = child->pif;
+ const jlif delegate_jlif = {
+ pif->name,
+ pif->alloc_jlink, /* Never used */
+ delegate_free,
+ pif->parse_null ? delegate_null : NULL,
+ pif->parse_boolean ? delegate_boolean : NULL,
+ pif->parse_integer ? delegate_integer : NULL,
+ pif->parse_double ? delegate_double : NULL,
+ pif->parse_string ? delegate_string : NULL,
+ pif->parse_start_map ? delegate_start_map : NULL,
+ pif->parse_map_key ? delegate_map_key : NULL,
+ pif->parse_end_map ? delegate_end_map : NULL,
+ pif->parse_start_array ? delegate_start_array : NULL,
+ pif->parse_end_array ? delegate_end_array : NULL,
+ pif->end_child ? delegate_end_child : NULL,
+ delegate_get_lset,
+ pif->report ? delegate_report : NULL,
+ pif->map_children ? delegate_map_children : NULL,
+ pif->start_child ? delegate_start_child : NULL
+ };
+
+ IFDEBUG(10)
+ printf("lnkDebug_start_child(debug@%p, %s@%p)\n", dlink,
+ child->pif->name, child);
+
+ dlink->child_jlif = pif;
+ memcpy(&dlink->jlif, &delegate_jlif, sizeof(jlif));
+
+ child->debug = 1;
+ child->pif = &dlink->jlif; /* Replace Child's interface table */
+
+ IFDEBUG(15)
+ printf("lnkDebug_start_child: pif %p => %p\n", pif, child->pif);
+
+ if (dlink->trace)
+ printf("Link trace: %s::alloc_jlink(%s) returned %p\n",
+ pif->name, dbGetFieldTypeString(dlink->dbfType), child);
+}
+
+static void lnkDebug_end_child(jlink *parent, jlink *child)
+{
+ debug_link *dlink = CONTAINER(parent, struct debug_link, jlink);
+ struct link *plink = &dlink->child_link;
+ const lset *plset = dlink->child_jlif->get_lset(child);
+ lset delegate_lset = {
+ plset->isConstant,
+ plset->isVolatile,
+ plset->openLink ? delegate_openLink : NULL,
+ plset->removeLink ? delegate_removeLink : NULL,
+ plset->loadScalar ? delegate_loadScalar : NULL,
+ plset->loadLS ? delegate_loadLS : NULL,
+ plset->loadArray ? delegate_loadArray : NULL,
+ plset->isConnected ? delegate_isConnected : NULL,
+ plset->getDBFtype ? delegate_getDBFtype : NULL,
+ plset->getElements ? delegate_getElements : NULL,
+ plset->getValue ? delegate_getValue : NULL,
+ plset->getControlLimits ? delegate_getControlLimits : NULL,
+ plset->getGraphicLimits ? delegate_getGraphicLimits : NULL,
+ plset->getAlarmLimits ? delegate_getAlarmLimits : NULL,
+ plset->getPrecision ? delegate_getPrecision : NULL,
+ plset->getUnits ? delegate_getUnits : NULL,
+ plset->getAlarm ? delegate_getAlarm : NULL,
+ plset->getTimeStamp ? delegate_getTimeStamp : NULL,
+ plset->putValue ? delegate_putValue : NULL,
+ plset->putAsync ? delegate_putAsync : NULL,
+ plset->scanForward ? delegate_scanForward : NULL,
+ plset->doLocked ? delegate_doLocked : NULL
+ };
+
+ IFDEBUG(10)
+ printf("lnkDebug_end_child(debug@%p, %s@%p)\n", dlink,
+ child->pif->name, child);
+
+ plink->type = JSON_LINK;
+ plink->value.json.string = NULL;
+ plink->value.json.jlink = child;
+
+ dlink->child_lset = plset;
+ memcpy(&dlink->lset, &delegate_lset, sizeof(lset));
+
+ IFDEBUG(15)
+ printf("lnkDebug_end_child: lset %p => %p\n", plset, &dlink->lset);
+}
+
+static struct lset* lnkDebug_get_lset(const jlink *pjlink)
+{
+ debug_link *dlink = CONTAINER(pjlink, struct debug_link, jlink);
+
+ IFDEBUG(10)
+ printf("lnkDebug_get_lset(debug@%p)\n", pjlink);
+
+ return &dlink->lset;
+}
+
+static void lnkDebug_report(const jlink *pjlink, int level, int indent)
+{
+ debug_link *dlink = CONTAINER(pjlink, struct debug_link, jlink);
+
+ IFDEBUG(10)
+ printf("lnkDebug_report(debug@%p)\n", dlink);
+
+ if (dlink->trace)
+ printf("%*s'trace':\n", indent, "");
+ else
+ printf("%*s'debug':\n", indent, "");
+
+ if (dlink->child_link.type == JSON_LINK) {
+ dbJLinkReport(dlink->child_link.value.json.jlink, level, indent + 2);
+ }
+}
+
+long lnkDebug_map_children(jlink *pjlink, jlink_map_fn rtn, void *ctx)
+{
+ debug_link *dlink = CONTAINER(pjlink, struct debug_link, jlink);
+
+ IFDEBUG(10)
+ printf("lnkDebug_map_children(debug@%p)\n", dlink);
+
+ if (dlink->child_link.type == JSON_LINK) {
+ return dbJLinkMapChildren(&dlink->child_link, rtn, ctx);
+ }
+ return 0;
+}
+
+
+/************************* Interface Tables *************************/
+
+static jlif lnkDebugIf = {
+ "debug", lnkDebug_alloc, lnkDebug_free,
+ NULL, NULL, NULL, NULL, NULL,
+ lnkDebug_start_map, NULL, lnkDebug_end_map,
+ NULL, NULL, lnkDebug_end_child, lnkDebug_get_lset,
+ lnkDebug_report, lnkDebug_map_children, lnkDebug_start_child
+};
+epicsExportAddress(jlif, lnkDebugIf);
+
+static jlif lnkTraceIf = {
+ "trace", lnkTrace_alloc, lnkDebug_free,
+ NULL, NULL, NULL, NULL, NULL,
+ lnkDebug_start_map, NULL, lnkDebug_end_map,
+ NULL, NULL, lnkDebug_end_child, lnkDebug_get_lset,
+ lnkDebug_report, lnkDebug_map_children, lnkDebug_start_child
+};
+epicsExportAddress(jlif, lnkTraceIf);
diff --git a/src/std/link/lnkState.c b/src/std/link/lnkState.c
new file mode 100644
index 000000000..b8791bd3b
--- /dev/null
+++ b/src/std/link/lnkState.c
@@ -0,0 +1,230 @@
+/*************************************************************************\
+* Copyright (c) 2017 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.
+\*************************************************************************/
+/* lnkState.c */
+
+/* Usage:
+ * {state:green}
+ */
+
+#include
+#include
+#include
+#include
+
+#include "alarm.h"
+#include "dbDefs.h"
+#include "errlog.h"
+#include "epicsAssert.h"
+#include "epicsString.h"
+#include "epicsTypes.h"
+#include "dbAccessDefs.h"
+#include "dbCommon.h"
+#include "dbConvertFast.h"
+#include "dbLink.h"
+#include "dbJLink.h"
+#include "dbStaticLib.h"
+#include "dbStaticPvt.h"
+#include "dbState.h"
+#include "recGbl.h"
+#include "epicsExport.h"
+
+
+typedef long (*FASTCONVERT)();
+
+typedef struct state_link {
+ jlink jlink; /* embedded object */
+ char *name;
+ short val;
+ short invert;
+ dbStateId state;
+} state_link;
+
+static lset lnkState_lset;
+
+
+/*************************** jlif Routines **************************/
+
+static jlink* lnkState_alloc(short dbfType)
+{
+ state_link *slink;
+
+ if (dbfType == DBF_FWDLINK) {
+ errlogPrintf("lnkState: DBF_FWDLINK not supported\n");
+ return NULL;
+ }
+
+ slink = calloc(1, sizeof(struct state_link));
+ if (!slink) {
+ errlogPrintf("lnkState: calloc() failed.\n");
+ return NULL;
+ }
+
+ slink->name = NULL;
+ slink->state = NULL;
+ slink->invert = 0;
+ slink->val = 0;
+
+ return &slink->jlink;
+}
+
+static void lnkState_free(jlink *pjlink)
+{
+ state_link *slink = CONTAINER(pjlink, struct state_link, jlink);
+
+ free(slink->name);
+ free(slink);
+}
+
+static jlif_result lnkState_string(jlink *pjlink, const char *val, size_t len)
+{
+ state_link *slink = CONTAINER(pjlink, struct state_link, jlink);
+
+ if (len > 1 && val[0] == '!') {
+ slink->invert = 1;
+ val++; len--;
+ }
+
+ slink->name = epicsStrnDup(val, len);
+ return jlif_continue;
+}
+
+static struct lset* lnkState_get_lset(const jlink *pjlink)
+{
+ return &lnkState_lset;
+}
+
+static void lnkState_report(const jlink *pjlink, int level, int indent)
+{
+ state_link *slink = CONTAINER(pjlink, struct state_link, jlink);
+
+ printf("%*s'state': \"%s\" = %s%s\n", indent, "",
+ slink->name, slink->invert ? "! " : "", slink->val ? "TRUE" : "FALSE");
+}
+
+/*************************** lset Routines **************************/
+
+static void lnkState_open(struct link *plink)
+{
+ state_link *slink = CONTAINER(plink->value.json.jlink,
+ struct state_link, jlink);
+
+ slink->state = dbStateCreate(slink->name);
+}
+
+static void lnkState_remove(struct dbLocker *locker, struct link *plink)
+{
+ state_link *slink = CONTAINER(plink->value.json.jlink,
+ struct state_link, jlink);
+
+ free(slink->name);
+ free(slink);
+
+ plink->value.json.jlink = NULL;
+}
+
+static int lnkState_getDBFtype(const struct link *plink)
+{
+ return DBF_SHORT;
+}
+
+static long lnkState_getElements(const struct link *plink, long *nelements)
+{
+ *nelements = 1;
+ return 0;
+}
+
+static long lnkState_getValue(struct link *plink, short dbrType, void *pbuffer,
+ long *pnRequest)
+{
+ state_link *slink = CONTAINER(plink->value.json.jlink,
+ struct state_link, jlink);
+ FASTCONVERT conv = dbFastPutConvertRoutine[DBR_SHORT][dbrType];
+
+ slink->val = slink->invert ^ dbStateGet(slink->state);
+ return conv(&slink->val, pbuffer, NULL);
+}
+
+static long lnkState_putValue(struct link *plink, short dbrType,
+ const void *pbuffer, long nRequest)
+{
+ state_link *slink = CONTAINER(plink->value.json.jlink,
+ struct state_link, jlink);
+ short val;
+ const char *pstr;
+
+ if (nRequest == 0)
+ return 0;
+
+ switch(dbrType) {
+ case DBR_CHAR:
+ case DBR_UCHAR:
+ val = !! *(const epicsInt8 *) pbuffer;
+ break;
+
+ case DBR_SHORT:
+ case DBR_USHORT:
+ val = !! *(const epicsInt16 *) pbuffer;
+ break;
+
+ case DBR_LONG:
+ case DBR_ULONG:
+ val = !! *(const epicsInt32 *) pbuffer;
+ break;
+
+ case DBR_INT64:
+ case DBR_UINT64:
+ val = !! *(const epicsInt64 *) pbuffer;
+ break;
+
+ case DBR_FLOAT:
+ val = !! *(const epicsFloat32 *) pbuffer;
+ break;
+
+ case DBR_DOUBLE:
+ val = !! *(const epicsFloat64 *) pbuffer;
+ break;
+
+ case DBR_STRING: /* Only "" and "0" are FALSE */
+ pstr = (const char *) pbuffer;
+ val = (pstr[0] != 0) && ((pstr[0] != '0') || (pstr[1] != 0));
+ break;
+
+ default:
+ return S_db_badDbrtype;
+ }
+ slink->val = val;
+
+ val ^= slink->invert;
+ (val ? dbStateSet : dbStateClear)(slink->state);
+
+ return 0;
+}
+
+/************************* Interface Tables *************************/
+
+static lset lnkState_lset = {
+ 0, 0, /* not constant, always connected */
+ lnkState_open, lnkState_remove,
+ NULL, NULL, NULL,
+ NULL, lnkState_getDBFtype, lnkState_getElements,
+ lnkState_getValue,
+ NULL, NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ lnkState_putValue, NULL,
+ NULL, NULL
+};
+
+static jlif lnkStateIf = {
+ "state", lnkState_alloc, lnkState_free,
+ NULL, NULL, NULL, NULL, lnkState_string,
+ NULL, NULL, NULL,
+ NULL, NULL,
+ NULL, lnkState_get_lset,
+ lnkState_report, NULL, NULL
+};
+epicsExportAddress(jlif, lnkStateIf);
diff --git a/src/std/link/test/Makefile b/src/std/link/test/Makefile
new file mode 100644
index 000000000..2a6507e07
--- /dev/null
+++ b/src/std/link/test/Makefile
@@ -0,0 +1,59 @@
+#*************************************************************************
+# Copyright (c) 2018 UChicago Argonne LLC, as Operator of Argonne
+# National Laboratory.
+# EPICS BASE is distributed subject to a Software License Agreement found
+# in the file LICENSE that is included with this distribution.
+#*************************************************************************
+TOP=../../../..
+
+include $(TOP)/configure/CONFIG
+
+TESTLIBRARY = Recs
+
+Recs_SRCS += ioRecord.c
+Recs_LIBS += dbCore ca Com
+
+PROD_LIBS = Recs dbRecStd dbCore ca Com
+
+DBDDEPENDS_FILES += linkTest.dbd$(DEP)
+TARGETS += $(COMMON_DIR)/linkTest.dbd
+linkTest_DBD += menuGlobal.dbd
+linkTest_DBD += menuConvert.dbd
+linkTest_DBD += menuScan.dbd
+linkTest_DBD += links.dbd
+linkTest_DBD += ioRecord.dbd
+TESTFILES += $(COMMON_DIR)/linkTest.dbd
+TESTFILES += ../ioRecord.db
+
+testHarness_SRCS += linkTest_registerRecordDeviceDriver.cpp
+
+TESTPROD_HOST += lnkStateTest
+lnkStateTest_SRCS += lnkStateTest.c
+lnkStateTest_SRCS += linkTest_registerRecordDeviceDriver.cpp
+testHarness_SRCS += lnkStateTest.c
+TESTS += lnkStateTest
+
+TESTPROD_HOST += lnkCalcTest
+lnkCalcTest_SRCS += lnkCalcTest.c
+lnkCalcTest_SRCS += linkTest_registerRecordDeviceDriver.cpp
+testHarness_SRCS += lnkCalcTest.c
+TESTS += lnkCalcTest
+
+# epicsRunLinkTests runs all the test programs in a known working order.
+testHarness_SRCS += epicsRunLinkTests.c
+
+linkTestHarness_SRCS += $(testHarness_SRCS)
+linkTestHarness_SRCS_RTEMS += rtemsTestHarness.c
+
+PROD_vxWorks = linkTestHarness
+PROD_RTEMS = linkTestHarness
+
+TESTSPEC_vxWorks = linkTestHarness.munch; epicsRunLinkTests
+TESTSPEC_RTEMS = linkTestHarness.boot; epicsRunLinkTests
+
+TESTSCRIPTS_HOST += $(TESTS:%=%.t)
+
+include $(TOP)/configure/RULES
+
+ioRecord$(DEP): $(COMMON_DIR)/ioRecord.h
+lnkStateTest$(DEP): $(COMMON_DIR)/ioRecord.h
diff --git a/src/std/link/test/epicsRunLinkTests.c b/src/std/link/test/epicsRunLinkTests.c
new file mode 100644
index 000000000..a0f3b7c72
--- /dev/null
+++ b/src/std/link/test/epicsRunLinkTests.c
@@ -0,0 +1,29 @@
+/*************************************************************************\
+* Copyright (c) 2018 UChicago Argonne LLC, as Operator of Argonne
+* National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in the file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/*
+ * Run filter tests as a batch.
+ */
+
+#include "epicsUnitTest.h"
+#include "epicsExit.h"
+#include "dbmf.h"
+
+int lnkStateTest(void);
+int lnkCalcTest(void);
+
+void epicsRunLinkTests(void)
+{
+ testHarness();
+
+ runTest(lnkStateTest);
+ runTest(lnkCalcTest);
+
+ dbmfFreeChunks();
+
+ epicsExit(0); /* Trigger test harness */
+}
diff --git a/src/std/link/test/ioRecord.c b/src/std/link/test/ioRecord.c
new file mode 100644
index 000000000..1807c9171
--- /dev/null
+++ b/src/std/link/test/ioRecord.c
@@ -0,0 +1,22 @@
+/*************************************************************************\
+* Copyright (c) 2018 UChicago Argonne LLC, as Operator of Argonne
+* National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in the file LICENSE that is included with this distribution.
+ \*************************************************************************/
+
+/*
+ * Author: Andrew Johnson
+ */
+
+#include
+#include
+
+#define GEN_SIZE_OFFSET
+#include "ioRecord.h"
+#undef GEN_SIZE_OFFSET
+
+#include
+
+static rset ioRSET;
+epicsExportAddress(rset,ioRSET);
diff --git a/src/std/link/test/ioRecord.db b/src/std/link/test/ioRecord.db
new file mode 100644
index 000000000..7a95a1025
--- /dev/null
+++ b/src/std/link/test/ioRecord.db
@@ -0,0 +1 @@
+record(io, io) {}
diff --git a/src/std/link/test/ioRecord.dbd b/src/std/link/test/ioRecord.dbd
new file mode 100644
index 000000000..efaec10a0
--- /dev/null
+++ b/src/std/link/test/ioRecord.dbd
@@ -0,0 +1,14 @@
+# This is a soft record type with both input and output links
+
+recordtype(io) {
+ include "dbCommon.dbd"
+ field(VAL, DBF_LONG) {
+ prompt("Value")
+ }
+ field(INPUT, DBF_INLINK) {
+ prompt("Input Link")
+ }
+ field(OUTPUT, DBF_OUTLINK) {
+ prompt("Output Link")
+ }
+}
diff --git a/src/std/link/test/lnkCalcTest.c b/src/std/link/test/lnkCalcTest.c
new file mode 100644
index 000000000..3fe8382d4
--- /dev/null
+++ b/src/std/link/test/lnkCalcTest.c
@@ -0,0 +1,164 @@
+/*************************************************************************\
+* Copyright (c) 2018 Andrew Johnson
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in the file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#include
+
+#include "dbAccess.h"
+#include "alarm.h"
+#include "dbUnitTest.h"
+#include "errlog.h"
+#include "epicsThread.h"
+#include "dbLink.h"
+#include "dbState.h"
+#include "recGbl.h"
+#include "testMain.h"
+#include "ioRecord.h"
+
+#define testPutLongStr(PV, VAL) \
+ testdbPutArrFieldOk(PV, DBF_CHAR, sizeof(VAL), VAL);
+
+void linkTest_registerRecordDeviceDriver(struct dbBase *);
+
+static void startTestIoc(const char *dbfile)
+{
+ testdbPrepare();
+ testdbReadDatabase("linkTest.dbd", NULL, NULL);
+ linkTest_registerRecordDeviceDriver(pdbbase);
+ testdbReadDatabase(dbfile, NULL, NULL);
+
+ eltc(0);
+ testIocInitOk();
+ eltc(1);
+}
+
+static void testCalc()
+{
+ ioRecord *pio;
+ DBLINK *pinp, *pout;
+ long status;
+ epicsFloat64 f64;
+
+ startTestIoc("ioRecord.db");
+
+ pio = (ioRecord *) testdbRecordPtr("io");
+ pinp = &pio->input;
+ pout = &pio->output;
+
+ testDiag("testing lnkCalc input");
+
+ {
+ dbStateId red;
+
+ testPutLongStr("io.INPUT", "{\"calc\":{"
+ "\"expr\":\"a\","
+ "\"args\":[{\"state\":\"red\"}]"
+ "}}");
+ if (testOk1(pinp->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pinp->value.json.string);
+ red = dbStateFind("red");
+ testOk(!!red, "State red was created");
+
+ dbStateSet(red);
+ status = dbGetLink(pinp, DBF_DOUBLE, &f64, NULL, NULL);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(f64, "Got TRUE (%g)", f64);
+
+ dbStateClear(red);
+ status = dbGetLink(pinp, DBF_DOUBLE, &f64, NULL, NULL);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(!f64, "Got FALSE (%g)", f64);
+ }
+
+ {
+ dbStateId major = dbStateCreate("major");
+ dbStateId minor = dbStateCreate("minor");
+ epicsEnum16 stat, sevr;
+
+ testPutLongStr("io.INPUT", "{\"calc\":{"
+ "\"expr\":\"0\","
+ "\"major\":\"A\","
+ "\"minor\":\"B\","
+ "\"args\":[{\"state\":\"major\"},{\"state\":\"minor\"}]"
+ "}}");
+ if (testOk1(pinp->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pinp->value.json.string);
+
+ dbStateSet(major);
+ dbStateSet(minor);
+ status = dbGetLink(pinp, DBF_DOUBLE, &f64, NULL, NULL);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(f64 == 0.0, "Got zero (%g)", f64);
+ testOk(recGblResetAlarms(pio) & DBE_ALARM, "Record alarm was raised");
+ status = dbGetAlarm(pinp, &stat, &sevr);
+ testOk(!status, "dbGetAlarm succeeded (status = %ld)", status);
+ testOk(stat == LINK_ALARM, "Alarm status = LINK (%d)", stat);
+ testOk(sevr == MAJOR_ALARM, "Alarm severity = MAJOR (%d)", sevr);
+
+ dbStateClear(major);
+ status = dbGetLink(pinp, DBF_DOUBLE, &f64, NULL, NULL);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(recGblResetAlarms(pio) & DBE_ALARM, "Record alarm was raised");
+ status = dbGetAlarm(pinp, &stat, &sevr);
+ testOk(!status, "dbGetAlarm succeeded (status = %ld)", status);
+ testOk(stat == LINK_ALARM, "Alarm status = LINK (%d)", stat);
+ testOk(sevr == MINOR_ALARM, "Alarm severity = MINOR (%d)", sevr);
+ }
+
+ testDiag("testing lnkCalc output");
+
+ {
+ dbStateId red = dbStateFind("red");
+ dbStateId out = dbStateCreate("out");
+
+ testPutLongStr("io.OUTPUT", "{\"calc\":{"
+ "\"expr\":\"!a\","
+ "\"out\":{\"state\":\"out\"},"
+ "\"args\":[{\"state\":\"red\"}],"
+ "\"units\":\"things\","
+ "\"prec\":3"
+ "}}");
+ if (testOk1(pout->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pout->value.json.string);
+
+ dbStateSet(red);
+ f64 = 1.0;
+ status = dbPutLink(pout, DBF_DOUBLE, &f64, 1);
+ testOk(!status, "dbPutLink succeeded (status = %ld)", status);
+ testOk(!dbStateGet(out), "output was cleared");
+
+ dbStateClear(red);
+ status = dbPutLink(pout, DBF_DOUBLE, &f64, 1);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(dbStateGet(out), "output was set");
+ }
+
+ {
+ char units[20] = {0};
+ short prec = 0;
+
+ status = dbGetUnits(pout, units, sizeof(units));
+ testOk(!status, "dbGetUnits succeeded (status = %ld)", status);
+ testOk(!strcmp(units, "things"), "Units string correct (%s)", units);
+
+ status = dbGetPrecision(pout, &prec);
+ testOk(!status, "dbGetPrecision succeeded (status = %ld)", status);
+ testOk(prec == 3, "Precision correct (%d)", prec);
+ }
+
+ testIocShutdownOk();
+
+ testdbCleanup();
+}
+
+
+MAIN(lnkCalcTest)
+{
+ testPlan(0);
+
+ testCalc();
+
+ return testDone();
+}
diff --git a/src/std/link/test/lnkStateTest.c b/src/std/link/test/lnkStateTest.c
new file mode 100644
index 000000000..d3da06f5c
--- /dev/null
+++ b/src/std/link/test/lnkStateTest.c
@@ -0,0 +1,130 @@
+/*************************************************************************\
+* Copyright (c) 2018 Andrew Johnson
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in the file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#include
+
+#include "dbAccess.h"
+#include "alarm.h"
+#include "dbUnitTest.h"
+#include "errlog.h"
+#include "epicsThread.h"
+#include "dbLink.h"
+#include "dbState.h"
+#include "ioRecord.h"
+
+#include "testMain.h"
+
+void linkTest_registerRecordDeviceDriver(struct dbBase *);
+
+static void startTestIoc(const char *dbfile)
+{
+ testdbPrepare();
+ testdbReadDatabase("linkTest.dbd", NULL, NULL);
+ linkTest_registerRecordDeviceDriver(pdbbase);
+ testdbReadDatabase(dbfile, NULL, NULL);
+
+ eltc(0);
+ testIocInitOk();
+ eltc(1);
+}
+
+static void testState()
+{
+ dbStateId red;
+ ioRecord *pio;
+ DBLINK *pinp, *pout;
+ long status;
+
+ testDiag("testing lnkState");
+
+ startTestIoc("ioRecord.db");
+
+ pio = (ioRecord *) testdbRecordPtr("io");
+ pinp = &pio->input;
+ pout = &pio->output;
+
+ red = dbStateFind("red");
+ testOk(!red, "No state red exists");
+
+ testdbPutFieldOk("io.INPUT", DBF_STRING, "{\"state\":\"red\"}");
+ if (testOk1(pinp->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pinp->value.json.string);
+ red = dbStateFind("red");
+ testOk(!!red, "state red exists");
+
+ {
+ epicsInt16 i16;
+
+ dbStateSet(red);
+ status = dbGetLink(pinp, DBF_SHORT, &i16, NULL, NULL);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(i16, "Got TRUE");
+
+ testdbPutFieldOk("io.INPUT", DBF_STRING, "{\"state\":\"!red\"}");
+ if (testOk1(pinp->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pinp->value.json.string);
+
+ status = dbGetLink(pinp, DBF_SHORT, &i16, NULL, NULL);
+ testOk(!status, "dbGetLink succeeded (status = %ld)", status);
+ testOk(!i16, "Got FALSE");
+
+ testdbPutFieldOk("io.OUTPUT", DBF_STRING, "{\"state\":\"red\"}");
+ if (testOk1(pout->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pout->value.json.string);
+
+ i16 = 0;
+ status = dbPutLink(pout, DBF_SHORT, &i16, 1);
+ testOk(!status, "dbPutLink %d succeeded (status = %ld)", i16, status);
+ testOk(!dbStateGet(red), "state was cleared");
+
+ i16 = 0x8000;
+ status = dbPutLink(pout, DBF_SHORT, &i16, 1);
+ testOk(!status, "dbPutLink 0x%hx succeeded (status = %ld)", i16, status);
+ testOk(dbStateGet(red), "state was set");
+ }
+
+ status = dbPutLink(pout, DBF_STRING, "", 1);
+ testOk(!status, "dbPutLink '' succeeded (status = %ld)", status);
+ testOk(!dbStateGet(red), "state was cleared");
+
+ status = dbPutLink(pout, DBF_STRING, "FALSE", 1); /* Not really... */
+ testOk(!status, "dbPutLink 'FALSE' succeeded (status = %ld)", status);
+ testOk(dbStateGet(red), "state was set");
+
+ status = dbPutLink(pout, DBF_STRING, "0", 1);
+ testOk(!status, "dbPutLink '0' succeeded (status = %ld)", status);
+ testOk(!dbStateGet(red), "state was cleared");
+
+ {
+ epicsFloat64 f64 = 0.1;
+
+ status = dbPutLink(pout, DBF_DOUBLE, &f64, 1);
+ testOk(!status, "dbPutLink %g succeeded (status = %ld)", f64, status);
+ testOk(dbStateGet(red), "state was set");
+
+ testdbPutFieldOk("io.OUTPUT", DBF_STRING, "{\"state\":\"!red\"}");
+ if (testOk1(pout->type == JSON_LINK))
+ testDiag("Link was set to '%s'", pout->value.json.string);
+
+ status = dbPutLink(pout, DBF_DOUBLE, &f64, 1);
+ testOk(!status, "dbPutLink %g succeeded (status = %ld)", f64, status);
+ testOk(!dbStateGet(red), "state was cleared");
+ }
+
+ testIocShutdownOk();
+
+ testdbCleanup();
+}
+
+
+MAIN(lnkStateTest)
+{
+ testPlan(28);
+
+ testState();
+
+ return testDone();
+}
diff --git a/src/std/link/test/rtemsTestHarness.c b/src/std/link/test/rtemsTestHarness.c
new file mode 100644
index 000000000..7397f74d6
--- /dev/null
+++ b/src/std/link/test/rtemsTestHarness.c
@@ -0,0 +1,14 @@
+/*************************************************************************\
+* Copyright (c) 2018 UChicago Argonne LLC, as Operator of Argonne
+* National Laboratory.
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in the file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+extern void epicsRunLinkTests(void);
+
+int main(int argc, char **argv)
+{
+ epicsRunLinkTests(); /* calls epicsExit(0) */
+ return 0;
+}