/*************************************************************************\ * Copyright (c) 2009 Helmholtz-Zentrum Berlin fuer Materialien und Energie. * Copyright (c) 2006 Diamond Light Source Ltd. * 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. * Copyright (c) 2002 Berliner Elektronenspeicherringgesellschaft fuer * Synchrotronstrahlung. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* * Author: Ralph Lange (BESSY) * * Modification History * 2006/01/17 Malcolm Walters (Tessella/Diamond Light Source) * Added put_callback option - heavily based on caget * 2008/03/06 Andy Foster (OSL/Diamond Light Source) * Remove timeout dependency of ca_put_callback by using an EPICS event * (semaphore), i.e. remove ca_pend_event time slicing. * 2008/04/16 Ralph Lange (BESSY) * Updated usage info * 2009/03/31 Larry Hoff (BNL) * Added field separators * 2009/04/01 Ralph Lange (HZB/BESSY) * Added support for long strings (array of char) and quoting of nonprintable characters * */ #include #include #include #include #include #include #include #include "tool_lib.h" #define VALID_DOUBLE_DIGITS 18 /* Max usable precision for a double */ /* Different output formats */ typedef enum { plain, terse, all } OutputT; /* Different request types */ typedef enum { get, callback } RequestT; /* Valid EPICS string */ typedef char EpicsStr[MAX_STRING_SIZE]; static int nConn = 0; /* Number of connected PVs */ static epicsEventId epId; void usage (void) { fprintf (stderr, "\nUsage: caput [options] \n" " caput -a [options] ...\n\n" " -h: Help: Print this message\n" "Channel Access options:\n" " -w : Wait time, specifies CA timeout, default is %f second(s)\n" " -c: Asynchronous put (use ca_put_callback and wait for completion)\n" " -p : CA priority (0-%u, default 0=lowest)\n" "Format options:\n" " -t: Terse mode - print only sucessfully written value, without name\n" " -l: Long mode \"name timestamp value stat sevr\" (read PVs as DBR_TIME_xxx)\n" "Enum format:\n" " Default: Auto - try value as ENUM string, then as index number\n" " -n: Force interpretation of values as numbers\n" " -s: Force interpretation of values as strings\n" "Arrays:\n" " -a: Put array\n" " Value format: number of requested values, then list of values\n" " -S: Put string as an array of char (long string)\n" "\nExample: caput my_channel 1.2\n" " (puts 1.2 to my_channel)\n\n" , DEFAULT_TIMEOUT, CA_PRIORITY_MAX); } /*+************************************************************************** * * Function: put_event_handler * * Description: CA event_handler for request type callback * Sets status flags and marks as done. * * Arg(s) In: args - event handler args (see CA manual) * **************************************************************************-*/ void put_event_handler ( struct event_handler_args args ) { /* Retrieve pv from event handler structure */ pv* pPv = args.usr; /* Store status, then give EPICS event */ pPv->status = args.status; epicsEventSignal( epId ); } /*+************************************************************************** * * Function: caget * * Description: Issue read request, wait for incoming data * and print the data * * Arg(s) In: pvs - Pointer to an array of pv structures * nPvs - Number of elements in the pvs array * format - Output format * dbrType - Requested dbr type * reqElems - Requested number of (array) elements * * Return(s): Error code: 0 = OK, 1 = Error * **************************************************************************-*/ int caget (pv *pvs, int nPvs, OutputT format, chtype dbrType, unsigned long reqElems) { unsigned int i; int n, result; for (n = 0; n < nPvs; n++) { /* Set up pvs structure */ /* -------------------- */ /* Get natural type and array count */ pvs[n].nElems = ca_element_count(pvs[n].chid); pvs[n].dbfType = ca_field_type(pvs[n].chid); pvs[n].dbrType = dbrType; /* Set up value structures */ pvs[n].dbrType = dbf_type_to_DBR_TIME(pvs[n].dbfType); /* Use native type */ if (dbr_type_is_ENUM(pvs[n].dbrType)) /* Enums honour -n option */ { if (enumAsNr) pvs[n].dbrType = DBR_TIME_INT; else pvs[n].dbrType = DBR_TIME_STRING; } if (reqElems == 0 || pvs[n].nElems < reqElems) /* Adjust array count */ pvs[n].reqElems = pvs[n].nElems; else pvs[n].reqElems = reqElems; /* Issue CA request */ /* ---------------- */ if (ca_state(pvs[n].chid) == cs_conn) { nConn++; pvs[n].onceConnected = 1; /* Allocate value structure */ pvs[n].value = calloc(1, dbr_size_n(pvs[n].dbrType, pvs[n].reqElems)); if(!pvs[n].value){ fprintf(stderr,"Allocation failed\n"); exit(1); } result = ca_array_get(pvs[n].dbrType, pvs[n].reqElems, pvs[n].chid, pvs[n].value); pvs[n].status = result; } else { pvs[n].status = ECA_DISCONN; } } if (!nConn) return 1; /* No connection? We're done. */ /* Wait for completion */ /* ------------------- */ result = ca_pend_io(caTimeout); if (result == ECA_TIMEOUT) fprintf(stderr, "Read operation timed out: PV data was not read.\n"); /* Print the data */ /* -------------- */ for (n = 0; n < nPvs; n++) { switch (format) { case plain: /* Emulate old caput behaviour */ if (pvs[n].reqElems <= 1 && fieldSeparator == ' ') printf("%-30s", pvs[n].name); else printf("%s", pvs[n].name); printf("%c", fieldSeparator); case terse: if (pvs[n].status == ECA_DISCONN) printf("*** not connected\n"); else if (pvs[n].status == ECA_NORDACCESS) printf("*** no read access\n"); else if (pvs[n].status != ECA_NORMAL) printf("*** CA error %s\n", ca_message(pvs[n].status)); else if (pvs[n].value == 0) printf("*** no data available (timeout)\n"); else { if (charArrAsStr && dbr_type_is_CHAR(pvs[n].dbrType) && (reqElems || pvs[n].reqElems > 1)) { dbr_char_t *s = (dbr_char_t*) dbr_value_ptr(pvs[n].value, pvs[n].dbrType); int dlen = epicsStrnEscapedFromRawSize((char*)s, strlen((char*)s)); char *d = calloc(dlen+1, sizeof(char)); if(!d){ fprintf(stderr,"Allocation failed\n"); exit(1); } epicsStrnEscapedFromRaw(d, dlen+1, (char*)s, strlen((char*)s)); printf("%s", d); free(d); } else { if (reqElems || pvs[n].nElems > 1) printf("%lu%c", pvs[n].reqElems, fieldSeparator); for (i=0; i ... * * Arg(s) Out: none * * Return(s): Standard return code (0=success, 1=error) * **************************************************************************-*/ int main (int argc, char *argv[]) { int n = 0; int i; int result; /* CA result */ OutputT format = plain; /* User specified format */ RequestT request = get; /* User specified request type */ int isArray = 0; /* Flag for array operation */ int enumAsString = 0; /* Force ENUM values to be strings */ int count = 1; int opt; /* getopt() current option */ chtype dbrType = DBR_STRING; char *pend; EpicsStr *sbuf; double *dbuf; char *cbuf = 0; char *ebuf = 0; void *pbuf; int len = 0; int waitStatus; struct dbr_gr_enum bufGrEnum; int nPvs; /* Number of PVs */ pv* pvs = 0; /* Array of PV structures */ LINE_BUFFER(stdout); /* Configure stdout buffering */ putenv("POSIXLY_CORRECT="); /* Behave correct on GNU getopt systems */ while ((opt = getopt(argc, argv, ":cnlhatsS#:w:p:F:")) != -1) { switch (opt) { case 'h': /* Print usage */ usage(); return 0; case 'n': /* Force interpret ENUM as index number */ enumAsNr = 1; enumAsString = 0; break; case 's': /* Force interpret ENUM as menu string */ enumAsString = 1; enumAsNr = 0; break; case 'S': /* Treat char array as (long) string */ charArrAsStr = 1; isArray = 0; break; case 't': /* Select terse output format */ format = terse; break; case 'l': /* Select long output format */ format = all; break; case 'a': /* Select array mode */ isArray = 1; charArrAsStr = 0; break; case 'c': /* Select put_callback mode */ request = callback; break; case 'w': /* Set CA timeout value */ if(epicsScanDouble(optarg, &caTimeout) != 1) { fprintf(stderr, "'%s' is not a valid timeout value " "- ignored. ('caput -h' for help.)\n", optarg); caTimeout = DEFAULT_TIMEOUT; } break; case '#': /* Array count */ if (sscanf(optarg,"%d", &count) != 1) { fprintf(stderr, "'%s' is not a valid array element count " "- ignored. ('caput -h' for help.)\n", optarg); count = 0; } break; case 'p': /* CA priority */ if (sscanf(optarg,"%u", &caPriority) != 1) { fprintf(stderr, "'%s' is not a valid CA priority " "- ignored. ('caget -h' for help.)\n", optarg); caPriority = DEFAULT_CA_PRIORITY; } if (caPriority > CA_PRIORITY_MAX) caPriority = CA_PRIORITY_MAX; break; case 'F': /* Store this for output and tool_lib formatting */ fieldSeparator = (char) *optarg; break; case '?': fprintf(stderr, "Unrecognized option: '-%c'. ('caput -h' for help.)\n", optopt); return 1; case ':': fprintf(stderr, "Option '-%c' requires an argument. ('caput -h' for help.)\n", optopt); return 1; default : usage(); return 1; } } nPvs = argc - optind; /* Remaining arg list are PV names and values */ if (nPvs < 1) { fprintf(stderr, "No pv name specified. ('caput -h' for help.)\n"); return 1; } if (nPvs == 1) { fprintf(stderr, "No value specified. ('caput -h' for help.)\n"); return 1; } nPvs = 1; /* One PV - the rest is value(s) */ epId = epicsEventCreate(epicsEventEmpty); /* Create empty EPICS event (semaphore) */ /* Start up Channel Access */ result = ca_context_create(ca_enable_preemptive_callback); if (result != ECA_NORMAL) { fprintf(stderr, "CA error %s occurred while trying " "to start channel access '%s'.\n", ca_message(result), pvs[n].name); return 1; } /* Allocate PV structure array */ pvs = calloc (nPvs, sizeof(pv)); if (!pvs) { fprintf(stderr, "Memory allocation for channel structure failed.\n"); return 1; } /* Connect channels */ pvs[0].name = argv[optind] ; /* Copy PV name from command line */ result = connect_pvs(pvs, nPvs); /* If the connection fails, we're done */ if (result) { ca_context_destroy(); return result; } /* Get values from command line */ optind++; if (isArray) { optind++; /* In case of array skip first value (nr * of elements) - actual number of values is used */ count = argc - optind; } else { /* Concatenate the remaining line to one string * (sucks but is compatible to the former version) */ for (i = optind; i < argc; i++) { len += strlen(argv[i]); len++; } cbuf = calloc(len, sizeof(char)); if (!cbuf) { fprintf(stderr, "Memory allocation failed.\n"); return 1; } strcpy(cbuf, argv[optind]); if (argc > optind+1) { for (i = optind + 1; i < argc; i++) { strcat(cbuf, " "); strcat(cbuf, argv[i]); } } if ((argc - optind) >= 1) count = 1; argv[optind] = cbuf; } sbuf = calloc (count, sizeof(EpicsStr)); dbuf = calloc (count, sizeof(double)); if(!sbuf || !dbuf) { fprintf(stderr, "Memory allocation failed\n"); return 1; } /* ENUM? Special treatment */ if (ca_field_type(pvs[0].chid) == DBR_ENUM) { /* Get the ENUM strings */ result = ca_array_get (DBR_GR_ENUM, 1, pvs[0].chid, &bufGrEnum); result = ca_pend_io(caTimeout); if (result == ECA_TIMEOUT) { fprintf(stderr, "Read operation timed out: ENUM data was not read.\n"); return 1; } if (enumAsNr) { /* Interpret values as numbers */ for (i = 0; i < count; ++i) { dbuf[i] = epicsStrtod(*(argv+optind+i), &pend); if (*(argv+optind+i) == pend) { /* Conversion didn't work */ fprintf(stderr, "Enum index value '%s' is not a number.\n", *(argv+optind+i)); return 1; } if (dbuf[i] >= bufGrEnum.no_str) { fprintf(stderr, "Warning: enum index value '%s' may be too large.\n", *(argv+optind+i)); } } dbrType = DBR_DOUBLE; } else { /* Interpret values as strings */ for (i = 0; i < count; ++i) { epicsStrnRawFromEscaped(sbuf[i], sizeof(EpicsStr), *(argv+optind+i), sizeof(EpicsStr)); *( sbuf[i]+sizeof(EpicsStr)-1 ) = '\0'; dbrType = DBR_STRING; /* Compare to ENUM strings */ for (len = 0; len < bufGrEnum.no_str; len++) if (!strcmp(sbuf[i], bufGrEnum.strs[len])) break; if (len >= bufGrEnum.no_str) { /* Not a string? Try as number */ dbuf[i] = epicsStrtod(sbuf[i], &pend); if (sbuf[i] == pend || enumAsString) { fprintf(stderr, "Enum string value '%s' invalid.\n", sbuf[i]); return 1; } if (dbuf[i] >= bufGrEnum.no_str) { fprintf(stderr, "Warning: enum index value '%s' may be too large.\n", sbuf[i]); } dbrType = DBR_DOUBLE; } } } } else { /* Not an ENUM */ if (charArrAsStr) { count = len; dbrType = DBR_CHAR; ebuf = calloc(strlen(cbuf), sizeof(char)); if(!ebuf) { fprintf(stderr, "Memory allocation failed\n"); return 1; } epicsStrnRawFromEscaped(ebuf, strlen(cbuf), cbuf, strlen(cbuf)); } else { for (i = 0; i < count; ++i) { epicsStrnRawFromEscaped(sbuf[i], sizeof(EpicsStr), *(argv+optind+i), sizeof(EpicsStr)); *( sbuf[i]+sizeof(EpicsStr)-1 ) = '\0'; } dbrType = DBR_STRING; } } /* Read and print old data */ if (format != terse) { printf("Old : "); result = caget(pvs, nPvs, format, 0, 0); } /* Write new data */ if (dbrType == DBR_STRING) pbuf = sbuf; else if (dbrType == DBR_CHAR) pbuf = ebuf; else pbuf = dbuf; if (request == callback) { /* Use callback version of put */ pvs[0].status = ECA_NORMAL; /* All ok at the moment */ result = ca_array_put_callback ( dbrType, count, pvs[0].chid, pbuf, put_event_handler, (void *) pvs); } else { /* Use standard put with defined timeout */ result = ca_array_put (dbrType, count, pvs[0].chid, pbuf); } result = ca_pend_io(caTimeout); if (result == ECA_TIMEOUT) { fprintf(stderr, "Write operation timed out: Data was not written.\n"); return 1; } if (request == callback) { /* Also wait for callbacks */ waitStatus = epicsEventWaitWithTimeout( epId, caTimeout ); if (waitStatus) fprintf(stderr, "Write callback operation timed out\n"); /* retrieve status from callback */ result = pvs[0].status; } if (result != ECA_NORMAL) { fprintf(stderr, "Error occured writing data.\n"); return 1; } /* Read and print new data */ if (format != terse) printf("New : "); result = caget(pvs, nPvs, format, 0, 0); /* Shut down Channel Access */ ca_context_destroy(); return result; }