Merge changes from 3.16 branch and below into database/master

Also removes some extraneous template files
This commit is contained in:
Andrew Johnson
2017-11-01 16:16:07 -05:00
51 changed files with 517 additions and 3700 deletions

View File

@@ -410,7 +410,7 @@ long dbd(const char *record_name)
precord = addr.precord;
if (! precord->bkpt & BKPT_ON_MASK) {
if (! (precord->bkpt & BKPT_ON_MASK)) {
printf(" BKPT> No breakpoint set in this record\n");
return(S_db_bkptNotSet);
}

View File

@@ -107,7 +107,7 @@ static int chf_boolean(void * ctx, int boolVal)
return result;
}
static int chf_integer(void * ctx, long integerVal)
static int chf_integer(void * ctx, long long integerVal)
{
parseContext *parser = (parseContext *) ctx;
chFilter *filter = parser->filter;
@@ -132,7 +132,7 @@ static int chf_double(void * ctx, double doubleVal)
}
static int chf_string(void * ctx, const unsigned char * stringVal,
unsigned int stringLen)
size_t stringLen)
{
parseContext *parser = (parseContext *) ctx;
chFilter *filter = parser->filter;
@@ -159,7 +159,7 @@ static int chf_start_map(void * ctx)
}
static int chf_map_key(void * ctx, const unsigned char * key,
unsigned int stringLen)
size_t stringLen)
{
parseContext *parser = (parseContext *) ctx;
chFilter *filter = parser->filter;
@@ -243,15 +243,12 @@ static const yajl_callbacks chf_callbacks =
{ chf_null, chf_boolean, chf_integer, chf_double, NULL, chf_string,
chf_start_map, chf_map_key, chf_end_map, chf_start_array, chf_end_array };
static const yajl_parser_config chf_config =
{ 0, 1 }; /* allowComments = NO , checkUTF8 = YES */
static void * chf_malloc(void *ctx, unsigned int sz)
static void * chf_malloc(void *ctx, size_t sz)
{
return malloc(sz);
}
static void * chf_realloc(void *ctx, void *ptr, unsigned int sz)
static void * chf_realloc(void *ctx, void *ptr, size_t sz)
{
return realloc(ptr, sz);
}
@@ -261,36 +258,38 @@ static void chf_free(void *ctx, void *ptr)
free(ptr);
}
static const yajl_alloc_funcs chf_alloc =
static yajl_alloc_funcs chf_alloc =
{ chf_malloc, chf_realloc, chf_free };
static long chf_parse(dbChannel *chan, const char **pjson)
{
parseContext parser =
{ chan, NULL, 0 };
yajl_handle yh = yajl_alloc(&chf_callbacks, &chf_config, &chf_alloc, &parser);
yajl_handle yh = yajl_alloc(&chf_callbacks, &chf_alloc, &parser);
const char *json = *pjson;
size_t jlen = strlen(json);
size_t jlen = strlen(json), ylen;
yajl_status ys;
long status;
if (!yh)
return S_db_noMemory;
ys = yajl_parse(yh, (const unsigned char *) json, (unsigned int) jlen);
if (ys == yajl_status_insufficient_data)
ys = yajl_parse_complete(yh);
ys = yajl_parse(yh, (const unsigned char *) json, jlen);
ylen = yajl_get_bytes_consumed(yh);
if (ys == yajl_status_ok)
ys = yajl_complete_parse(yh);
switch (ys) {
case yajl_status_ok:
*pjson += ylen;
status = 0;
*pjson += yajl_get_bytes_consumed(yh);
break;
case yajl_status_error: {
unsigned char *err;
err = yajl_get_error(yh, 1, (const unsigned char *) json, (unsigned int) jlen);
err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen);
printf("dbChannelCreate: %s\n", err);
yajl_free_error(yh, err);
} /* fall through */

View File

@@ -37,20 +37,20 @@ static int dbcj_boolean(void *ctx, int val) {
return 0; /* Illegal */
}
static int dbcj_integer(void *ctx, long num) {
static int dbcj_integer(void *ctx, long long num) {
parseContext *parser = (parseContext *) ctx;
epicsInt32 val32 = num;
FASTCONVERT conv = dbFastPutConvertRoutine[DBF_LONG][parser->dbrType];
epicsInt64 val64 = num;
FASTCONVERT conv = dbFastPutConvertRoutine[DBF_INT64][parser->dbrType];
if (parser->elems > 0) {
conv(&val32, parser->pdest, NULL);
conv(&val64, parser->pdest, NULL);
parser->pdest += parser->dbrSize;
parser->elems--;
}
return 1;
}
static int dblsj_integer(void *ctx, long num) {
static int dblsj_integer(void *ctx, long long num) {
return 0; /* Illegal */
}
@@ -70,7 +70,7 @@ static int dblsj_double(void *ctx, double num) {
return 0; /* Illegal */
}
static int dbcj_string(void *ctx, const unsigned char *val, unsigned int len) {
static int dbcj_string(void *ctx, const unsigned char *val, size_t len) {
parseContext *parser = (parseContext *) ctx;
char *pdest = parser->pdest;
@@ -93,7 +93,7 @@ static int dbcj_string(void *ctx, const unsigned char *val, unsigned int len) {
return 1;
}
static int dblsj_string(void *ctx, const unsigned char *val, unsigned int len) {
static int dblsj_string(void *ctx, const unsigned char *val, size_t len) {
parseContext *parser = (parseContext *) ctx;
char *pdest = parser->pdest;
@@ -118,7 +118,7 @@ static int dbcj_start_map(void *ctx) {
return 0; /* Illegal */
}
static int dbcj_map_key(void *ctx, const unsigned char *key, unsigned int len) {
static int dbcj_map_key(void *ctx, const unsigned char *key, size_t len) {
return 0; /* Illegal */
}
@@ -149,9 +149,6 @@ static yajl_callbacks dbcj_callbacks = {
dbcj_start_array, dbcj_end_array
};
static const yajl_parser_config dbcj_config =
{ 0, 0 }; /* allowComments = NO, checkUTF8 = NO */
long dbPutConvertJSON(const char *json, short dbrType,
void *pdest, long *pnRequest)
{
@@ -169,13 +166,13 @@ long dbPutConvertJSON(const char *json, short dbrType,
parser->elems = *pnRequest;
yajl_set_default_alloc_funcs(&dbcj_alloc);
yh = yajl_alloc(&dbcj_callbacks, &dbcj_config, &dbcj_alloc, parser);
yh = yajl_alloc(&dbcj_callbacks, &dbcj_alloc, parser);
if (!yh)
return S_db_noMemory;
ys = yajl_parse(yh, (const unsigned char *) json, (unsigned int) jlen);
if (ys == yajl_status_insufficient_data)
ys = yajl_parse_complete(yh);
ys = yajl_parse(yh, (const unsigned char *) json, jlen);
if (ys == yajl_status_ok)
ys = yajl_complete_parse(yh);
switch (ys) {
case yajl_status_ok:
@@ -185,7 +182,7 @@ long dbPutConvertJSON(const char *json, short dbrType,
case yajl_status_error: {
unsigned char *err = yajl_get_error(yh, 1,
(const unsigned char *) json, (unsigned int) jlen);
(const unsigned char *) json, jlen);
fprintf(stderr, "dbConvertJSON: %s\n", err);
yajl_free_error(yh, err);
}
@@ -227,13 +224,11 @@ long dbLSConvertJSON(const char *json, char *pdest, epicsUInt32 size,
parser->elems = 1;
yajl_set_default_alloc_funcs(&dbcj_alloc);
yh = yajl_alloc(&dblsj_callbacks, &dbcj_config, &dbcj_alloc, parser);
yh = yajl_alloc(&dblsj_callbacks, &dbcj_alloc, parser);
if (!yh)
return S_db_noMemory;
ys = yajl_parse(yh, (const unsigned char *) json, (unsigned int) jlen);
if (ys == yajl_status_insufficient_data)
ys = yajl_parse_complete(yh);
ys = yajl_parse(yh, (const unsigned char *) json, jlen);
switch (ys) {
case yajl_status_ok:
@@ -243,7 +238,7 @@ long dbLSConvertJSON(const char *json, char *pdest, epicsUInt32 size,
case yajl_status_error: {
unsigned char *err = yajl_get_error(yh, 1,
(const unsigned char *) json, (unsigned int) jlen);
(const unsigned char *) json, jlen);
fprintf(stderr, "dbLoadLS_JSON: %s\n", err);
yajl_free_error(yh, err);
}

View File

@@ -920,18 +920,14 @@ static long cvt_q_q(
epicsInt64 *from,
epicsInt64 *to,
const dbAddr *paddr)
{
*to=*from; return(0); }
{ *to=*from; return(0); }
/* Convert Int64 to UInt64 */
static long cvt_q_uq(
epicsInt64 *from,
epicsUInt64 *to,
const dbAddr *paddr)
{
*to=*from; return(0); }
{ *to=*from; return(0); }
/* Convert Int64 to Float */
static long cvt_q_f(
@@ -949,7 +945,7 @@ static long cvt_q_d(
/* Convert Int64 to Enumerated */
static long cvt_q_e(
epicsInt32 *from,
epicsInt64 *from,
epicsEnum16 *to,
const dbAddr *paddr)
{ *to=*from; return(0); }

View File

@@ -112,12 +112,12 @@ static int dbjl_boolean(void *ctx, int val) {
CALL_OR_STOP(pjlink->pif->parse_boolean)(pjlink, val));
}
static int dbjl_integer(void *ctx, long num) {
static int dbjl_integer(void *ctx, long long num) {
parseContext *parser = (parseContext *) ctx;
jlink *pjlink = parser->pjlink;
IFDEBUG(10)
printf("dbjl_integer(%s@%p, %ld)\n",
printf("dbjl_integer(%s@%p, %lld)\n",
pjlink->pif->name, pjlink, num);
assert(pjlink);
@@ -138,13 +138,13 @@ static int dbjl_double(void *ctx, double num) {
CALL_OR_STOP(pjlink->pif->parse_double)(pjlink, num));
}
static int dbjl_string(void *ctx, const unsigned char *val, unsigned len) {
static int dbjl_string(void *ctx, const unsigned char *val, size_t len) {
parseContext *parser = (parseContext *) ctx;
jlink *pjlink = parser->pjlink;
IFDEBUG(10)
printf("dbjl_string(%s@%p, \"%.*s\")\n",
pjlink->pif->name, pjlink, len, val);
pjlink->pif->name, pjlink, (int) len, val);
assert(pjlink);
return dbjl_value(parser,
@@ -190,7 +190,7 @@ static int dbjl_start_map(void *ctx) {
return dbjl_return(parser, result);
}
static int dbjl_map_key(void *ctx, const unsigned char *key, unsigned len) {
static int dbjl_map_key(void *ctx, const unsigned char *key, size_t len) {
parseContext *parser = (parseContext *) ctx;
jlink *pjlink = parser->pjlink;
char *link_name;
@@ -200,13 +200,13 @@ static int dbjl_map_key(void *ctx, const unsigned char *key, unsigned len) {
if (!parser->key_is_link) {
if (!pjlink) {
errlogPrintf("dbJLinkInit: Illegal second link key '%.*s'\n",
len, key);
(int) len, key);
return dbjl_return(parser, jlif_stop);
}
IFDEBUG(10) {
printf("dbjl_map_key(%s@%p, \"%.*s\")\t",
pjlink->pif->name, pjlink, len, key);
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);
}
@@ -218,7 +218,7 @@ static int dbjl_map_key(void *ctx, const unsigned char *key, unsigned len) {
}
IFDEBUG(10) {
printf("dbjl_map_key(NULL, \"%.*s\")\t", len, key);
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);
}
@@ -334,9 +334,6 @@ static yajl_callbacks dbjl_callbacks = {
dbjl_start_map, dbjl_map_key, dbjl_end_map, dbjl_start_array, dbjl_end_array
};
static const yajl_parser_config dbjl_config =
{ 0, 0 }; /* allowComments = NO, checkUTF8 = NO */
long dbJLinkParse(const char *json, size_t jlen, short dbfType,
jlink **ppjlink, unsigned opts)
{
@@ -363,13 +360,13 @@ long dbJLinkParse(const char *json, size_t jlen, short dbfType,
parser->jsonDepth, parser->key_is_link);
yajl_set_default_alloc_funcs(&dbjl_allocs);
yh = yajl_alloc(&dbjl_callbacks, &dbjl_config, &dbjl_allocs, parser);
yh = yajl_alloc(&dbjl_callbacks, &dbjl_allocs, parser);
if (!yh)
return S_db_noMemory;
ys = yajl_parse(yh, (const unsigned char *) json, (unsigned) jlen);
if (ys == yajl_status_insufficient_data)
ys = yajl_parse_complete(yh);
ys = yajl_parse(yh, (const unsigned char *) json, jlen);
if (ys == yajl_status_ok)
ys = yajl_complete_parse(yh);
switch (ys) {
unsigned char *err;
@@ -381,10 +378,11 @@ long dbJLinkParse(const char *json, size_t jlen, short dbfType,
break;
case yajl_status_error:
err = yajl_get_error(yh, 1, (const unsigned char *) json, (unsigned) jlen);
err = yajl_get_error(yh, 1, (const unsigned char *) json, jlen);
errlogPrintf("dbJLinkInit: %s\n", err);
yajl_free_error(yh, err);
dbJLinkFree(parser->pjlink);
dbJLinkFree(parser->product);
/* fall through */
default:
status = S_db_badField;

View File

@@ -66,6 +66,26 @@ static const char * link_field_name(const struct link *plink)
return "????";
}
/* Special TSEL handler for PV links */
/* FIXME: Generalize for new link types... */
static void TSEL_modified(struct link *plink)
{
struct pv_link *ppv_link;
char *pfieldname;
if (plink->type != PV_LINK) {
errlogPrintf("dbLink::TSEL_modified called for non PV_LINK\n");
return;
}
/* If pvname contains .TIME truncate it to point to VAL instead */
ppv_link = &plink->value.pv_link;
pfieldname = strstr(ppv_link->pvname, ".TIME");
if (pfieldname) {
*pfieldname = 0;
plink->flags |= DBLINK_FLAG_TSELisTIME;
}
}
/***************************** Generic Link API *****************************/
@@ -93,7 +113,7 @@ void dbInitLink(struct link *plink, short dbfType)
return;
if (plink == &precord->tsel)
recGblTSELwasModified(plink);
TSEL_modified(plink);
if (!(plink->value.pv_link.pvlMask & (pvlOptCA | pvlOptCP | pvlOptCPP))) {
/* Make it a DB link if possible */
@@ -127,6 +147,9 @@ void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
{
struct dbCommon *precord = plink->precord;
/* Clear old TSELisTIME flag */
plink->flags &= ~DBLINK_FLAG_TSELisTIME;
if (plink->type == CONSTANT) {
dbConstAddLink(plink);
return;
@@ -145,7 +168,7 @@ void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
return;
if (plink == &precord->tsel)
recGblTSELwasModified(plink);
TSEL_modified(plink);
if (ptarget) {
/* It's a DB link */
@@ -470,4 +493,3 @@ long dbPutLinkLS(struct link *plink, char *pbuffer, epicsUInt32 len)
return dbPutLink(plink, DBR_STRING, pbuffer, 1);
}

View File

@@ -150,7 +150,7 @@ long testdbVPutField(const char* pv, short dbrType, va_list ap)
return dbPutField(&addr, dbrType, pod.bytes, 1);
}
void testdbPutFieldOk(const char* pv, short dbrType, ...)
void testdbPutFieldOk(const char* pv, int dbrType, ...)
{
long ret;
va_list ap;
@@ -162,7 +162,7 @@ void testdbPutFieldOk(const char* pv, short dbrType, ...)
testOk(ret==0, "dbPutField(\"%s\", %d, ...) -> %#lx (%s)", pv, dbrType, ret, errSymMsg(ret));
}
void testdbPutFieldFail(long status, const char* pv, short dbrType, ...)
void testdbPutFieldFail(long status, const char* pv, int dbrType, ...)
{
long ret;
va_list ap;
@@ -175,7 +175,7 @@ void testdbPutFieldFail(long status, const char* pv, short dbrType, ...)
pv, dbrType, status, errSymMsg(status), ret, errSymMsg(ret));
}
void testdbGetFieldEqual(const char* pv, short dbrType, ...)
void testdbGetFieldEqual(const char* pv, int dbrType, ...)
{
va_list ap;

View File

@@ -48,13 +48,13 @@ epicsShareFunc void testdbCleanup(void);
* testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1);
* testdbPutFieldOk("pvname", DBF_STRING, "hello world");
*/
epicsShareFunc void testdbPutFieldOk(const char* pv, short dbrType, ...);
epicsShareFunc void testdbPutFieldOk(const char* pv, int dbrType, ...);
/* Tests for put failure */
epicsShareFunc void testdbPutFieldFail(long status, const char* pv, short dbrType, ...);
epicsShareFunc void testdbPutFieldFail(long status, const char* pv, int dbrType, ...);
epicsShareFunc long testdbVPutField(const char* pv, short dbrType, va_list ap);
epicsShareFunc void testdbGetFieldEqual(const char* pv, short dbrType, ...);
epicsShareFunc void testdbGetFieldEqual(const char* pv, int dbrType, ...);
epicsShareFunc void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap);
epicsShareFunc void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf);

View File

@@ -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 file LICENSE that is included with this distribution.
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* recGbl.c */
/*
@@ -259,15 +259,13 @@ void recGblGetTimeStamp(void *pvoid)
struct link *plink = &prec->tsel;
if (!dbLinkIsConstant(plink)) {
struct pv_link *ppv_link = &plink->value.pv_link;
if (ppv_link->pvlMask & pvlOptTSELisTime) {
if (plink->flags & DBLINK_FLAG_TSELisTIME) {
if (dbGetTimeStamp(plink, &prec->time))
errlogPrintf("recGblGetTimeStamp: dbGetTimeStamp failed, %s.TSEL = %s\n",
prec->name, ppv_link->pvname);
errlogPrintf("recGblGetTimeStamp: dbGetTimeStamp failed for %s.TSEL",
prec->name);
return;
}
dbGetLink(&prec->tsel, DBR_SHORT, &prec->tse, 0, 0);
dbGetLink(plink, DBR_SHORT, &prec->tse, 0, 0);
}
if (prec->tse != epicsTimeEventDeviceTime) {
if (epicsTimeGetEvent(&prec->time, prec->tse))
@@ -276,24 +274,6 @@ void recGblGetTimeStamp(void *pvoid)
}
}
void recGblTSELwasModified(struct link *plink)
{
struct pv_link *ppv_link = &plink->value.pv_link;
char *pfieldname;
if (plink->type != PV_LINK) {
errlogPrintf("recGblTSELwasModified called for non PV_LINK\n");
return;
}
/*If pvname ends in .TIME then just ask for VAL*/
/*Note that the VAL value will not be used*/
pfieldname = strstr(ppv_link->pvname, ".TIME");
if (pfieldname) {
*pfieldname = 0;
ppv_link->pvlMask |= pvlOptTSELisTime;
}
}
void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval,
const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask)
{

View File

@@ -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 file LICENSE that is included with this distribution.
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/* recGbl.h */
/* Record Global
@@ -63,7 +63,6 @@ epicsShareFunc void recGblInheritSevr(int msMode, void *precord, epicsEnum16 sta
epicsEnum16 sevr);
epicsShareFunc void recGblFwdLink(void *precord);
epicsShareFunc void recGblGetTimeStamp(void *precord);
epicsShareFunc void recGblTSELwasModified(struct link *plink);
epicsShareFunc void recGblCheckDeadband(epicsFloat64 *poldval, const epicsFloat64 newval,
const epicsFloat64 deadband, unsigned *monitor_mask, const unsigned add_mask);

View File

@@ -1940,7 +1940,7 @@ char * dbGetString(DBENTRY *pdbentry)
else ppind=0;
dbMsgPrint(pdbentry, "%s%s%s%s",
plink->value.pv_link.pvname ? plink->value.pv_link.pvname : "",
(pvlMask & pvlOptTSELisTime) ? ".TIME" : "",
(plink->flags & DBLINK_FLAG_TSELisTIME) ? ".TIME" : "",
ppstring[ppind],
msstring[plink->value.pv_link.pvlMask&pvlOptMsMode]);
break;

View File

@@ -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 file LICENSE that is included with this distribution.
* in file LICENSE that is included with this distribution.
\*************************************************************************/
%{
static int yyerror();
@@ -97,7 +97,7 @@ choice: tokenCHOICE '(' tokenSTRING ',' tokenSTRING ')'
{
if(dbStaticDebug>2) printf("choice %s %s\n",$3,$5);
dbMenuChoice($3,$5); dbmfFree($3); dbmfFree($5);
}
}
| include;
recordtype_head: '(' tokenSTRING ')'
@@ -139,12 +139,12 @@ recordtype_field_body: '{' recordtype_field_item_list '}' ;
recordtype_field_item_list: recordtype_field_item_list recordtype_field_item
| recordtype_field_item;
recordtype_field_item: tokenSTRING '(' tokenSTRING ')'
recordtype_field_item: tokenSTRING '(' tokenSTRING ')'
{
if(dbStaticDebug>2) printf("recordtype_field_item %s %s\n",$1,$3);
dbRecordtypeFieldItem($1,$3); dbmfFree($1); dbmfFree($3);
}
| tokenMENU '(' tokenSTRING ')'
| tokenMENU '(' tokenSTRING ')'
{
if(dbStaticDebug>2) printf("recordtype_field_item %s (%s)\n","menu",$3);
@@ -154,7 +154,7 @@ recordtype_field_item: tokenSTRING '(' tokenSTRING ')'
device: tokenDEVICE '('
tokenSTRING ',' tokenSTRING ',' tokenSTRING ',' tokenSTRING ')'
{
{
if(dbStaticDebug>2) printf("device %s %s %s %s\n",$3,$5,$7,$9);
dbDevice($3,$5,$7,$9);
dbmfFree($3); dbmfFree($5);
@@ -290,6 +290,7 @@ json_object: '{' '}'
};
json_members: json_pair
| json_pair ','
| json_pair ',' json_members
{
$$ = dbmfStrcat3($1, ",", $3);
@@ -325,6 +326,14 @@ json_array: '[' ']'
};
json_elements: json_value
| json_value ','
{ /* Retain the trailing ',' so link parser can distinguish a
* 1-element const list from a PV name (commas are illegal)
*/
$$ = dbmfStrcat3($1, ",", "");
dbmfFree($1);
if (dbStaticDebug>2) printf("json %s\n", $$);
};
| json_value ',' json_elements
{
$$ = dbmfStrcat3($1, ",", $3);
@@ -342,7 +351,7 @@ json_value: jsonNULL { $$ = dbmfStrdup("null"); }
%%
#include "dbLex.c"
@@ -363,7 +372,7 @@ static long pvt_yy_parse(void)
{
static int FirstFlag = 1;
long rtnval;
if (!FirstFlag) {
yyAbort = FALSE;
yyFailed = FALSE;

View File

@@ -67,10 +67,10 @@ epicsShareExtern maplinkType pamaplinkType[];
#define pvlOptInpString 0x100 /*Input as string*/
#define pvlOptOutNative 0x200 /*Output native*/
#define pvlOptOutString 0x400 /*Output as string*/
#define pvlOptTSELisTime 0x800 /*Field TSEL is getting timeStamp*/
/* DBLINK Flag bits */
#define DBLINK_FLAG_INITIALIZED 1 /* dbInitLink() called */
#define DBLINK_FLAG_TSELisTIME 2 /* Use TSEL to get timeStamp */
struct macro_link {
char *macroStr;

View File

@@ -59,7 +59,7 @@ void camsgtask ( void *pParm )
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf("CAS: ioctl error - %s\n",
errlogPrintf("CAS: FIONREAD error: %s\n",
sockErrBuf);
cas_send_bs_msg(client, TRUE);
}

View File

@@ -114,7 +114,7 @@ void cas_send_bs_msg ( struct client *pclient, int lock_needed )
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf ( "CAS: TCP send to %s failed - %s\n",
errlogPrintf ( "CAS: TCP send to %s failed: %s\n",
buf, sockErrBuf);
}
pclient->disconnect = TRUE;
@@ -140,7 +140,7 @@ void cas_send_bs_msg ( struct client *pclient, int lock_needed )
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf ("CAS: Socket shutdown error - %s\n",
errlogPrintf ("CAS: Socket shutdown error: %s\n",
sockErrBuf );
}
}
@@ -218,7 +218,7 @@ void cas_send_dg_msg ( struct client * pclient )
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
ipAddrToDottedIP ( &pclient->addr, buf, sizeof(buf) );
errlogPrintf( "CAS: UDP send to %s failed - %s\n",
errlogPrintf( "CAS: UDP send to %s failed: %s\n",
buf, sockErrBuf);
}

View File

@@ -73,7 +73,7 @@ static void req_server (void *pParm)
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf ( "CAS: Listen error %s\n",
errlogPrintf ( "CAS: Listen error: %s\n",
sockErrBuf );
epicsSocketDestroy (IOC_sock);
epicsThreadSuspendSelf ();
@@ -95,7 +95,7 @@ static void req_server (void *pParm)
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf("CAS: Client accept error was \"%s\"\n",
errlogPrintf("CAS: Client accept error: %s\n",
sockErrBuf );
epicsThreadSleep(15.0);
continue;
@@ -140,7 +140,7 @@ int tryBind(SOCKET sock, const osiSockAddr* addr, const char *name)
{
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf ( "CAS: %s bind error: \"%s\"\n",
errlogPrintf ( "CAS: %s bind error: %s\n",
name, sockErrBuf );
epicsThreadSuspendSelf ();
}
@@ -205,7 +205,7 @@ SOCKET* rsrv_grab_tcp(unsigned short *port)
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf ( "CAS: getsockname error was \"%s\"\n",
errlogPrintf ( "CAS: getsockname error: %s\n",
sockErrBuf );
epicsThreadSuspendSelf ();
ok = 0;
@@ -244,7 +244,7 @@ SOCKET* rsrv_grab_tcp(unsigned short *port)
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
ipAddrToDottedIP(&scratch.ia, name, sizeof(name));
cantProceed( "CAS: Socket bind %s error was %s\n",
cantProceed( "CAS: Socket bind %s error: %s\n",
name, sockErrBuf );
}
ok = 0;
@@ -310,13 +310,14 @@ void rsrv_build_addr_lists(void)
}
#ifdef IP_ADD_MEMBERSHIP
{
int flag = 1;
osiSockOptMcastLoop_t flag = 1;
if (setsockopt(beaconSocket, IPPROTO_IP, IP_MULTICAST_LOOP,
(char *)&flag, sizeof(flag))<0) {
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
errlogPrintf("rsrv: failed to set mcast loopback\n");
errlogPrintf("CAS: failed to set mcast loopback: %s\n",
sockErrBuf);
}
}
#endif
@@ -665,7 +666,7 @@ int rsrv_init (void)
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
ipAddrToDottedIP (&temp, name, sizeof(name));
fprintf(stderr, "CAS: Socket mcast join %s to %s failed with \"%s\"\n",
errlogPrintf("CAS: Socket mcast join %s to %s failed: %s\n",
ifaceName, name, sockErrBuf );
}
}

View File

@@ -176,7 +176,7 @@ void cast_server(void *pParm)
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
epicsPrintf ("CAS: UDP recv error (errno=%s)\n",
epicsPrintf ("CAS: UDP recv error: %s\n",
sockErrBuf);
epicsThreadSleep(1.0);
}

View File

@@ -87,8 +87,8 @@ void rsrv_online_notify_task(void *pParm)
char sockErrBuf[64];
epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
ipAddrToDottedIP (&pAddr->addr.ia, buf, sizeof(buf));
errlogPrintf ( "%s: CA beacon (send to \"%s\") error was \"%s\"\n",
__FILE__, buf, sockErrBuf);
errlogPrintf ( "CAS: CA beacon send to %s error: %s\n",
buf, sockErrBuf);
}
else {
assert (status == sizeof(msg));

View File

@@ -114,6 +114,14 @@ expression. Equivalent to the C<EGU> field of a record.
An optional integer specifying the numeric precision with which the calculation
result should be displayed. Equivalent to the C<PREC> field of a record.
=item time
An optional string containing a single upper or lower-case letter C<A> ... C<L>
which must correspond to an input provided in the c<args> parameter. When the
record containing such a link has C<TSEL> set to -2 (epicsTimeEventDeviceTime)
the record's timestamp field C<TIME> will be read from the indicated input link
atomically with the value of the input argument.
=back
=head4 Example

View File

@@ -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.
\*************************************************************************/
/* lnkCalc.c */
@@ -18,6 +18,7 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "alarm.h"
#include "dbDefs.h"
@@ -26,6 +27,7 @@
#include "epicsString.h"
#include "epicsTypes.h"
#include "dbAccessDefs.h"
#include "dbCommon.h"
#include "dbConvertFast.h"
#include "dbLink.h"
#include "dbJLink.h"
@@ -49,6 +51,7 @@ typedef struct calc_link {
ps_args,
ps_prec,
ps_units,
ps_time,
ps_error
} pstate;
epicsEnum16 stat;
@@ -61,6 +64,7 @@ typedef struct calc_link {
char *post_major;
char *post_minor;
char *units;
short tinp;
struct link inp[CALCPERFORM_NARGS];
double arg[CALCPERFORM_NARGS];
double val;
@@ -81,6 +85,7 @@ static jlink* lnkCalc_alloc(short dbfType)
clink->nArgs = 0;
clink->pstate = ps_init;
clink->prec = 15; /* standard value for a double */
clink->tinp = -1;
IFDEBUG(10)
printf("lnkCalc_alloc -> calc@%p\n", clink);
@@ -174,6 +179,16 @@ static jlif_result lnkCalc_string(jlink *pjlink, const char *val, size_t len)
return jlif_continue;
}
if (clink->pstate == ps_time) {
char tinp;
if (len != 1 || (tinp = toupper(val[0])) < 'A' || tinp > 'L') {
errlogPrintf("lnkCalc: Bad 'time' parameter \"%.*s\"\n", (int) len, val);
return jlif_stop;
}
clink->tinp = tinp - 'A';
return jlif_continue;
}
if (clink->pstate < ps_expr || clink->pstate > ps_minor) {
errlogPrintf("lnkCalc: Unexpected string \"%.*s\"\n", (int) len, val);
return jlif_stop;
@@ -247,6 +262,8 @@ static jlif_result lnkCalc_map_key(jlink *pjlink, const char *key, size_t len)
clink->pstate = ps_args;
else if (!strncmp(key, "prec", len))
clink->pstate = ps_prec;
else if (!strncmp(key, "time", len))
clink->pstate = ps_time;
else {
errlogPrintf("lnkCalc: Unknown key \"%.4s\"\n", key);
return jlif_stop;
@@ -371,6 +388,10 @@ 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');
for (i = 0; i < clink->nArgs; i++) {
struct link *plink = &clink->inp[i];
jlink *child = plink->type == JSON_LINK ?
@@ -502,11 +523,30 @@ static long lnkCalc_getElements(const struct link *plink, long *nelements)
return 0;
}
/* Get value and timestamp atomically for link indicated by time */
struct lcvt {
double *pval;
epicsTimeStamp *ptime;
};
static long readLocked(struct link *pinp, void *vvt)
{
struct lcvt *pvt = (struct lcvt *) vvt;
long nReq = 1;
long status = dbGetLink(pinp, DBR_DOUBLE, pvt->pval, NULL, &nReq);
if (!status && pvt->ptime)
dbGetTimeStamp(pinp, pvt->ptime);
return status;
}
static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer,
long *pnRequest)
{
calc_link *clink = CONTAINER(plink->value.json.jlink,
struct calc_link, jlink);
dbCommon *prec = plink->precord;
int i;
long status;
FASTCONVERT conv = dbFastPutConvertRoutine[DBR_DOUBLE][dbrType];
@@ -515,12 +555,22 @@ static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer,
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;
dbGetLink(child, DBR_DOUBLE, &clink->arg[i], NULL, &nReq);
/* Any errors have already triggered a LINK/INVALID alarm */
if (i == clink->tinp &&
dbLinkIsConstant(&prec->tsel) &&
prec->tse == epicsTimeEventDeviceTime) {
struct lcvt vt = {&clink->arg[i], &prec->time};
status = dbLinkDoLocked(child, readLocked, &vt);
if (status == S_db_noLSET)
status = readLocked(child, &vt);
}
else
dbGetLink(child, DBR_DOUBLE, &clink->arg[i], NULL, &nReq);
}
clink->stat = 0;
clink->sevr = 0;
@@ -545,7 +595,7 @@ static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer,
if (!status && alval) {
clink->stat = LINK_ALARM;
clink->sevr = MAJOR_ALARM;
recGblSetSevr(plink->precord, clink->stat, clink->sevr);
recGblSetSevr(prec, clink->stat, clink->sevr);
}
}
@@ -556,7 +606,7 @@ static long lnkCalc_getValue(struct link *plink, short dbrType, void *pbuffer,
if (!status && alval) {
clink->stat = LINK_ALARM;
clink->sevr = MINOR_ALARM;
recGblSetSevr(plink->precord, clink->stat, clink->sevr);
recGblSetSevr(prec, clink->stat, clink->sevr);
}
}
@@ -639,4 +689,3 @@ static jlif lnkCalcIf = {
lnkCalc_report, lnkCalc_map_children
};
epicsExportAddress(jlif, lnkCalcIf);

View File

@@ -22,20 +22,20 @@
#include "epicsExport.h"
#define IFDEBUG(n) if(clink->jlink.debug)
#define IFDEBUG(n) if (clink->jlink.debug)
typedef long (*FASTCONVERT)();
typedef struct const_link {
jlink jlink; /* embedded object */
int nElems;
enum {s0, si32, sf64, sc40, a0, ai32, af64, ac40} type;
enum {s0, si64, sf64, sc40, a0, ai64, af64, ac40} type;
union {
epicsInt32 scalar_integer; /* si32 */
epicsInt64 scalar_integer; /* si64 */
epicsFloat64 scalar_double; /* sf64 */
char *scalar_string; /* sc40 */
void *pmem;
epicsInt32 *pintegers; /* ai32 */
epicsInt64 *pintegers; /* ai64 */
epicsFloat64 *pdoubles; /* af64 */
char **pstrings; /* ac40 */
} value;
@@ -77,13 +77,13 @@ static void lnkConst_free(jlink *pjlink)
free(clink->value.pstrings[i]);
/* fall through */
case sc40:
case ai32:
case ai64:
case af64:
free(clink->value.pmem);
break;
case s0:
case a0:
case si32:
case si64:
case sf64:
break;
}
@@ -102,20 +102,24 @@ static jlif_result lnkConst_integer(jlink *pjlink, long long num)
void *buf;
case s0:
clink->type = si32;
clink->type = si64;
clink->value.scalar_integer = num;
IFDEBUG(12)
printf(" si64 := %lld\n", num);
break;
case a0:
clink->type = ai32;
clink->type = ai64;
/* fall through */
case ai32:
buf = realloc(clink->value.pmem, newElems * sizeof(epicsInt32));
case ai64:
buf = realloc(clink->value.pmem, newElems * sizeof(epicsInt64));
if (!buf)
return jlif_stop;
clink->value.pmem = buf;
clink->value.pintegers[clink->nElems] = num;
IFDEBUG(12)
printf(" ai64 += %lld\n", num);
break;
case af64:
@@ -125,6 +129,8 @@ static jlif_result lnkConst_integer(jlink *pjlink, long long num)
clink->value.pmem = buf;
clink->value.pdoubles[clink->nElems] = num;
IFDEBUG(12)
printf(" af64 += %lld\n", num);
break;
case ac40:
@@ -176,7 +182,7 @@ static jlif_result lnkConst_double(jlink *pjlink, double num)
clink->value.pdoubles = f64buf;
break;
case ai32: /* promote earlier ai32 values to af64 */
case ai64: /* promote earlier ai64 values to af64 */
f64buf = calloc(newElems, sizeof(epicsFloat64));
if (!f64buf)
return jlif_stop;
@@ -241,7 +247,7 @@ static jlif_result lnkConst_string(jlink *pjlink, const char *val, size_t len)
break;
case af64:
case ai32:
case ai64:
errlogPrintf("lnkConst: Mixed data types in array\n");
/* fall thorough */
default:
@@ -328,10 +334,10 @@ static void lnkConst_report(const jlink *pjlink, int level, int indent)
int i;
switch (clink->type) {
case ai32:
printf("\n%*s[%d", indent+2, "", clink->value.pintegers[0]);
case ai64:
printf("\n%*s[%lld", indent+2, "", clink->value.pintegers[0]);
for (i = 1; i < clink->nElems; i++) {
printf(", %d", clink->value.pintegers[i]);
printf(", %lld", clink->value.pintegers[i]);
}
break;
case af64:
@@ -357,8 +363,8 @@ static void lnkConst_report(const jlink *pjlink, int level, int indent)
printf("%*s'const': %s", indent, "", dtype);
switch (clink->type) {
case si32:
printf(" %d\n", clink->value.scalar_integer);
case si64:
printf(" %lld\n", clink->value.scalar_integer);
return;
case sf64:
printf(" %g\n", clink->value.scalar_double);
@@ -394,37 +400,51 @@ static long lnkConst_loadScalar(struct link *plink, short dbrType, void *pbuffer
clink, dbrType, pbuffer);
switch (clink->type) {
case si32:
status = dbFastPutConvertRoutine[DBF_LONG][dbrType]
case si64:
IFDEBUG(12)
printf(" si64 %lld\n", clink->value.scalar_integer);
status = dbFastPutConvertRoutine[DBF_INT64][dbrType]
(&clink->value.scalar_integer, pbuffer, NULL);
break;
case sf64:
IFDEBUG(12)
printf(" sf64 %g\n", clink->value.scalar_double);
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
(&clink->value.scalar_double, pbuffer, NULL);
break;
case sc40:
IFDEBUG(12)
printf(" sc40 '%s'\n", clink->value.scalar_string);
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
(clink->value.scalar_string, pbuffer, NULL);
break;
case ai32:
status = dbFastPutConvertRoutine[DBF_LONG][dbrType]
case ai64:
IFDEBUG(12)
printf(" ai64 [%lld, ...]\n", clink->value.pintegers[0]);
status = dbFastPutConvertRoutine[DBF_INT64][dbrType]
(clink->value.pintegers, pbuffer, NULL);
break;
case af64:
IFDEBUG(12)
printf(" af64 [%g, ...]\n", clink->value.pdoubles[0]);
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
(clink->value.pdoubles, pbuffer, NULL);
break;
case ac40:
IFDEBUG(12)
printf(" ac40 ['%s', ...]\n", clink->value.pstrings[0]);
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
(clink->value.pstrings[0], pbuffer, NULL);
break;
default:
IFDEBUG(12)
printf(" Bad type %d\n", clink->type);
status = S_db_badField;
break;
}
@@ -446,14 +466,20 @@ static long lnkConst_loadLS(struct link *plink, char *pbuffer, epicsUInt32 size,
switch (clink->type) {
case sc40:
IFDEBUG(12)
printf(" sc40 '%s'\n", clink->value.scalar_string);
pstr = clink->value.scalar_string;
break;
case ac40:
IFDEBUG(12)
printf(" ac40 ['%s', ...]\n", clink->value.pstrings[0]);
pstr = clink->value.pstrings[0];
break;
default:
IFDEBUG(12)
printf(" Bad type %d\n", clink->type);
return S_db_badField;
}
@@ -483,23 +509,31 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
switch (clink->type) {
int i;
case si32:
status = dbFastPutConvertRoutine[DBF_LONG][dbrType]
case si64:
IFDEBUG(12)
printf(" si64 %lld\n", clink->value.scalar_integer);
status = dbFastPutConvertRoutine[DBF_INT64][dbrType]
(&clink->value.scalar_integer, pdest, NULL);
break;
case sf64:
IFDEBUG(12)
printf(" sf64 %g\n", clink->value.scalar_double);
status = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType]
(&clink->value.scalar_double, pdest, NULL);
break;
case sc40:
IFDEBUG(12)
printf(" sc40 '%s'\n", clink->value.scalar_string);
status = dbFastPutConvertRoutine[DBF_STRING][dbrType]
(clink->value.scalar_string, pbuffer, NULL);
break;
case ai32:
conv = dbFastPutConvertRoutine[DBF_LONG][dbrType];
case ai64:
IFDEBUG(12)
printf(" ai64 [%lld, ...]\n", clink->value.pintegers[0]);
conv = dbFastPutConvertRoutine[DBF_INT64][dbrType];
for (i = 0; i < nElems; i++) {
conv(&clink->value.pintegers[i], pdest, NULL);
pdest += dbrSize;
@@ -508,6 +542,8 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
break;
case af64:
IFDEBUG(12)
printf(" af64 [%g, ...]\n", clink->value.pdoubles[0]);
conv = dbFastPutConvertRoutine[DBF_DOUBLE][dbrType];
for (i = 0; i < nElems; i++) {
conv(&clink->value.pdoubles[i], pdest, NULL);
@@ -517,6 +553,8 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
break;
case ac40:
IFDEBUG(12)
printf(" ac40 ['%s', ...]\n", clink->value.pstrings[0]);
conv = dbFastPutConvertRoutine[DBF_STRING][dbrType];
for (i = 0; i < nElems; i++) {
conv(clink->value.pstrings[i], pdest, NULL);
@@ -526,6 +564,8 @@ static long lnkConst_loadArray(struct link *plink, short dbrType, void *pbuffer,
break;
default:
IFDEBUG(12)
printf(" Bad type %d\n", clink->type);
status = S_db_badField;
}
*pnReq = nElems;
@@ -551,7 +591,8 @@ static long lnkConst_getValue(struct link *plink, short dbrType, void *pbuffer,
IFDEBUG(10)
printf("lnkConst_getValue(const@%p, %d, %p, ... (%ld))\n",
plink->value.json.jlink, dbrType, pbuffer, *pnRequest);
plink->value.json.jlink, dbrType, pbuffer,
pnRequest ? *pnRequest : 0);
if (pnRequest)
*pnRequest = 0;

View File

@@ -1,19 +0,0 @@
TOP=..
include $(TOP)/configure/CONFIG
#----------------------------------------
# ADD MACRO DEFINITIONS AFTER THIS LINE
#=============================
PROD_HOST += caExample
caExample_SRCS += caExample.c
caExample_LIBS += $(EPICS_BASE_HOST_LIBS)
PROD_HOST += caMonitor
caMonitor_SRCS += caMonitor.c
caMonitor_LIBS += $(EPICS_BASE_HOST_LIBS)
include $(TOP)/configure/RULES
#----------------------------------------
# ADD RULES AFTER THIS LINE

View File

@@ -1,25 +0,0 @@
/*caExample.c*/
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cadef.h"
int main(int argc,char **argv)
{
double data;
chid mychid;
if(argc != 2) {
fprintf(stderr,"usage: caExample pvname\n");
exit(1);
}
SEVCHK(ca_context_create(ca_disable_preemptive_callback),"ca_context_create");
SEVCHK(ca_create_channel(argv[1],NULL,NULL,10,&mychid),"ca_create_channel failure");
SEVCHK(ca_pend_io(5.0),"ca_pend_io failure");
SEVCHK(ca_get(DBR_DOUBLE,mychid,(void *)&data),"ca_get failure");
SEVCHK(ca_pend_io(5.0),"ca_pend_io failure");
printf("%s %f\n",argv[1],data);
return(0);
}

View File

@@ -1,130 +0,0 @@
/*caMonitor.c*/
/* This example accepts the name of a file containing a list of pvs to monitor.
* It prints a message for all ca events: connection, access rights and monitor.
*/
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cadef.h"
#include "dbDefs.h"
#include "epicsString.h"
#include "cantProceed.h"
#define MAX_PV 1000
#define MAX_PV_NAME_LEN 40
typedef struct{
char value[20];
chid mychid;
evid myevid;
} MYNODE;
static void printChidInfo(chid chid, char *message)
{
printf("\n%s\n",message);
printf("pv: %s type(%d) nelements(%ld) host(%s)",
ca_name(chid),ca_field_type(chid),ca_element_count(chid),
ca_host_name(chid));
printf(" read(%d) write(%d) state(%d)\n",
ca_read_access(chid),ca_write_access(chid),ca_state(chid));
}
static void exceptionCallback(struct exception_handler_args args)
{
chid chid = args.chid;
long stat = args.stat; /* Channel access status code*/
const char *channel;
static char *noname = "unknown";
channel = (chid ? ca_name(chid) : noname);
if(chid) printChidInfo(chid,"exceptionCallback");
printf("exceptionCallback stat %s channel %s\n",
ca_message(stat),channel);
}
static void connectionCallback(struct connection_handler_args args)
{
chid chid = args.chid;
printChidInfo(chid,"connectionCallback");
}
static void accessRightsCallback(struct access_rights_handler_args args)
{
chid chid = args.chid;
printChidInfo(chid,"accessRightsCallback");
}
static void eventCallback(struct event_handler_args eha)
{
chid chid = eha.chid;
if(eha.status!=ECA_NORMAL) {
printChidInfo(chid,"eventCallback");
} else {
char *pdata = (char *)eha.dbr;
printf("Event Callback: %s = %s\n",ca_name(eha.chid),pdata);
}
}
int main(int argc,char **argv)
{
char *filename;
int npv = 0;
MYNODE *pmynode[MAX_PV];
char *pname[MAX_PV];
int i;
char tempStr[MAX_PV_NAME_LEN];
char *pstr;
FILE *fp;
if (argc != 2) {
fprintf(stderr,"usage: caMonitor filename\n");
exit(1);
}
filename = argv[1];
fp = fopen(filename,"r");
if (!fp) {
perror("fopen failed");
return(1);
}
while (npv < MAX_PV) {
size_t len;
pstr = fgets(tempStr, MAX_PV_NAME_LEN, fp);
if (!pstr) break;
len = strlen(pstr);
if (len <= 1) continue;
pstr[len - 1] = '\0'; /* Strip newline */
pname[npv] = epicsStrDup(pstr);
pmynode[npv] = callocMustSucceed(1, sizeof(MYNODE), "caMonitor");
npv++;
}
fclose(fp);
SEVCHK(ca_context_create(ca_disable_preemptive_callback),"ca_context_create");
SEVCHK(ca_add_exception_event(exceptionCallback,NULL),
"ca_add_exception_event");
for (i=0; i<npv; i++) {
SEVCHK(ca_create_channel(pname[i],connectionCallback,
pmynode[i],20,&pmynode[i]->mychid),
"ca_create_channel");
SEVCHK(ca_replace_access_rights_event(pmynode[i]->mychid,
accessRightsCallback),
"ca_replace_access_rights_event");
SEVCHK(ca_create_subscription(DBR_STRING,1,pmynode[i]->mychid,
DBE_VALUE,eventCallback,pmynode[i],&pmynode[i]->myevid),
"ca_create_subscription");
}
/*Should never return from following call*/
SEVCHK(ca_pend_event(0.0),"ca_pend_event");
return 0;
}

View File

@@ -1,34 +0,0 @@
#*************************************************************************
# Copyright (c) 2002 The University of Chicago, as Operator of Argonne
# National Laboratory.
# Copyright (c) 2002 The Regents of the University of California, as
# Operator of Los Alamos National Laboratory.
# EPICS BASE Versions 3.13.7
# and higher are distributed subject to a Software License Agreement found
# in file LICENSE that is included with this distribution.
#*************************************************************************
TOP=..
include $(TOP)/configure/CONFIG
PROD_LIBS += $(EPICS_BASE_HOST_LIBS)
#
# Added ws2_32 winmm user32 for the non-dll build
#
PROD_SYS_LIBS_WIN32 += ws2_32 advapi32 user32
casexample_SRCS += main.cc
casexample_SRCS += exServer.cc
casexample_SRCS += exPV.cc
casexample_SRCS += exVectorPV.cc
casexample_SRCS += exScalarPV.cc
casexample_SRCS += exAsyncPV.cc
casexample_SRCS += exChannel.cc
PROD_HOST = casexample
include $(TOP)/configure/RULES

View File

@@ -1,63 +0,0 @@
The files in this directory build an example CA server. This code
is meant to provide some examples of how the CA server library can
be used but is not intended to be an example of exemplary code organization
and therefore care should be taken when emulating what is found here.
The example server exports the process variables (PVs) in the table below.
ScanPeriod Name HOPR LOPR Type Asynchronous Elements
.1 "jane" 10.0 0.0 DBR_DOUBLE No 1
2.0 "fred" 10.0 -10.0 DBR_DOUBLE No 1
.1 "janet" 10.0 0.0 DBR_DOUBLE Yes 1
2.0 "freddy"10.0 -10.0 DBR_DOUBLE Yes 1
2.0 "alan" 10.0 -10.0 DBR_DOUBLE No 100
20.0 "albert"10.0 -10.0 DBR_DOUBLE No 1000
-1.0 "boot" 10.0 -10.0 DBR_ENUM No 1
-1.0 "booty" 10.0 -10.0 DBR_ENUM Yes 1
-1.0 "bill" 10.0 -10.0 DBR_DOUBLE No 1
-1.0 "billy" 10.0 -10.0 DBR_DOUBLE Yes 1
-1.0 "bloaty"10.0 -10.0 DBR_DOUBLE No 100000
Many ca servers will find that synchronous variables will meet
their needs and will not require the increased complexity
associated with asynchronous PVs. Asynchronous PVs are needed
when read and write IO requests cant be completed immediately.
The PVs in the example server are updated periodically if the
"ScanPeriod" column above contains a positive number. Some random
"noise" is added to a PV's current value each time that it is
updated.
usage:
excas [-d<debug level> -t<execution time> -p<PV name prefix>
-c<numbered alias count> -s<1=scan on (default), 0=scan off>
-ss<1=synchronous scan (default), 0=asynchronous scan>]
-d<debug level>
Increased diagnostics messages with increasing debug level. Defaults to no
messages.
-t<execution time>
Specifies the duration that the server will run. Defaults to forever.
-p<PV name prefix>
Specifies the prefix applied to all PV names. If you specify "-pxxx:"
then clients must specify PV names like "xxx:fred" or "xxx:jane".
This is useful when several example servers are running on the same
IP subnet for testing purposes. Defaults to no prefix.
-c<numbered alias count>
Useful when you need lots of aliased PV names. The alias names are
of the form "fred1", "fred2", etc. Defaults to no aliases.
-s<1=scan on (default), 0=scan off>
Used to turn off updating of the PVs with random noise. Default is
to update all PVs with a positive scan period.
-ss<1=synchronous scan (default), 0=asynchronous scan>
Controls updating of PVs from an asynchronous thread (tests thread
safety of the server library). Defaults to synchronous.

View File

@@ -1,228 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// Example EPICS CA server
// (asynchrronous process variable)
//
#include "exServer.h"
exAsyncPV::exAsyncPV ( exServer & cas, pvInfo & setup,
bool preCreateFlag, bool scanOnIn,
double asyncDelayIn ) :
exScalarPV ( cas, setup, preCreateFlag, scanOnIn ),
asyncDelay ( asyncDelayIn ),
simultAsychReadIOCount ( 0u ),
simultAsychWriteIOCount ( 0u )
{
}
//
// exAsyncPV::read()
//
caStatus exAsyncPV::read (const casCtx &ctx, gdd &valueIn)
{
exAsyncReadIO *pIO;
if ( this->simultAsychReadIOCount >= this->cas.maxSimultAsyncIO () ) {
return S_casApp_postponeAsyncIO;
}
pIO = new exAsyncReadIO ( this->cas, ctx,
*this, valueIn, this->asyncDelay );
if ( ! pIO ) {
if ( this->simultAsychReadIOCount > 0 ) {
return S_casApp_postponeAsyncIO;
}
else {
return S_casApp_noMemory;
}
}
this->simultAsychReadIOCount++;
return S_casApp_asyncCompletion;
}
//
// exAsyncPV::writeNotify()
//
caStatus exAsyncPV::writeNotify ( const casCtx &ctx, const gdd &valueIn )
{
if ( this->simultAsychWriteIOCount >= this->cas.maxSimultAsyncIO() ) {
return S_casApp_postponeAsyncIO;
}
exAsyncWriteIO * pIO = new
exAsyncWriteIO ( this->cas, ctx, *this,
valueIn, this->asyncDelay );
if ( ! pIO ) {
if ( this->simultAsychReadIOCount > 0 ) {
return S_casApp_postponeAsyncIO;
}
else {
return S_casApp_noMemory;
}
}
this->simultAsychWriteIOCount++;
return S_casApp_asyncCompletion;
}
//
// exAsyncPV::write()
//
caStatus exAsyncPV::write ( const casCtx &ctx, const gdd &valueIn )
{
// implement the discard intermediate values, but last value
// sent always applied behavior that IOCs provide excepting
// that we will alow N requests to pend instead of a limit
// of only one imposed in the IOC
if ( this->simultAsychWriteIOCount >= this->cas.maxSimultAsyncIO() ) {
pStandbyValue.set ( & valueIn );
return S_casApp_success;
}
exAsyncWriteIO * pIO = new
exAsyncWriteIO ( this->cas, ctx, *this,
valueIn, this->asyncDelay );
if ( ! pIO ) {
pStandbyValue.set ( & valueIn );
return S_casApp_success;
}
this->simultAsychWriteIOCount++;
return S_casApp_asyncCompletion;
}
// Implementing a specialized update for exAsyncPV
// allows standby value to update when we update
// the PV from an asynchronous write timer expiration
// which is a better time compared to removeIO below
// which, if used, gets the reads and writes out of
// order. This type of reordering can cause the
// regression tests to fail.
caStatus exAsyncPV :: updateFromAsyncWrite ( const gdd & src )
{
caStatus stat = this->update ( src );
if ( this->simultAsychWriteIOCount <=1 &&
pStandbyValue.valid () ) {
//printf("updateFromAsyncWrite: write standby\n");
stat = this->update ( *this->pStandbyValue );
this->pStandbyValue.set ( 0 );
}
return stat;
}
void exAsyncPV::removeReadIO ()
{
if ( this->simultAsychReadIOCount > 0u ) {
this->simultAsychReadIOCount--;
}
else {
fprintf ( stderr, "inconsistent simultAsychReadIOCount?\n" );
}
}
void exAsyncPV::removeWriteIO ()
{
if ( this->simultAsychWriteIOCount > 0u ) {
this->simultAsychWriteIOCount--;
if ( this->simultAsychWriteIOCount == 0 &&
pStandbyValue.valid () ) {
//printf("removeIO: write standby\n");
this->update ( *this->pStandbyValue );
this->pStandbyValue.set ( 0 );
}
}
else {
fprintf ( stderr, "inconsistent simultAsychWriteIOCount?\n" );
}
}
//
// exAsyncWriteIO::exAsyncWriteIO()
//
exAsyncWriteIO::exAsyncWriteIO ( exServer & cas,
const casCtx & ctxIn, exAsyncPV & pvIn,
const gdd & valueIn, double asyncDelay ) :
casAsyncWriteIO ( ctxIn ), pv ( pvIn ),
timer ( cas.createTimer () ), pValue(valueIn)
{
this->timer.start ( *this, asyncDelay );
}
//
// exAsyncWriteIO::~exAsyncWriteIO()
//
exAsyncWriteIO::~exAsyncWriteIO()
{
this->timer.destroy ();
// if the timer hasnt expired, and the value
// hasnt been written then force it to happen
// now so that regression testing works
if ( this->pValue.valid () ) {
this->pv.updateFromAsyncWrite ( *this->pValue );
}
this->pv.removeWriteIO();
}
//
// exAsyncWriteIO::expire()
// (a virtual function that runs when the base timer expires)
//
epicsTimerNotify::expireStatus exAsyncWriteIO::
expire ( const epicsTime & /* currentTime */ )
{
assert ( this->pValue.valid () );
caStatus status = this->pv.updateFromAsyncWrite ( *this->pValue );
this->pValue.set ( 0 );
this->postIOCompletion ( status );
return noRestart;
}
//
// exAsyncReadIO::exAsyncReadIO()
//
exAsyncReadIO::exAsyncReadIO ( exServer & cas, const casCtx & ctxIn,
exAsyncPV & pvIn, gdd & protoIn,
double asyncDelay ) :
casAsyncReadIO ( ctxIn ), pv ( pvIn ),
timer ( cas.createTimer() ), pProto ( protoIn )
{
this->timer.start ( *this, asyncDelay );
}
//
// exAsyncReadIO::~exAsyncReadIO()
//
exAsyncReadIO::~exAsyncReadIO()
{
this->pv.removeReadIO ();
this->timer.destroy ();
}
//
// exAsyncReadIO::expire()
// (a virtual function that runs when the base timer expires)
//
epicsTimerNotify::expireStatus
exAsyncReadIO::expire ( const epicsTime & /* currentTime */ )
{
//
// map between the prototype in and the
// current value
//
caStatus status = this->pv.exPV::readNoCtx ( this->pProto );
//
// post IO completion
//
this->postIOCompletion ( status, *this->pProto );
return noRestart;
}

View File

@@ -1,41 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// Example EPICS CA server
//
#include "exServer.h"
//
// exChannel::setOwner ()
//
void exChannel::setOwner(const char * const /* pUserName */,
const char * const /* pHostName */)
{
}
//
// exChannel::readAccess ()
//
bool exChannel::readAccess () const
{
return true;
}
//
// exChannel::writeAccess ()
//
bool exChannel::writeAccess () const
{
return true;
}

View File

@@ -1,344 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// Example EPICS CA server
//
#include "exServer.h"
#include "gddApps.h"
//
// static data for exPV
//
char exPV::hasBeenInitialized = 0;
gddAppFuncTable<exPV> exPV::ft;
epicsTime exPV::currentTime;
//
// special gddDestructor guarantees same form of new and delete
//
class exFixedStringDestructor: public gddDestructor {
virtual void run (void *);
};
//
// exPV::exPV()
//
exPV::exPV ( exServer & casIn, pvInfo & setup,
bool preCreateFlag, bool scanOnIn ) :
cas ( casIn ),
timer ( cas.createTimer() ),
info ( setup ),
interest ( false ),
preCreate ( preCreateFlag ),
scanOn ( scanOnIn )
{
//
// no dataless PV allowed
//
assert (this->info.getCapacity()>=1u);
//
// start a very slow background scan
// (we will speed this up to the normal rate when
// someone is watching the PV)
//
if ( this->scanOn && this->info.getScanPeriod () > 0.0 ) {
this->timer.start ( *this, this->getScanPeriod() );
}
}
//
// exPV::~exPV()
//
exPV::~exPV()
{
this->timer.destroy ();
this->info.unlinkPV();
}
//
// exPV::destroy()
//
// this is replaced by a noop since we are
// pre-creating most of the PVs during init in this simple server
//
void exPV::destroy()
{
if ( ! this->preCreate ) {
delete this;
}
}
//
// exPV::update()
//
caStatus exPV::update ( const gdd & valueIn )
{
# if DEBUG
printf("Setting %s too:\n", this->info.getName().string());
valueIn.dump();
# endif
caStatus status = this->updateValue ( valueIn );
if ( status || ( ! this->pValue.valid() ) ) {
return status;
}
//
// post a value change event
//
caServer * pCAS = this->getCAS();
if ( this->interest == true && pCAS != NULL ) {
casEventMask select ( pCAS->valueEventMask() | pCAS->logEventMask() );
this->postEvent ( select, *this->pValue );
}
return S_casApp_success;
}
//
// exScanTimer::expire ()
//
epicsTimerNotify::expireStatus
exPV::expire ( const epicsTime & /*currentTime*/ )
{
this->scan();
if ( this->scanOn && this->getScanPeriod() > 0.0 ) {
return expireStatus ( restart, this->getScanPeriod() );
}
else {
return noRestart;
}
}
//
// exPV::bestExternalType()
//
aitEnum exPV::bestExternalType () const
{
return this->info.getType ();
}
//
// exPV::interestRegister()
//
caStatus exPV::interestRegister ()
{
if ( ! this->getCAS() ) {
return S_casApp_success;
}
this->interest = true;
if ( this->scanOn && this->getScanPeriod() > 0.0 &&
this->getScanPeriod() < this->timer.getExpireDelay() ) {
this->timer.start ( *this, this->getScanPeriod() );
}
return S_casApp_success;
}
//
// exPV::interestDelete()
//
void exPV::interestDelete()
{
this->interest = false;
}
//
// exPV::show()
//
void exPV::show ( unsigned level ) const
{
if (level>1u) {
if ( this->pValue.valid () ) {
printf ( "exPV: cond=%d\n", this->pValue->getStat () );
printf ( "exPV: sevr=%d\n", this->pValue->getSevr () );
printf ( "exPV: value=%f\n", static_cast < double > ( * this->pValue ) );
}
printf ( "exPV: interest=%d\n", this->interest );
this->timer.show ( level - 1u );
}
}
//
// exPV::initFT()
//
void exPV::initFT ()
{
if ( exPV::hasBeenInitialized ) {
return;
}
//
// time stamp, status, and severity are extracted from the
// GDD associated with the "value" application type.
//
exPV::ft.installReadFunc ("value", &exPV::getValue);
exPV::ft.installReadFunc ("precision", &exPV::getPrecision);
exPV::ft.installReadFunc ("graphicHigh", &exPV::getHighLimit);
exPV::ft.installReadFunc ("graphicLow", &exPV::getLowLimit);
exPV::ft.installReadFunc ("controlHigh", &exPV::getHighLimit);
exPV::ft.installReadFunc ("controlLow", &exPV::getLowLimit);
exPV::ft.installReadFunc ("alarmHigh", &exPV::getHighLimit);
exPV::ft.installReadFunc ("alarmLow", &exPV::getLowLimit);
exPV::ft.installReadFunc ("alarmHighWarning", &exPV::getHighLimit);
exPV::ft.installReadFunc ("alarmLowWarning", &exPV::getLowLimit);
exPV::ft.installReadFunc ("units", &exPV::getUnits);
exPV::ft.installReadFunc ("enums", &exPV::getEnums);
exPV::hasBeenInitialized = 1;
}
//
// exPV::getPrecision()
//
caStatus exPV::getPrecision ( gdd & prec )
{
prec.put(4u);
return S_cas_success;
}
//
// exPV::getHighLimit()
//
caStatus exPV::getHighLimit ( gdd & value )
{
value.put(info.getHopr());
return S_cas_success;
}
//
// exPV::getLowLimit()
//
caStatus exPV::getLowLimit ( gdd & value )
{
value.put(info.getLopr());
return S_cas_success;
}
//
// exPV::getUnits()
//
caStatus exPV::getUnits( gdd & units )
{
aitString str("furlongs", aitStrRefConstImortal);
units.put(str);
return S_cas_success;
}
//
// exPV::getEnums()
//
// returns the eneumerated state strings
// for a discrete channel
//
// The PVs in this example are purely analog,
// and therefore this isnt appropriate in an
// analog context ...
//
caStatus exPV::getEnums ( gdd & enumsIn )
{
if ( this->info.getType () == aitEnumEnum16 ) {
static const unsigned nStr = 2;
aitFixedString *str;
exFixedStringDestructor *pDes;
str = new aitFixedString[nStr];
if (!str) {
return S_casApp_noMemory;
}
pDes = new exFixedStringDestructor;
if (!pDes) {
delete [] str;
return S_casApp_noMemory;
}
strncpy (str[0].fixed_string, "off",
sizeof(str[0].fixed_string));
strncpy (str[1].fixed_string, "on",
sizeof(str[1].fixed_string));
enumsIn.setDimension(1);
enumsIn.setBound (0,0,nStr);
enumsIn.putRef (str, pDes);
return S_cas_success;
}
return S_cas_success;
}
//
// exPV::getValue()
//
caStatus exPV::getValue ( gdd & value )
{
caStatus status;
if ( this->pValue.valid () ) {
gddStatus gdds;
gdds = gddApplicationTypeTable::
app_table.smartCopy ( &value, & (*this->pValue) );
if (gdds) {
status = S_cas_noConvert;
}
else {
status = S_cas_success;
}
}
else {
status = S_casApp_undefined;
}
return status;
}
//
// exPV::write()
// (synchronous default)
//
caStatus exPV::write ( const casCtx &, const gdd & valueIn )
{
return this->update ( valueIn );
}
//
// exPV::read()
// (synchronous default)
//
caStatus exPV::read ( const casCtx &, gdd & protoIn )
{
return this->ft.read ( *this, protoIn );
}
//
// exPV::createChannel()
//
// for access control - optional
//
casChannel *exPV::createChannel ( const casCtx &ctx,
const char * const /* pUserName */,
const char * const /* pHostName */ )
{
return new exChannel ( ctx );
}
//
// exFixedStringDestructor::run()
//
// special gddDestructor guarantees same form of new and delete
//
void exFixedStringDestructor::run ( void * pUntyped )
{
aitFixedString *ps = (aitFixedString *) pUntyped;
delete [] ps;
}

View File

@@ -1,120 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <math.h>
#include <limits.h>
#include <stdlib.h>
#include "exServer.h"
#include "gddApps.h"
#define myPI 3.14159265358979323846
//
// SUN C++ does not have RAND_MAX yet
//
#if !defined(RAND_MAX)
//
// Apparently SUN C++ is using the SYSV version of rand
//
#if 0
#define RAND_MAX INT_MAX
#else
#define RAND_MAX SHRT_MAX
#endif
#endif
//
// exScalarPV::scan
//
void exScalarPV::scan()
{
caStatus status;
double radians;
smartGDDPointer pDD;
float newValue;
float limit;
int gddStatus;
//
// update current time (so we are not required to do
// this every time that we write the PV which impacts
// throughput under sunos4 because gettimeofday() is
// slow)
//
this->currentTime = epicsTime::getCurrent ();
pDD = new gddScalar ( gddAppType_value, aitEnumFloat64 );
if ( ! pDD.valid () ) {
return;
}
//
// smart pointer class manages reference count after this point
//
gddStatus = pDD->unreference ();
assert ( ! gddStatus );
radians = ( rand () * 2.0 * myPI ) / RAND_MAX;
if ( this->pValue.valid () ) {
this->pValue->getConvert(newValue);
}
else {
newValue = 0.0f;
}
newValue += (float) ( sin (radians) / 10.0 );
limit = (float) this->info.getHopr ();
newValue = epicsMin ( newValue, limit );
limit = (float) this->info.getLopr ();
newValue = epicsMax ( newValue, limit );
*pDD = newValue;
aitTimeStamp gddts ( this->currentTime );
pDD->setTimeStamp ( & gddts );
status = this->update ( *pDD );
if (status!=S_casApp_success) {
errMessage ( status, "scalar scan update failed\n" );
}
}
//
// exScalarPV::updateValue ()
//
// NOTES:
// 1) This should have a test which verifies that the
// incoming value in all of its various data types can
// be translated into a real number?
// 2) We prefer to unreference the old PV value here and
// reference the incomming value because this will
// result in each value change events retaining an
// independent value on the event queue.
//
caStatus exScalarPV::updateValue ( const gdd & valueIn )
{
//
// Really no need to perform this check since the
// server lib verifies that all requests are in range
//
if ( ! valueIn.isScalar() ) {
return S_casApp_outOfBounds;
}
if ( ! pValue.valid () ) {
this->pValue = new gddScalar (
gddAppType_value, this->info.getType () );
if ( ! pValue.valid () ) {
return S_casApp_noMemory;
}
}
this->pValue->put ( & valueIn );
return S_casApp_success;
}

View File

@@ -1,435 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// fileDescriptorManager.process(delay);
// (the name of the global symbol has leaked in here)
//
//
// Example EPICS CA server
//
#include "exServer.h"
//
// static list of pre-created PVs
//
pvInfo exServer::pvList[] = {
pvInfo (1.0e-1, "jane", 10.0f, 0.0f, aitEnumFloat64, excasIoSync, 1u),
pvInfo (2.0, "fred", 10.0f, -10.0f, aitEnumFloat64, excasIoSync, 1u),
pvInfo (1.0e-1, "janet", 10.0f, 0.0f, aitEnumFloat64, excasIoAsync, 1u),
pvInfo (2.0, "freddy", 10.0f, -10.0f, aitEnumFloat64, excasIoAsync, 1u),
pvInfo (2.0, "alan", 10.0f, -10.0f, aitEnumFloat64, excasIoSync, 100u),
pvInfo (20.0, "albert", 10.0f, -10.0f, aitEnumFloat64, excasIoSync, 1000u),
pvInfo (-1.0, "boot", 10.0f, -10.0f, aitEnumEnum16, excasIoSync, 1u),
pvInfo (1.0, "booty", 10.0f, -10.0f, aitEnumEnum16, excasIoAsync, 1u),
pvInfo (-1.0, "bill", 10.0f, -10.0f, aitEnumFloat64, excasIoSync, 1u),
pvInfo (-1.0, "billy", 10.0f, -10.0f, aitEnumFloat64, excasIoAsync, 1u)
};
const unsigned exServer::pvListNElem = NELEMENTS (exServer::pvList);
//
// static on-the-fly PVs
//
pvInfo exServer::billy (-1.0, "billybob", 10.0f, -10.0f, aitEnumFloat64, excasIoAsync, 1u);
pvInfo exServer::bloater (.010, "bloater", 10.0f, -10.0f, aitEnumFloat64, excasIoSync, 10000u);
pvInfo exServer::bloaty (.010, "bloaty", 10.0f, -10.0f, aitEnumFloat64, excasIoSync, 100000u);
//
// exServer::exServer()
//
exServer::exServer ( const char * const pvPrefix,
unsigned aliasCount, bool scanOnIn,
bool asyncScan, double asyncDelayIn,
unsigned maxSimultAsyncIOIn ) :
pTimerQueue ( 0 ), simultAsychIOCount ( 0u ),
_maxSimultAsyncIO ( maxSimultAsyncIOIn ),
asyncDelay ( asyncDelayIn ), scanOn ( scanOnIn )
{
unsigned i;
exPV *pPV;
pvInfo *pPVI;
pvInfo *pPVAfter = &exServer::pvList[pvListNElem];
char pvAlias[256];
const char * const pNameFmtStr = "%.100s%.20s";
const char * const pAliasFmtStr = "%.100s%.20s%.6u";
exPV::initFT();
if ( asyncScan ) {
unsigned timerPriotity;
epicsThreadBooleanStatus etbs = epicsThreadLowestPriorityLevelAbove (
epicsThreadGetPrioritySelf (), & timerPriotity );
if ( etbs != epicsThreadBooleanStatusSuccess ) {
timerPriotity = epicsThreadGetPrioritySelf ();
}
this->pTimerQueue = & epicsTimerQueueActive::allocate ( false, timerPriotity );
}
//
// pre-create all of the simple PVs that this server will export
//
for (pPVI = exServer::pvList; pPVI < pPVAfter; pPVI++) {
pPV = pPVI->createPV (*this, true, scanOnIn, this->asyncDelay );
if (!pPV) {
fprintf(stderr, "Unable to create new PV \"%s\"\n",
pPVI->getName());
}
//
// Install canonical (root) name
//
sprintf(pvAlias, pNameFmtStr, pvPrefix, pPVI->getName());
this->installAliasName(*pPVI, pvAlias);
//
// Install numbered alias names
//
for (i=0u; i<aliasCount; i++) {
sprintf(pvAlias, pAliasFmtStr, pvPrefix,
pPVI->getName(), i);
this->installAliasName(*pPVI, pvAlias);
}
}
//
// Install create on-the-fly PVs
// into the PV name hash table
//
sprintf ( pvAlias, pNameFmtStr, pvPrefix, billy.getName() );
this->installAliasName ( billy, pvAlias );
sprintf ( pvAlias, pNameFmtStr, pvPrefix, bloater.getName() );
this->installAliasName ( bloater, pvAlias );
sprintf ( pvAlias, pNameFmtStr, pvPrefix, bloaty.getName() );
this->installAliasName ( bloaty, pvAlias );
}
//
// exServer::~exServer()
//
exServer::~exServer()
{
this->destroyAllPV ();
this->stringResTbl.traverse ( &pvEntry::destroy );
}
void exServer::destroyAllPV ()
{
for ( unsigned i = 0;
i < NELEMENTS(exServer::pvList); i++ ) {
exServer::pvList[i].deletePV ();
}
}
//
// exServer::installAliasName()
//
void exServer::installAliasName(pvInfo &info, const char *pAliasName)
{
pvEntry *pEntry;
pEntry = new pvEntry(info, *this, pAliasName);
if (pEntry) {
int resLibStatus;
resLibStatus = this->stringResTbl.add(*pEntry);
if (resLibStatus==0) {
return;
}
else {
delete pEntry;
}
}
fprintf ( stderr,
"Unable to enter PV=\"%s\" Alias=\"%s\" in PV name alias hash table\n",
info.getName(), pAliasName );
}
//
// More advanced pvExistTest() isnt needed so we forward to
// original version. This avoids sun pro warnings and speeds
// up execution.
//
pvExistReturn exServer::pvExistTest
( const casCtx & ctx, const caNetAddr &, const char * pPVName )
{
return this->pvExistTest ( ctx, pPVName );
}
//
// exServer::pvExistTest()
//
pvExistReturn exServer::pvExistTest
( const casCtx& ctxIn, const char * pPVName )
{
//
// lifetime of id is shorter than lifetime of pName
//
stringId id ( pPVName, stringId::refString );
pvEntry *pPVE;
//
// Look in hash table for PV name (or PV alias name)
//
pPVE = this->stringResTbl.lookup ( id );
if ( ! pPVE ) {
return pverDoesNotExistHere;
}
pvInfo & pvi = pPVE->getInfo();
//
// Initiate async IO if this is an async PV
//
if ( pvi.getIOType() == excasIoSync ) {
return pverExistsHere;
}
else {
if ( this->simultAsychIOCount >= this->_maxSimultAsyncIO ) {
return pverDoesNotExistHere;
}
this->simultAsychIOCount++;
exAsyncExistIO * pIO =
new exAsyncExistIO ( pvi, ctxIn, *this );
if ( pIO ) {
return pverAsyncCompletion;
}
else {
this->simultAsychIOCount--;
return pverDoesNotExistHere;
}
}
}
//
// exServer::pvAttach()
//
pvAttachReturn exServer::pvAttach
(const casCtx &ctx, const char *pName)
{
//
// lifetime of id is shorter than lifetime of pName
//
stringId id(pName, stringId::refString);
exPV *pPV;
pvEntry *pPVE;
pPVE = this->stringResTbl.lookup(id);
if (!pPVE) {
return S_casApp_pvNotFound;
}
pvInfo &pvi = pPVE->getInfo();
//
// If this is a synchronous PV create the PV now
//
if (pvi.getIOType() == excasIoSync) {
pPV = pvi.createPV(*this, false, this->scanOn, this->asyncDelay );
if (pPV) {
return *pPV;
}
else {
return S_casApp_noMemory;
}
}
//
// Initiate async IO if this is an async PV
//
else {
if (this->simultAsychIOCount>=this->_maxSimultAsyncIO) {
return S_casApp_postponeAsyncIO;
}
this->simultAsychIOCount++;
exAsyncCreateIO *pIO =
new exAsyncCreateIO ( pvi, *this, ctx,
this->scanOn, this->asyncDelay );
if (pIO) {
return S_casApp_asyncCompletion;
}
else {
this->simultAsychIOCount--;
return S_casApp_noMemory;
}
}
}
//
// exServer::setDebugLevel ()
//
void exServer::setDebugLevel ( unsigned level )
{
this->caServer::setDebugLevel ( level );
}
//
// exServer::createTimer ()
//
class epicsTimer & exServer::createTimer ()
{
if ( this->pTimerQueue ) {
return this->pTimerQueue->createTimer ();
}
else {
return this->caServer::createTimer ();
}
}
//
// pvInfo::createPV()
//
exPV *pvInfo::createPV ( exServer & cas, bool preCreateFlag,
bool scanOn, double asyncDelay )
{
if (this->pPV) {
return this->pPV;
}
exPV *pNewPV;
//
// create an instance of the appropriate class
// depending on the io type and the number
// of elements
//
if (this->capacity==1u) {
switch (this->ioType){
case excasIoSync:
pNewPV = new exScalarPV ( cas, *this, preCreateFlag, scanOn );
break;
case excasIoAsync:
pNewPV = new exAsyncPV ( cas, *this,
preCreateFlag, scanOn, asyncDelay );
break;
default:
pNewPV = NULL;
break;
}
}
else {
if ( this->ioType == excasIoSync ) {
pNewPV = new exVectorPV ( cas, *this, preCreateFlag, scanOn );
}
else {
pNewPV = NULL;
}
}
//
// load initial value (this is not done in
// the constructor because the base class's
// pure virtual function would be called)
//
// We always perform this step even if
// scanning is disable so that there will
// always be an initial value
//
if (pNewPV) {
this->pPV = pNewPV;
pNewPV->scan();
}
return pNewPV;
}
//
// exServer::show()
//
void exServer::show (unsigned level) const
{
//
// server tool specific show code goes here
//
this->stringResTbl.show(level);
//
// print information about ca server libarary
// internals
//
this->caServer::show(level);
}
//
// exAsyncExistIO::exAsyncExistIO()
//
exAsyncExistIO::exAsyncExistIO ( const pvInfo &pviIn, const casCtx &ctxIn,
exServer &casIn ) :
casAsyncPVExistIO ( ctxIn ), pvi ( pviIn ),
timer ( casIn.createTimer () ), cas ( casIn )
{
this->timer.start ( *this, 0.00001 );
}
//
// exAsyncExistIO::~exAsyncExistIO()
//
exAsyncExistIO::~exAsyncExistIO()
{
this->cas.removeIO ();
this->timer.destroy ();
}
//
// exAsyncExistIO::expire()
// (a virtual function that runs when the base timer expires)
//
epicsTimerNotify::expireStatus exAsyncExistIO::expire ( const epicsTime & /*currentTime*/ )
{
//
// post IO completion
//
this->postIOCompletion ( pvExistReturn(pverExistsHere) );
return noRestart;
}
//
// exAsyncCreateIO::exAsyncCreateIO()
//
exAsyncCreateIO ::
exAsyncCreateIO ( pvInfo &pviIn, exServer &casIn,
const casCtx &ctxIn, bool scanOnIn, double asyncDelayIn ) :
casAsyncPVAttachIO ( ctxIn ), pvi ( pviIn ),
timer ( casIn.createTimer () ),
cas ( casIn ), asyncDelay ( asyncDelayIn ), scanOn ( scanOnIn )
{
this->timer.start ( *this, 0.00001 );
}
//
// exAsyncCreateIO::~exAsyncCreateIO()
//
exAsyncCreateIO::~exAsyncCreateIO()
{
this->cas.removeIO ();
this->timer.destroy ();
}
//
// exAsyncCreateIO::expire()
// (a virtual function that runs when the base timer expires)
//
epicsTimerNotify::expireStatus exAsyncCreateIO::expire ( const epicsTime & /*currentTime*/ )
{
exPV * pPV = this->pvi.createPV ( this->cas, false,
this->scanOn, this->asyncDelay );
if ( pPV ) {
this->postIOCompletion ( pvAttachReturn ( *pPV ) );
}
else {
this->postIOCompletion ( pvAttachReturn ( S_casApp_noMemory ) );
}
return noRestart;
}

View File

@@ -1,602 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// Example EPICS CA server
//
//
// caServer
// |
// exServer
//
// casPV
// |
// exPV-----------
// | |
// exScalarPV exVectorPV
// |
// exAsyncPV
//
// casChannel
// |
// exChannel
//
//
// ANSI C
//
#include <string.h>
#include <stdio.h>
//
// EPICS
//
#define epicsAssertAuthor "Jeff Hill johill@lanl.gov"
#include "gddAppFuncTable.h"
#include "smartGDDPointer.h"
#include "epicsTimer.h"
#include "casdef.h"
#include "epicsAssert.h"
#include "resourceLib.h"
#include "epicsAlgorithm.h"
#ifndef NELEMENTS
# define NELEMENTS(A) (sizeof(A)/sizeof(A[0]))
#endif
//
// info about all pv in this server
//
enum excasIoType { excasIoSync, excasIoAsync };
class exPV;
class exServer;
//
// pvInfo
//
class pvInfo {
public:
pvInfo ( double scanPeriodIn, const char * pNameIn,
aitFloat32 hoprIn, aitFloat32 loprIn, aitEnum typeIn,
excasIoType ioTypeIn, unsigned countIn );
pvInfo ( const pvInfo & copyIn );
~pvInfo ();
double getScanPeriod () const;
const char * getName ()
const; double getHopr () const;
double getLopr () const;
aitEnum getType () const;
excasIoType getIOType () const;
unsigned getCapacity () const;
unsigned getElementCount () const;
void setElementCount (unsigned);
void unlinkPV ();
exPV *createPV ( exServer & exCAS, bool preCreateFlag,
bool scanOn, double asyncDelay );
void deletePV ();
private:
const double scanPeriod;
const char * pName;
const double hopr;
const double lopr;
aitEnum type;
const excasIoType ioType;
const unsigned capacity;
unsigned elementCount;
exPV * pPV;
pvInfo & operator = ( const pvInfo & );
};
//
// pvEntry
//
// o entry in the string hash table for the pvInfo
// o Since there may be aliases then we may end up
// with several of this class all referencing
// the same pv info class (justification
// for this breaking out into a seperate class
// from pvInfo)
//
class pvEntry
: public stringId, public tsSLNode < pvEntry > {
public:
pvEntry ( pvInfo &infoIn, exServer & casIn,
const char * pAliasName );
~pvEntry();
pvInfo & getInfo() const { return this->info; }
void destroy ();
private:
pvInfo & info;
exServer & cas;
pvEntry & operator = ( const pvEntry & );
pvEntry ( const pvEntry & );
};
//
// exPV
//
class exPV : public casPV, public epicsTimerNotify,
public tsSLNode < exPV > {
public:
exPV ( exServer & cas, pvInfo & setup,
bool preCreateFlag, bool scanOn );
virtual ~exPV();
void show ( unsigned level ) const;
//
// Called by the server libary each time that it wishes to
// subscribe for PV the server tool via postEvent() below.
//
caStatus interestRegister ();
//
// called by the server library each time that it wishes to
// remove its subscription for PV value change events
// from the server tool via caServerPostEvents()
//
void interestDelete ();
aitEnum bestExternalType () const;
//
// chCreate() is called each time that a PV is attached to
// by a client. The server tool must create a casChannel object
// (or a derived class) each time that this routine is called
//
// If the operation must complete asynchronously then return
// the status code S_casApp_asyncCompletion and then
// create the casChannel object at some time in the future
//
//casChannel *createChannel ();
//
// This gets called when the pv gets a new value
//
caStatus update ( const gdd & );
//
// Gets called when we add noise to the current value
//
virtual void scan () = 0;
//
// If no one is watching scan the PV with 10.0
// times the specified period
//
double getScanPeriod ();
caStatus read ( const casCtx &, gdd & protoIn );
caStatus readNoCtx ( smartGDDPointer pProtoIn );
caStatus write ( const casCtx &, const gdd & value );
void destroy ();
const pvInfo & getPVInfo ();
const char * getName() const;
static void initFT();
casChannel * createChannel ( const casCtx &ctx,
const char * const pUserName,
const char * const pHostName );
protected:
smartGDDPointer pValue;
exServer & cas;
epicsTimer & timer;
pvInfo & info;
bool interest;
bool preCreate;
bool scanOn;
static epicsTime currentTime;
virtual caStatus updateValue ( const gdd & ) = 0;
private:
//
// scan timer expire
//
expireStatus expire ( const epicsTime & currentTime );
//
// Std PV Attribute fetch support
//
gddAppFuncTableStatus getPrecision(gdd &value);
gddAppFuncTableStatus getHighLimit(gdd &value);
gddAppFuncTableStatus getLowLimit(gdd &value);
gddAppFuncTableStatus getUnits(gdd &value);
gddAppFuncTableStatus getValue(gdd &value);
gddAppFuncTableStatus getEnums(gdd &value);
exPV & operator = ( const exPV & );
exPV ( const exPV & );
//
// static
//
static gddAppFuncTable<exPV> ft;
static char hasBeenInitialized;
};
//
// exScalarPV
//
class exScalarPV : public exPV {
public:
exScalarPV ( exServer & cas, pvInfo &setup,
bool preCreateFlag, bool scanOnIn ) :
exPV ( cas, setup,
preCreateFlag, scanOnIn) {}
void scan();
private:
caStatus updateValue ( const gdd & );
exScalarPV & operator = ( const exScalarPV & );
exScalarPV ( const exScalarPV & );
};
//
// exVectorPV
//
class exVectorPV : public exPV {
public:
exVectorPV ( exServer & cas, pvInfo &setup,
bool preCreateFlag, bool scanOnIn ) :
exPV ( cas, setup,
preCreateFlag, scanOnIn) {}
void scan();
unsigned maxDimension() const;
aitIndex maxBound (unsigned dimension) const;
private:
caStatus updateValue ( const gdd & );
exVectorPV & operator = ( const exVectorPV & );
exVectorPV ( const exVectorPV & );
};
//
// exServer
//
class exServer : private caServer {
public:
exServer ( const char * const pvPrefix,
unsigned aliasCount, bool scanOn,
bool asyncScan, double asyncDelay,
unsigned maxSimultAsyncIO );
~exServer ();
void show ( unsigned level ) const;
void removeIO ();
void removeAliasName ( pvEntry & entry );
class epicsTimer & createTimer ();
void setDebugLevel ( unsigned level );
void destroyAllPV ();
unsigned maxSimultAsyncIO () const;
private:
resTable < pvEntry, stringId > stringResTbl;
epicsTimerQueueActive * pTimerQueue;
unsigned simultAsychIOCount;
const unsigned _maxSimultAsyncIO;
double asyncDelay;
bool scanOn;
void installAliasName ( pvInfo & info, const char * pAliasName );
pvExistReturn pvExistTest ( const casCtx &,
const caNetAddr &, const char * pPVName );
pvExistReturn pvExistTest ( const casCtx &,
const char * pPVName );
pvAttachReturn pvAttach ( const casCtx &,
const char * pPVName );
exServer & operator = ( const exServer & );
exServer ( const exServer & );
//
// list of pre-created PVs
//
static pvInfo pvList[];
static const unsigned pvListNElem;
//
// on-the-fly PVs
//
static pvInfo bill;
static pvInfo billy;
static pvInfo bloater;
static pvInfo bloaty;
static pvInfo boot;
static pvInfo booty;
};
//
// exAsyncPV
//
class exAsyncPV : public exScalarPV {
public:
exAsyncPV ( exServer & cas, pvInfo &setup,
bool preCreateFlag, bool scanOnIn, double asyncDelay );
caStatus read ( const casCtx & ctxIn, gdd & protoIn );
caStatus write ( const casCtx & ctxIn, const gdd & value );
caStatus writeNotify ( const casCtx & ctxIn, const gdd & value );
void removeReadIO();
void removeWriteIO();
caStatus updateFromAsyncWrite ( const gdd & );
private:
double asyncDelay;
smartConstGDDPointer pStandbyValue;
unsigned simultAsychReadIOCount;
unsigned simultAsychWriteIOCount;
exAsyncPV & operator = ( const exAsyncPV & );
exAsyncPV ( const exAsyncPV & );
};
//
// exChannel
//
class exChannel : public casChannel{
public:
exChannel ( const casCtx & ctxIn );
void setOwner ( const char * const pUserName,
const char * const pHostName );
bool readAccess () const;
bool writeAccess () const;
private:
exChannel & operator = ( const exChannel & );
exChannel ( const exChannel & );
};
//
// exAsyncWriteIO
//
class exAsyncWriteIO : public casAsyncWriteIO, public epicsTimerNotify {
public:
exAsyncWriteIO ( exServer &, const casCtx & ctxIn,
exAsyncPV &, const gdd &, double asyncDelay );
~exAsyncWriteIO ();
private:
exAsyncPV & pv;
epicsTimer & timer;
smartConstGDDPointer pValue;
expireStatus expire ( const epicsTime & currentTime );
exAsyncWriteIO & operator = ( const exAsyncWriteIO & );
exAsyncWriteIO ( const exAsyncWriteIO & );
};
//
// exAsyncReadIO
//
class exAsyncReadIO : public casAsyncReadIO, public epicsTimerNotify {
public:
exAsyncReadIO ( exServer &, const casCtx &,
exAsyncPV &, gdd &, double asyncDelay );
virtual ~exAsyncReadIO ();
private:
exAsyncPV & pv;
epicsTimer & timer;
smartGDDPointer pProto;
expireStatus expire ( const epicsTime & currentTime );
exAsyncReadIO & operator = ( const exAsyncReadIO & );
exAsyncReadIO ( const exAsyncReadIO & );
};
//
// exAsyncExistIO
// (PV exist async IO)
//
class exAsyncExistIO : public casAsyncPVExistIO, public epicsTimerNotify {
public:
exAsyncExistIO ( const pvInfo & pviIn, const casCtx & ctxIn,
exServer & casIn );
virtual ~exAsyncExistIO ();
private:
const pvInfo & pvi;
epicsTimer & timer;
exServer & cas;
expireStatus expire ( const epicsTime & currentTime );
exAsyncExistIO & operator = ( const exAsyncExistIO & );
exAsyncExistIO ( const exAsyncExistIO & );
};
//
// exAsyncCreateIO
// (PV create async IO)
//
class exAsyncCreateIO : public casAsyncPVAttachIO, public epicsTimerNotify {
public:
exAsyncCreateIO ( pvInfo & pviIn, exServer & casIn,
const casCtx & ctxIn, bool scanOnIn, double asyncDelay );
virtual ~exAsyncCreateIO ();
private:
pvInfo & pvi;
epicsTimer & timer;
exServer & cas;
double asyncDelay;
bool scanOn;
expireStatus expire ( const epicsTime & currentTime );
exAsyncCreateIO & operator = ( const exAsyncCreateIO & );
exAsyncCreateIO ( const exAsyncCreateIO & );
};
inline pvInfo::pvInfo ( double scanPeriodIn, const char *pNameIn,
aitFloat32 hoprIn, aitFloat32 loprIn,
aitEnum typeIn, excasIoType ioTypeIn,
unsigned countIn ) :
scanPeriod ( scanPeriodIn ), pName ( pNameIn ),
hopr ( hoprIn ), lopr ( loprIn ), type ( typeIn ),
ioType ( ioTypeIn ), capacity ( countIn ),
elementCount ( 0 ), pPV ( 0 )
{
}
//
// for use when MSVC++ will not build a default copy constructor
// for this class
//
inline pvInfo::pvInfo ( const pvInfo & copyIn ) :
scanPeriod ( copyIn.scanPeriod ), pName ( copyIn.pName ),
hopr ( copyIn.hopr ), lopr ( copyIn.lopr ), type ( copyIn.type ),
ioType ( copyIn.ioType ), capacity ( copyIn.capacity ),
elementCount ( copyIn.elementCount ), pPV ( copyIn.pPV )
{
}
inline pvInfo::~pvInfo ()
{
//
// GDD cleanup gets rid of GDD's that are in use
// by the PV before the file scope destructer for
// this class runs here so this does not seem to
// be a good idea
//
//if ( this->pPV != NULL ) {
// delete this->pPV;
//}
}
inline void pvInfo::deletePV ()
{
if ( this->pPV != NULL ) {
delete this->pPV;
}
}
inline double pvInfo::getScanPeriod () const
{
return this->scanPeriod;
}
inline const char *pvInfo::getName () const
{
return this->pName;
}
inline double pvInfo::getHopr () const
{
return this->hopr;
}
inline double pvInfo::getLopr () const
{
return this->lopr;
}
inline aitEnum pvInfo::getType () const
{
return this->type;
}
inline excasIoType pvInfo::getIOType () const
{
return this->ioType;
}
inline unsigned pvInfo::getCapacity () const
{
return this->capacity;
}
inline unsigned pvInfo::getElementCount () const
{
return this->elementCount;
}
inline void pvInfo::setElementCount (unsigned newCount)
{
this->elementCount = newCount;
}
inline void pvInfo::unlinkPV ()
{
this->pPV = NULL;
}
inline pvEntry::pvEntry ( pvInfo & infoIn, exServer & casIn,
const char * pAliasName ) :
stringId ( pAliasName ), info ( infoIn ), cas ( casIn )
{
assert ( this->stringId::resourceName() != NULL );
}
inline pvEntry::~pvEntry ()
{
this->cas.removeAliasName ( *this );
}
inline void pvEntry::destroy ()
{
delete this;
}
inline void exServer::removeAliasName ( pvEntry & entry )
{
pvEntry * pE;
pE = this->stringResTbl.remove ( entry );
assert ( pE == &entry );
}
inline double exPV::getScanPeriod ()
{
double curPeriod = this->info.getScanPeriod ();
if ( ! this->interest ) {
curPeriod *= 10.0L;
}
return curPeriod;
}
inline caStatus exPV::readNoCtx ( smartGDDPointer pProtoIn )
{
return this->ft.read ( *this, *pProtoIn );
}
inline const pvInfo & exPV::getPVInfo ()
{
return this->info;
}
inline const char * exPV::getName () const
{
return this->info.getName();
}
inline void exServer::removeIO()
{
if ( this->simultAsychIOCount > 0u ) {
this->simultAsychIOCount--;
}
else {
fprintf ( stderr,
"simultAsychIOCount underflow?\n" );
}
}
inline unsigned exServer :: maxSimultAsyncIO () const
{
return this->_maxSimultAsyncIO;
}
inline exChannel::exChannel ( const casCtx & ctxIn ) :
casChannel(ctxIn)
{
}

View File

@@ -1,264 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "exServer.h"
#include "gddApps.h"
#define myPI 3.14159265358979323846
//
// SUN C++ does not have RAND_MAX yet
//
#if ! defined(RAND_MAX)
//
// Apparently SUN C++ is using the SYSV version of rand
//
# if 0
# define RAND_MAX INT_MAX
# else
# define RAND_MAX SHRT_MAX
# endif
#endif
//
// special gddDestructor guarantees same form of new and delete
//
class exVecDestructor: public gddDestructor {
virtual void run (void *);
};
//
// exVectorPV::maxDimension()
//
unsigned exVectorPV::maxDimension() const
{
return 1u;
}
//
// exVectorPV::maxBound()
//
aitIndex exVectorPV::maxBound (unsigned dimension) const
{
if (dimension==0u) {
return this->info.getCapacity();
}
else {
return 0u;
}
}
//
// exVectorPV::scan
//
void exVectorPV::scan()
{
static epicsTime startTime = epicsTime::getCurrent();
// update current time
//
this->currentTime = epicsTime::getCurrent();
// demonstrate a changing array size
unsigned ramp = 15 & (unsigned) (this->currentTime - startTime);
unsigned newSize = this->info.getCapacity();
if (newSize > ramp) {
newSize -= ramp;
}
smartGDDPointer pDD = new gddAtomic (gddAppType_value, aitEnumFloat64,
1u, newSize);
if ( ! pDD.valid () ) {
return;
}
//
// smart pointer class manages reference count after this point
//
gddStatus gdds = pDD->unreference();
assert(!gdds);
//
// allocate array buffer
//
aitFloat64 * pF = new aitFloat64 [newSize];
if (!pF) {
return;
}
exVecDestructor * pDest = new exVecDestructor;
if (!pDest) {
delete [] pF;
return;
}
//
// install the buffer into the DD
// (do this before we increment pF)
//
pDD->putRef(pF, pDest);
//
// double check for reasonable bounds on the
// current value
//
const aitFloat64 *pCF = NULL, *pCFE = NULL;
if (this->pValue.valid () &&
this->pValue->dimension() == 1u) {
const gddBounds *pB = this->pValue->getBounds();
pCF = *this->pValue;
pCFE = &pCF[pB->size()];
}
aitFloat64 * pFE = &pF[newSize];
while (pF < pFE) {
double radians = (rand () * 2.0 * myPI)/RAND_MAX;
double newValue;
if (pCF && pCF < pCFE) {
newValue = *pCF++;
}
else {
newValue = 0.0f;
}
newValue += (sin (radians) / 10.0);
double limit = this->info.getHopr();
newValue = epicsMin (newValue, limit);
limit = this->info.getLopr();
newValue = epicsMax (newValue, limit);
*pF++ = newValue;
}
aitTimeStamp gddts = this->currentTime;
pDD->setTimeStamp ( & gddts );
caStatus status = this->update ( *pDD );
this->info.setElementCount(newSize);
if ( status != S_casApp_success ) {
errMessage (status, "vector scan update failed\n");
}
}
//
// exVectorPV::updateValue ()
//
// NOTES:
// 1) This should have a test which verifies that the
// incoming value in all of its various data types can
// be translated into a real number?
// 2) We prefer to unreference the old PV value here and
// reference the incomming value because this will
// result in value change events each retaining an
// independent value on the event queue. With large arrays
// this may result in too much memory consumtion on
// the event queue.
//
caStatus exVectorPV::updateValue ( const gdd & value )
{
aitUint32 newSize = 0;
//
// Check bounds of incoming request
// (and see if we are replacing all elements -
// replaceOk==true)
//
// Perhaps much of this is unnecessary since the
// server lib checks the bounds of all requests
//
if ( value.isAtomic()) {
if ( value.dimension() != 1u ) {
return S_casApp_badDimension;
}
const gddBounds* pb = value.getBounds ();
if ( pb[0u].first() != 0u ) {
return S_casApp_outOfBounds;
}
newSize = pb[0u].size();
if ( newSize > this->info.getCapacity() ) {
return S_casApp_outOfBounds;
}
}
else if ( ! value.isScalar() ) {
//
// no containers
//
return S_casApp_outOfBounds;
}
//
// Create a new array data descriptor
// (so that old values that may be referenced on the
// event queue are not replaced)
//
smartGDDPointer pNewValue ( new gddAtomic ( gddAppType_value, aitEnumFloat64,
1u, newSize ) );
if ( ! pNewValue.valid() ) {
return S_casApp_noMemory;
}
//
// smart pointer class takes care of the reference count
// from here down
//
gddStatus gdds = pNewValue->unreference( );
assert ( ! gdds );
//
// allocate array buffer
//
aitFloat64 * pF = new aitFloat64 [newSize];
if (!pF) {
return S_casApp_noMemory;
}
//
// Install (and initialize) array buffer
// if no old values exist
//
for ( unsigned i = 0u; i < newSize; i++ ) {
pF[i] = 0.0f;
}
exVecDestructor * pDest = new exVecDestructor;
if (!pDest) {
delete [] pF;
return S_casApp_noMemory;
}
//
// install the buffer into the DD
// (do this before we increment pF)
//
pNewValue->putRef ( pF, pDest );
//
// copy in the values that they are writing
//
gdds = pNewValue->put( & value );
if ( gdds ) {
return S_cas_noConvert;
}
this->pValue = pNewValue;
this->info.setElementCount(newSize);
return S_casApp_success;
}
//
// exVecDestructor::run()
//
// special gddDestructor guarantees same form of new and delete
//
void exVecDestructor::run ( void *pUntyped )
{
aitFloat64 * pf = reinterpret_cast < aitFloat64 * > ( pUntyped );
delete [] pf;
}

View File

@@ -1,151 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include "envDefs.h"
#include "errlog.h"
#include "exServer.h"
#include "fdManager.h"
//
// main()
// (example single threaded ca server tool main loop)
//
extern int main ( int argc, const char **argv )
{
epicsTime begin (epicsTime::getCurrent());
exServer *pCAS;
unsigned debugLevel = 0u;
double executionTime = 0.0;
double asyncDelay = 0.1;
char pvPrefix[128] = "";
unsigned aliasCount = 1u;
unsigned scanOn = true;
unsigned syncScan = true;
char arraySize[64] = "";
bool forever = true;
unsigned maxSimultAsyncIO = 1000u;
int i;
i = 1;
while ( i < argc ) {
if ( strcmp ( argv[i], "-d" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%u", & debugLevel ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i],"-t" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%lf", & executionTime ) == 1 ) {
forever = false;
i += 2;
continue;
}
}
if ( strcmp ( argv[i], "-p" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%127s", pvPrefix ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i], "-c" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%u", & aliasCount ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i], "-s" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%u", & scanOn ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i], "-a" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%63s", arraySize ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i],"-ss" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%u", & syncScan ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i],"-ad" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%lf", & asyncDelay ) == 1 ) {
i += 2;
continue;
}
}
if ( strcmp ( argv[i],"-an" ) == 0 ) {
if ( i+1 < argc && sscanf ( argv[i+1], "%u", & maxSimultAsyncIO ) == 1 ) {
i += 2;
continue;
}
}
printf ( "\"%s\"?\n", argv[i] );
if ( i + 1 < argc ) {
printf ( "\"%s\"?\n", argv[i+1] );
}
printf (
"usage: %s [-d <debug level> -t <execution time> -p <PV name prefix> "
"-c <numbered alias count> -s <1=scan on (default), 0=scan off> "
"-ss <1=synchronous scan (default), 0=asynchronous scan> "
"-a <max array size> -ad <async delay> "
"-an <max simultaneous async>\n",
argv[0]);
return (1);
}
if ( arraySize[0] != '\0' ) {
epicsEnvSet ( "EPICS_CA_MAX_ARRAY_BYTES", arraySize );
}
try {
pCAS = new exServer ( pvPrefix, aliasCount,
scanOn != 0, syncScan == 0, asyncDelay,
maxSimultAsyncIO );
}
catch ( ... ) {
errlogPrintf ( "Server initialization error\n" );
errlogFlush ();
return (-1);
}
pCAS->setDebugLevel(debugLevel);
if ( forever ) {
//
// loop here forever
//
while (true) {
fileDescriptorManager.process(1000.0);
}
}
else {
double delay = epicsTime::getCurrent() - begin;
//
// loop here untill the specified execution time
// expires
//
while ( delay < executionTime ) {
fileDescriptorManager.process ( executionTime - delay );
delay = epicsTime::getCurrent() - begin;
}
}
//pCAS->show(2u);
delete pCAS;
errlogFlush ();
return (0);
}

View File

@@ -1,844 +0,0 @@
file {
name="test.dl"
}
display {
magic="305419896"
majv="2"
mnrv="4"
ndyng="0"
npc="5"
nstr="8"
ndynamic="12"
nplot="0"
nrd="0"
nes="0"
nkd="0"
object {
x="0"
y="0"
width="421"
height="306"
}
clr="0"
bclr="1"
nwords_dspy="1106"
nwords_sta="28"
nwords_cmap="36"
nwords_crules="106"
odyng="306"
osta="278"
odynamic="306"
oplot="1106"
ord="1106"
oes="1106"
okd="1106"
opc="58"
ostr="88"
ocmap="136"
ocrules="172"
style="solid"
fill="outline"
width="0"
clrmod="static"
vismod="static"
clrrule="alarm"
pv=""
cmap=""
}
"<<color map>>" {
ncolors="8"
dl_color {
r="255"
g="255"
b="255"
inten="255"
blink="off"
RISCpad="128"
}
dl_color {
r="0"
g="0"
b="0"
inten="0"
blink="off"
RISCpad="75"
}
dl_color {
r="255"
g="0"
b="0"
inten="255"
blink="off"
RISCpad="-14684"
}
dl_color {
r="255"
g="0"
b="0"
inten="255"
blink="on"
RISCpad="14744"
}
dl_color {
r="255"
g="255"
b="0"
inten="255"
blink="off"
RISCpad="-16536"
}
dl_color {
r="255"
g="255"
b="0"
inten="255"
blink="on"
RISCpad="-15536"
}
dl_color {
r="0"
g="0"
b="255"
inten="255"
blink="off"
RISCpad="-28408"
}
dl_color {
r="0"
g="0"
b="255"
inten="255"
blink="on"
RISCpad="0"
}
}
"<<color rules>>" {
nrules="1"
dl_color_rule {
name="alarm"
info[0] {
chan="$(C).SEVR"
value="MAJOR"
connector="use"
comparator="equals"
clr="2"
RISCpad="0"
}
info[1] {
chan="$(C).SEVR"
value="MINOR"
connector="use"
comparator="equals"
clr="4"
RISCpad="127"
}
info[2] {
chan="$(C).SEVR"
value="INFO"
connector="use"
comparator="equals"
clr="6"
RISCpad="44"
}
info[3] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="-128"
}
info[4] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="-1"
}
info[5] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="-104"
}
info[6] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="-1"
}
info[7] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="8"
}
info[8] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="120"
}
info[9] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="1"
}
info[10] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="7"
}
info[11] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="19"
}
info[12] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="48"
}
info[13] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="28"
}
info[14] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="-88"
}
info[15] {
chan=""
value=""
connector="use"
comparator="equals"
clr="1"
RISCpad="0"
}
fg_enable="on"
bg_enable="on"
default_fg="0"
default_bg="1"
}
}
"<<basic attribute>>" {
attr {
clr="0"
style="solid"
fill="outline"
width="0"
}
}
"text" {
object {
x="44"
y="16"
width="104"
height="14"
groupid="0"
}
textix="Sync"
align="horiz. left"
RISC_pad="0"
}
"text" {
object {
x="260"
y="13"
width="92"
height="17"
groupid="0"
}
textix="Async"
align="horiz. left"
RISC_pad="0"
}
"indicator" {
object {
x="15"
y="88"
width="170"
height="22"
groupid="0"
}
monitor {
chan="fred"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
RISC_pad="0"
}
"text update" {
object {
x="16"
y="133"
width="169"
height="17"
groupid="0"
}
monitor {
chan="fred"
clr="0"
bclr="1"
label="none"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="append"
decorate="none"
convertFunc=""
convertParams=""
}
align="horiz. left"
format="decimal"
}
"valuator" {
object {
x="15"
y="43"
width="168"
height="26"
groupid="0"
}
control {
chan="fred"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
gain="coarse"
sendMode="send on motion"
increment="0"
}
"indicator" {
object {
x="215"
y="81"
width="170"
height="30"
groupid="0"
}
monitor {
chan="freddy"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
RISC_pad="0"
}
"text update" {
object {
x="216"
y="133"
width="171"
height="18"
groupid="0"
}
monitor {
chan="freddy"
clr="0"
bclr="1"
label="none"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="append"
decorate="none"
convertFunc=""
convertParams=""
}
align="horiz. left"
format="decimal"
}
"valuator" {
object {
x="215"
y="43"
width="168"
height="28"
groupid="0"
}
control {
chan="freddy"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
gain="coarse"
sendMode="send on motion"
increment="0"
}
"indicator" {
object {
x="16"
y="225"
width="171"
height="19"
groupid="0"
}
monitor {
chan="jane"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
RISC_pad="0"
}
"text update" {
object {
x="17"
y="259"
width="170"
height="20"
groupid="0"
}
monitor {
chan="jane"
clr="0"
bclr="1"
label="none"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="append"
decorate="none"
convertFunc=""
convertParams=""
}
align="horiz. left"
format="decimal"
}
"valuator" {
object {
x="15"
y="187"
width="170"
height="19"
groupid="0"
}
control {
chan="jane"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
gain="coarse"
sendMode="send on motion"
increment="0"
}
"indicator" {
object {
x="219"
y="218"
width="173"
height="23"
groupid="0"
}
monitor {
chan="janet"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
RISC_pad="0"
}
"text update" {
object {
x="220"
y="257"
width="174"
height="20"
groupid="0"
}
monitor {
chan="janet"
clr="0"
bclr="1"
label="none"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="append"
decorate="none"
convertFunc=""
convertParams=""
}
align="horiz. left"
format="decimal"
}
"valuator" {
object {
x="219"
y="188"
width="171"
height="21"
groupid="0"
}
control {
chan="janet"
clr="0"
bclr="1"
label="limits"
clrmod="static"
rulechan[0] = ""
rulechan[1] = ""
rulechan[2] = ""
rulechan[3] = ""
rulechan[4] = ""
rulechan[5] = ""
rulechan[6] = ""
rulechan[7] = ""
rulechan[8] = ""
rulechan[9] = ""
rulechan[10] = ""
rulechan[11] = ""
rulechan[12] = ""
rulechan[13] = ""
rulechan[14] = ""
rulechan[15] = ""
clrrule="alarm"
clrargs=""
rulecolorbg="0"
rulecolorfg="0"
hdl="0"
ldl="0"
prec="-1"
newunits=""
units="none"
decorate="none"
convertFunc=""
convertParams=""
}
direction="down"
gain="coarse"
sendMode="send on motion"
increment="0"
}

View File

@@ -1,78 +0,0 @@
/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// Author: Jeff HIll (LANL)
//
#include <vxWorks.h>
#include <taskLib.h>
#include "exServer.h"
//
// so we can call this from the vxWorks shell
//
extern "C" {
exServer *pExampleCAS;
//
// excas ()
// (vxWorks example server entry point)
//
int excas (unsigned debugLevel, unsigned delaySec)
{
epicsTime begin(epicsTime::getCurrent());
exServer *pCAS;
pCAS = new exServer(32u,5u,500u);
if (!pCAS) {
return (-1);
}
pCAS->setDebugLevel(debugLevel);
pExampleCAS = pCAS;
if (delaySec==0u) {
//
// loop here forever
//
while (1) {
taskDelay(10);
}
}
else {
epicsTime total( ((float)delaySec) );
epicsTime delay(epicsTime::getCurrent() - begin);
//
// loop here untill the specified execution time
// expires
//
while (delay < total) {
taskDelay(10);
delay = epicsTime::getCurrent() - begin;
}
}
pCAS->show(debugLevel);
pExampleCAS = NULL;
delete pCAS;
return 0;
}
int excasShow(unsigned level)
{
if (pExampleCAS!=NULL) {
pExampleCAS->show(level);
}
return 0;
}
} // extern "C"

View File

@@ -1,29 +0,0 @@
# CONFIG - Load build configuration data
#
# Do not make changes to this file!
# Allow user to override where the build rules come from
RULES = $(EPICS_BASE)
# RELEASE files point to other application tops
include $(TOP)/configure/RELEASE
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).Common
ifdef T_A
-include $(TOP)/configure/RELEASE.Common.$(T_A)
-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A)
endif
CONFIG = $(RULES)/configure
include $(CONFIG)/CONFIG
# Override the Base definition:
INSTALL_LOCATION = $(TOP)
# CONFIG_SITE files contain other build configuration settings
include $(TOP)/configure/CONFIG_SITE
-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).Common
ifdef T_A
-include $(TOP)/configure/CONFIG_SITE.Common.$(T_A)
-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A)
endif

View File

@@ -1,43 +0,0 @@
# CONFIG_SITE
# Make any application-specific changes to the EPICS build
# configuration variables in this file.
#
# Host/target specific settings can be specified in files named
# CONFIG_SITE.$(EPICS_HOST_ARCH).Common
# CONFIG_SITE.Common.$(T_A)
# CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A)
# CHECK_RELEASE controls the consistency checking of the support
# applications pointed to by the RELEASE* files.
# Normally CHECK_RELEASE should be set to YES.
# Set CHECK_RELEASE to NO to disable checking completely.
# Set CHECK_RELEASE to WARN to perform consistency checking but
# continue building even if conflicts are found.
CHECK_RELEASE = YES
# Set this when you only want to compile this application
# for a subset of the cross-compiled target architectures
# that Base is built for.
#CROSS_COMPILER_TARGET_ARCHS = vxWorks-ppc32
# To install files into a location other than $(TOP) define
# INSTALL_LOCATION here.
#INSTALL_LOCATION=</absolute/path/to/install/top>
# Set this when the IOC and build host use different paths
# to the install location. This may be needed to boot from
# a Microsoft FTP server say, or on some NFS configurations.
#IOCS_APPL_TOP = </IOC's/absolute/path/to/install/top>
# For application debugging purposes, override the HOST_OPT and/
# or CROSS_OPT settings from base/configure/CONFIG_SITE
#HOST_OPT = NO
#CROSS_OPT = NO
# These allow developers to override the CONFIG_SITE variable
# settings without having to modify the configure/CONFIG_SITE
# file itself.
-include $(TOP)/../CONFIG_SITE.local
-include $(TOP)/configure/CONFIG_SITE.local

View File

@@ -1,8 +0,0 @@
TOP=..
include $(TOP)/configure/CONFIG
TARGETS = $(CONFIG_TARGETS)
CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS)))
include $(TOP)/configure/RULES

View File

@@ -1,43 +0,0 @@
# RELEASE - Location of external support modules
#
# IF YOU MAKE ANY CHANGES to this file you must subsequently
# do a "gnumake rebuild" in this application's top level
# directory.
#
# The build process does not check dependencies against files
# that are outside this application, thus you should do a
# "gnumake rebuild" in the top level directory after EPICS_BASE
# or any other external module pointed to below is rebuilt.
#
# Host- or target-specific settings can be given in files named
# RELEASE.$(EPICS_HOST_ARCH).Common
# RELEASE.Common.$(T_A)
# RELEASE.$(EPICS_HOST_ARCH).$(T_A)
#
# This file is parsed by both GNUmake and an EPICS Perl script,
# so it can ONLY contain definititions of paths to other support
# modules, variable definitions that are used in module paths,
# and include statements that pull in other RELEASE files.
# Variables may be used before their values have been set.
# Build variables that are NOT used in paths should be set in
# the CONFIG_SITE file.
# Variables and paths to dependent modules:
#MODULES = /path/to/modules
#MYMODULE = $(MODULES)/my-module
# If using the sequencer, point SNCSEQ at its top directory:
#SNCSEQ = $(MODULES)/seq-ver
# EPICS_BASE should appear last so earlier modules can override stuff:
EPICS_BASE = _EPICS_BASE_
# Set RULES here if you want to use build rules from somewhere
# other than EPICS_BASE:
#RULES = $(MODULES)/build-rules
# These allow developers to override the RELEASE variable settings
# without having to modify the configure/RELEASE file itself.
-include $(TOP)/../RELEASE.local
-include $(TOP)/configure/RELEASE.local

View File

@@ -1,6 +0,0 @@
# RULES
include $(CONFIG)/RULES
# Library should be rebuilt because LIBOBJS may have changed.
$(LIBNAME): ../Makefile

View File

@@ -1,2 +0,0 @@
#RULES.ioc
include $(CONFIG)/RULES.ioc

View File

@@ -1,2 +0,0 @@
#RULES_DIRS
include $(CONFIG)/RULES_DIRS

View File

@@ -1,3 +0,0 @@
#RULES_TOP
include $(CONFIG)/RULES_TOP

View File

@@ -16,6 +16,7 @@
#include "epicsThread.h"
#include "iocInit.h"
#include "dbBase.h"
#include "dbDefs.h"
#include "link.h"
#include "dbAccess.h"
#include "registry.h"
@@ -565,7 +566,7 @@ void testJLink(void)
testIocInitOk();
eltc(1);
testNumZ(3);
testNumZ(6);
testdbPutFieldOk("j1.PROC", DBF_LONG, 1);
testdbPutFieldOk("j2.PROC", DBF_LONG, 1);
@@ -576,21 +577,31 @@ void testJLink(void)
testdbGetFieldEqual("j2.VAL", DBF_LONG, 2);
testdbGetFieldEqual("j3.VAL", DBF_LONG, 3);
testNumZ(3);
testNumZ(6);
testdbPutFieldOk("j1.INP", DBF_STRING, "{\"z\":{\"good\":4}}");
testdbPutFieldOk("j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("j1.VAL", DBF_LONG, 4);
testNumZ(3);
testdbPutFieldOk("j2.TSEL", DBF_STRING, "{\"z\":{\"good\":0}}");
testdbPutFieldOk("j2.PROC", DBF_LONG, 1);
testNumZ(7);
testdbPutFieldFail(S_dbLib_badField, "j1.INP", DBF_STRING, "{\"z\":{\"fail\":5}}");
testdbPutFieldFail(S_dbLib_badField, "j1.INP", DBF_STRING, "{\"z\":{\"good\":6}");
testdbPutFieldOk("j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("j1.VAL", DBF_LONG, 4);
/* put failure in parsing stage doesn't modify link */
/* put failures in parsing stage don't modify link */
testdbGetFieldEqual("j1.INP", DBF_STRING, "{\"z\":{\"good\":4}}");
testNumZ(3);
testNumZ(7);
/* Check SDIS using a JSON link prevents processing */
testdbPutFieldOk("j1.SDIS", DBF_STRING, "{\"z\":{\"good\":1}}");
testdbPutFieldOk("j1.INP", DBF_STRING, "{\"z\":{\"good\":1}}");
testdbPutFieldOk("j1.PROC", DBF_LONG, 1);
testdbGetFieldEqual("j1.VAL", DBF_LONG, 4);
testIocShutdownOk();
@@ -599,9 +610,81 @@ void testJLink(void)
testdbCleanup();
}
static
void testTSEL(void)
{
dbCommon *rec[2];
dbLocker *locker;
testDiag("Test TSEL link to .TIME");
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
dbTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("dbPutLinkTest.db", NULL, NULL);
rec[0] = testdbRecordPtr("time:one");
rec[1] = testdbRecordPtr("time:two");
eltc(0);
testIocInitOk();
eltc(1);
locker = dbLockerAlloc(rec, NELEMENTS(rec), 0);
if(!locker)
testAbort("dbLockerAlloc() fails");
testdbPutFieldOk("time:one.PROC", DBF_LONG, 1);
/* wait a bit so that we would get different timestamps */
epicsThreadSleep(0.001);
testdbPutFieldOk("time:two.PROC", DBF_LONG, 1);
#define COMPARE(MSG, C, TS1, TS2) testOk((C)^((TS1)->secPastEpoch == (TS2)->secPastEpoch && (TS1)->nsec == (TS2)->nsec), \
MSG " %u:%u == %u:%u", (unsigned)(TS1)->secPastEpoch, (unsigned)(TS1)->nsec, \
(unsigned)(TS2)->secPastEpoch, (unsigned)(TS2)->nsec)
testDiag("Check initially connected TSEL link");
dbScanLockMany(locker);
COMPARE("first", 0, &rec[0]->time, &rec[1]->time);
testOk1(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME);
dbScanUnlockMany(locker);
testdbPutFieldOk("time:two.TSEL", DBF_STRING, "");
testdbPutFieldOk("time:two.PROC", DBF_LONG, 1);
testDiag("Check no TSEL link");
dbScanLockMany(locker);
COMPARE("second", 1, &rec[0]->time, &rec[1]->time);
testOk1(!(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME));
dbScanUnlockMany(locker);
testdbPutFieldOk("time:two.TSEL", DBF_STRING, "time:one.TIME");
testdbPutFieldOk("time:two.PROC", DBF_LONG, 1);
testDiag("Check re-connected TSEL link");
dbScanLockMany(locker);
COMPARE("third", 0, &rec[0]->time, &rec[1]->time);
testOk1(rec[1]->tsel.flags & DBLINK_FLAG_TSELisTIME);
dbScanUnlockMany(locker);
dbLockerFree(locker);
testIocShutdownOk();
testdbCleanup();
#undef COMPARE
}
MAIN(dbPutLinkTest)
{
testPlan(301);
testPlan(320);
testLinkParse();
testLinkFailParse();
testCADBSet();
@@ -610,5 +693,6 @@ MAIN(dbPutLinkTest)
testLinkInitFail();
testLinkFail();
testJLink();
testTSEL();
return testDone();
}

View File

@@ -47,3 +47,8 @@ record(x, "rVXI_IO2") {
field(DTYP, "Unit Test VXI_IO")
field(INP, "#V81 S82 @parm2 VXI_IO")
}
record(x, "time:one") {}
record(x, "time:two") {
field(TSEL, "time:one.TIME")
}

View File

@@ -1,10 +1,14 @@
record(x, "j1") {
field(INP, {z:{good:1}})
field(TSEL, {z:{good:0}})
field(SDIS, {z:{good:0}})
field(FLNK, {z:{good:0}})
}
record(x, "j2") {
field(INP, {z:{good:2}})
field(TSEL, "j1.TIME")
}
record(x, "j3") {

View File

@@ -210,16 +210,34 @@ static void testEventRecord()
testdbCleanup();
}
void testInt64Inputs(void)
{
testDiag("testInt64Inputs");
startTestIoc("linkInitTest.db");
testdbGetFieldEqual("i1.VAL", DBR_INT64, 1234567890123456789LL);
testdbGetFieldEqual("i2.VAL", DBR_INT64, 1234567890123456789LL);
testdbGetFieldEqual("i3.VAL", DBR_INT64, 1234567890123456789LL);
testdbGetFieldEqual("i4.NORD", DBR_LONG, 1);
testdbGetFieldEqual("i4.VAL", DBR_INT64, 1234567890123456789LL);
testIocShutdownOk();
testdbCleanup();
}
MAIN(linkInitTest)
{
testPlan(72);
testPlan(77);
testLongStringInit();
testCalcInit();
testPrintfStrings();
testArrayInputs();
testEventRecord();
testInt64Inputs();
return testDone();
}

View File

@@ -19,7 +19,8 @@ record(ai, "emptylink" ) {
field(INP, {calc: {expr:"0"}})
}
record(ai, "emptylink1" ) {
field(INP, {calc: {expr:"1"}})
field(INP, {calc: {expr:"A", args:[1], time:"a"}})
field(TSEL, -2)
}
record(printf, "printf1") {
@@ -88,3 +89,19 @@ record(event, "ev1") {
record(event, "ev2") {
field(INP, "count1.EVNT")
}
record(int64in, "i1") {
field(INP, [1234567890123456789,])
}
record(int64in, "i2") {
field(INP, {const:1234567890123456789,})
}
record(int64in, "i3") {
field(INP, 1234567890123456789)
}
record(waveform, "i4") {
field(NELM, 1)
field(FTVL, "INT64")
field(INP, [1234567890123456789,])
}

View File

@@ -24,24 +24,27 @@ void checkDtyp(const char *rec)
strcpy(dtyp, rec);
strcat(dtyp, ".DTYP");
testdbGetFieldEqual(dtyp, DBF_LONG, 0); /* Soft Channel = 0 */
testdbGetFieldEqual(dtyp, DBR_LONG, 0);
testdbGetFieldEqual(dtyp, DBR_STRING, "Soft Channel");
}
static
void checkInput(const char *rec, int value)
void doProcess(const char *rec)
{
char proc[16];
testDiag("Checking record '%s'", rec);
strcpy(proc, rec);
strcat(proc, ".PROC");
testdbPutFieldOk(proc, DBF_CHAR, 1);
testdbGetFieldEqual(rec, DBF_LONG, value);
testdbPutFieldOk(proc, DBR_CHAR, 1);
}
/* Group 0 are all soft-channel input records with INP being a DB link
* to the PV 'source'. Their VAL fields all start out with the default
* value for the type, i.e. 0 or an empty string. Triggering record
* processing should read the integer value from the 'source' PV.
*/
static
void testGroup0(void)
{
@@ -52,18 +55,27 @@ void testGroup0(void)
testDiag("============ Starting %s ============", EPICS_FUNCTION);
testdbPutFieldOk("source", DBF_LONG, 1);
testdbPutFieldOk("source", DBR_LONG, 1);
for (rec = records; *rec; rec++) {
checkInput(*rec, 1);
if (strcmp(*rec, "lsi0") != 0)
testdbGetFieldEqual(*rec, DBR_LONG, 0);
checkDtyp(*rec);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, 1);
}
testdbPutFieldOk("source", DBF_LONG, 0);
testdbPutFieldOk("source", DBR_LONG, 0);
for (rec = records; *rec; rec++) {
checkInput(*rec, 0);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, 0);
}
}
/* Group 1 are all soft-channel input records with INP being a non-zero
* "const" JSON-link, 9 for most records, 1 for the binary. Their VAL
* fields should all be initialized to that constant value. Triggering
* record processing should succeed, but shouldn't change VAL.
*/
static
void testGroup1(void)
{
@@ -77,11 +89,40 @@ void testGroup1(void)
testDiag("============ Starting %s ============", EPICS_FUNCTION);
for (rec = records; *rec; rec++) {
checkInput(*rec, init);
init = 9; /* remainder initialize to 9 */
testdbGetFieldEqual(*rec, DBR_LONG, init);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, init);
init = 9; /* other records initialize to 9 */
}
}
/* Group 2 are all soft-channel input records with INP being a CONSTANT
* link with value 9 for most records, 1 for the binary. Their VAL
* fields should all be initialized to that constant value. Triggering
* record processing should succeed, but shouldn't change VAL.
*/
static
void testGroup2(void)
{
const char ** rec;
const char * records[] = {
"bi2",
"ai2", "di2", "ii2", "li2", "mi2", NULL
};
int init = 1; /* bi1 initializes to 1 */
testDiag("============ Starting %s ============", EPICS_FUNCTION);
for (rec = records; *rec; rec++) {
testdbGetFieldEqual(*rec, DBR_LONG, init);
doProcess(*rec);
testdbGetFieldEqual(*rec, DBR_LONG, init);
init = 9; /* other records initialize to 9 */
}
}
int dest;
static
@@ -96,13 +137,17 @@ void checkOutput(const char *rec, int value)
{
testDiag("Checking record '%s'", rec);
testdbPutFieldOk(rec, DBF_LONG, value);
testdbPutFieldOk(rec, DBR_LONG, value);
testOk(dest == value, "value %d output -> %d", value, dest);
}
/* Group 3 are all soft-channel output records with OUT being a DB link
* to the PV 'dest' with PP. Putting a value to the record writes that
* value to 'dest' and processes it.
*/
static
void testGroup2(void)
void testGroup3(void)
{
const char ** rec;
const char * records[] = {
@@ -124,8 +169,11 @@ void testGroup2(void)
checkOutput("do0.B0", 0);
}
/* Group 4 are all soft-channel output records with OUT being empty
* (i.e. a CONSTANT link). Putting a value to the record must succeed.
*/
static
void testGroup3(void)
void testGroup4(void)
{
const char ** rec;
const char * records[] = {
@@ -143,7 +191,7 @@ void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(softTest)
{
testPlan(114);
testPlan(163);
testdbPrepare();
testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
@@ -161,6 +209,7 @@ MAIN(softTest)
testGroup1();
testGroup2();
testGroup3();
testGroup4();
testIocShutdownOk();
testdbCleanup();

View File

@@ -1,5 +1,7 @@
# Group 0 are input records with INP being a DB link to 'source'.
# Processing them reads that value.
# Group 0 are all soft-channel input records with INP being a DB link
# to the PV 'source'. Their VAL fields all start out with the default
# value for the type, i.e. 0 or an empty string. Triggering record
# processing should read the integer value from the 'source' PV.
record(longin, "source") {}
@@ -57,8 +59,11 @@ record(stringin, "si0") {
field(INP, "source")
}
# Group 1 are input records with INP being a non-zero constant.
# Processing them succeeds but does not change VAL.
# Group 1 are all soft-channel input records with INP being a non-zero
# "const" JSON-link, 9 for most records, 1 for the binary. Their VAL
# fields should all be initialized to that constant value. Triggering
# record processing should succeed, but shouldn't change VAL.
record(ai, "ai1") {
field(DTYP, "Soft Channel")
@@ -115,8 +120,60 @@ record(stringin, "si1") {
}
# Group 2 are output records with OUT being a DB link to 'dest' with PP.
# Putting a value to them writes that value to 'dest'.
# Group 2 are all soft-channel input records with INP being a CONSTANT
# link with value 9 for most records, 1 for the binary. Their VAL
# fields should all be initialized to that constant value. Triggering
# record processing should succeed, but shouldn't change VAL.
record(ai, "ai2") {
field(DTYP, "Soft Channel")
field(INP, 9)
}
record(bi, "bi2") {
field(DTYP, "Soft Channel")
field(INP, 1)
field(ZNAM, "Zero")
field(ONAM, "One")
}
record(int64in, "ii2") {
field(DTYP, "Soft Channel")
field(INP, 9)
}
record(longin, "li2") {
field(DTYP, "Soft Channel")
field(INP, 9)
}
record(mbbiDirect, "di2") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, 9)
}
record(mbbi, "mi2") {
field(DTYP, "Soft Channel")
field(NOBT, 4)
field(INP, 9)
field(ZRST, "Zero")
field(ONST, "One")
field(TWST, "Two")
field(THST, "Three")
field(FRST, "Four")
field(FVST, "Five")
field(SXST, "Six")
field(SVST, "Seven")
field(EIST, "Eight")
field(NIST, "Nine")
field(TEST, "Ten")
field(ELST, "Eleven")
field(TWST, "Twelve")
field(TTST, "Thirteen")
field(FTST, "Fourteen")
field(FFST, "Fifteen")
}
# Group 3 are all soft-channel output records with OUT being a DB link
# to the PV 'dest' with PP. Putting a value to the record writes that
# value to 'dest' and processes it.
record(sub, "dest") {
field(SNAM, "destSubr")
@@ -177,8 +234,8 @@ record(stringout, "so0") {
}
# Group 3 are output records with OUT being empty (a constant link).
# Putting a value to them must succeed.
# Group 4 are all soft-channel output records with OUT being empty
# (i.e. a CONSTANT link). Putting a value to the record must succeed.
record(ao, "ao1") {
field(DTYP, "Soft Channel")