#include #include #include #define epicsAlarmGLOBAL #include "epicsPV.h" static int contextCreated = 0; static unsigned long pendingConnects = 0; static unsigned long pendingGets = 0; const char* PVdataTypeStrings[9] = { "STRING", "SHORT", "FLOAT", "ENUM", "CHAR", "LONG", "DOUBLE", "NO_ACCESS", "UNDEFINED" }; const char* PVconnectionStateStrings [4] = { "never connected", "disconnected", "connected", "closed" }; struct epicsPV_s { chid channel; void* data; void* info; caDatatype usedDatatype; caDatatype requestedDatatype; int status; char stringrep[40]; PVconnectionCallback connectionCallback; void* userarg; }; epicsPV PVcreateWithTypeAndConnectionCallback(const char* channelName,caDatatype preferredDatatype, PVconnectionCallback cb, void* userarg) { epicsPV pv; if (!contextCreated) { ca_context_create(ca_disable_preemptive_callback); atexit(ca_context_destroy); contextCreated = 1; } pv = malloc(sizeof (struct epicsPV_s)); pv->channel = NULL; pv->data = NULL; pv->info = NULL; pv->usedDatatype = caTypeNative; pv->requestedDatatype = caTypeNative; pv->status = ECA_DISCONN; pv->connectionCallback = cb; pv->userarg = userarg; if (channelName) PVlinkWithType(pv, channelName, preferredDatatype); return pv; } void PVdestroy(epicsPV pv) { if (!pv) return; PVunlink(pv); free(pv); } static void connectCallback(struct connection_handler_args args) { epicsPV pv = ca_puser(args.chid); int up = (args.op == CA_OP_CONN_UP); if (up) { if (pv->usedDatatype == caTypeNative) pendingConnects --; if (pv->requestedDatatype != caTypeNative) pv->usedDatatype = pv->requestedDatatype; else pv->usedDatatype = ca_field_type(pv->channel); pv->data = realloc(pv->data, dbr_size_n(dbf_type_to_DBR_TIME(pv->usedDatatype), ca_element_count(pv->channel))); if (!pv->data) { pv->status = ECA_ALLOCMEM; return; } if (pv->usedDatatype != DBF_STRING) { pv->info = realloc(pv->info, dbr_size_n(dbf_type_to_DBR_CTRL(pv->usedDatatype),1)); if (!pv->info) { pv->status = ECA_ALLOCMEM; return; } pv->status = ca_get(dbf_type_to_DBR_CTRL(pv->usedDatatype), pv->channel, pv->info); } else { free(pv->info); pv->info = NULL; } } else { pv->status = ECA_DISCONN; } if (pv->connectionCallback) { pv->connectionCallback(pv, up, pv->userarg); } } int PVlinkWithType(epicsPV pv, const char* channelName, caDatatype preferredDatatype) { int status; if (!pv) return ECA_INTERNAL; pv->requestedDatatype = preferredDatatype; if (pv->channel) PVunlink(pv); if (!channelName) return ECA_NORMAL; status = ca_create_channel(channelName, connectCallback, pv, CA_PRIORITY_DEFAULT, &pv->channel); if (status == ECA_NORMAL) { pendingConnects ++; } return status; } void PVunlink(epicsPV pv) { if (pv && pv->channel) { if (pv->connectionCallback) { pv->connectionCallback(pv, 0, pv->userarg); } ca_clear_channel(pv->channel); pv->channel = NULL; free(pv->data); pv->data = NULL; free(pv->info); pv->info = NULL; pv->status = ECA_DISCONN; if (pv->usedDatatype != caTypeNative) { pendingConnects --; } pv->usedDatatype = caTypeNative; } } double PVwaitForConnect(epicsPV pv, double timeoutSec) { ca_poll(); double wait = 0.001; while (timeoutSec != 0.0 && (pv ? ca_state(pv->channel) == cs_never_conn : pendingConnects != 0)) { if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec; ca_pend_event(wait); if (timeoutSec > 0.0) timeoutSec -= wait; wait *= 1.2; if (wait > 0.5) wait = 0.5; } return timeoutSec; } const char* PVname(epicsPV pv) { return pv && pv->channel ? ca_name(pv->channel) : ""; } int PVerrorStatus(epicsPV pv) { return pv ? pv->status : ECA_BADCHID; } enum channel_state PVconnectionState(epicsPV pv) { return pv && pv->channel ? ca_state(pv->channel) : cs_never_conn; } caDatatype PVdatatype(epicsPV pv) { return pv ? pv->usedDatatype : caTypeNative; } unsigned long PVelementCount(epicsPV pv) { return pv && pv->channel ? ca_element_count(pv->channel) : 0; } const char* PVunits(epicsPV pv) { if (!pv->info) return ""; switch (pv->usedDatatype) { case DBF_CHAR: return ((struct dbr_ctrl_char*)(pv->info))->units; case DBF_SHORT: return ((struct dbr_ctrl_short*)(pv->info))->units; case DBF_LONG: return ((struct dbr_ctrl_long*)(pv->info))->units; case DBF_FLOAT: return ((struct dbr_ctrl_float*)(pv->info))->units; case DBF_DOUBLE: return ((struct dbr_ctrl_double*)(pv->info))->units; default: return ""; } } int PVprecision(epicsPV pv) { if (!pv->info) return 0; switch (pv->usedDatatype) { case DBF_FLOAT: return ((struct dbr_ctrl_float*)(pv->info))->precision; case DBF_DOUBLE: return ((struct dbr_ctrl_double*)(pv->info))->precision; default: return 0; } } int PVhasReadAccess(epicsPV pv) { return pv && pv->channel ? ca_read_access(pv->channel) : 0; } int PVhasWriteAccess(epicsPV pv) { return pv && pv->channel ? ca_write_access(pv->channel) : 0; } epicsAlarmSeverity PValarmSeverity(epicsPV pv) { if (pv && pv->data) { epicsUInt16 sevr = ((struct dbr_time_string*)(pv->data))->severity; if (sevr <= lastEpicsAlarmSev) return sevr; } return epicsSevInvalid; } epicsAlarmCondition PValarmStatus(epicsPV pv) { if (pv && pv->data) { epicsUInt16 stat = ((struct dbr_time_string*)(pv->data))->status; if (stat <= lastEpicsAlarmCond) return stat; } return epicsAlarmUDF; } const epicsTimeStamp* PVtimestamp(epicsPV pv) { static const epicsTimeStamp zerotime = {0,0}; if (pv && pv->data) { return &((struct dbr_time_string*)(pv->data))->stamp; } return &zerotime; } double PVtoDoubleElement(epicsPV pv, unsigned long index) { if (!pv || !pv->data || index >= ca_element_count(pv->channel)) { return atof("NAN"); } switch (pv->usedDatatype) { case DBF_CHAR: return (&((struct dbr_time_char*)(pv->data))->value)[index]; case DBF_SHORT: return (&((struct dbr_time_short*)(pv->data))->value)[index]; case DBF_LONG: return (&((struct dbr_time_long*)(pv->data))->value)[index]; case DBF_FLOAT: return (&((struct dbr_time_float*)(pv->data))->value)[index]; case DBF_DOUBLE: return (&((struct dbr_time_double*)(pv->data))->value)[index]; case DBF_ENUM: return (&((struct dbr_time_enum*)(pv->data))->value)[index]; case DBF_STRING: return atof((&((struct dbr_time_string*)(pv->data))->value)[index]); default: return atof("NAN"); } } long PVtoLongElement(epicsPV pv, unsigned long index) { if (!pv || !pv->data || index >= ca_element_count(pv->channel)) { return 0; } switch (pv->usedDatatype) { case DBF_CHAR: return (&((struct dbr_time_char*)(pv->data))->value)[index]; case DBF_SHORT: return (&((struct dbr_time_short*)(pv->data))->value)[index]; case DBF_LONG: return (&((struct dbr_time_long*)(pv->data))->value)[index]; case DBF_FLOAT: return (&((struct dbr_time_float*)(pv->data))->value)[index]; case DBF_DOUBLE: return (&((struct dbr_time_double*)(pv->data))->value)[index]; case DBF_ENUM: return (&((struct dbr_time_enum*)(pv->data))->value)[index]; case DBF_STRING: return atoi((&((struct dbr_time_string*)(pv->data))->value)[index]); default: return 0; } } const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index) { int prec; double val; long ival; struct dbr_ctrl_enum* enuminfo; const char* units; if (!pv || !pv->data) return ""; if (index > ca_element_count(pv->channel)) return ""; switch (pv->usedDatatype) { case DBF_CHAR: ival = (&((struct dbr_time_char*)(pv->data))->value)[index]; units = ((struct dbr_ctrl_char*)(pv->info))->units; goto printint; case DBF_SHORT: ival = (&((struct dbr_time_short*)(pv->data))->value)[index]; units = ((struct dbr_ctrl_short*)(pv->info))->units; goto printint; case DBF_LONG: ival = (&((struct dbr_time_long*)(pv->data))->value)[index]; units = ((struct dbr_ctrl_long*)(pv->info))->units; printint: if (flags & PV_WITHUNITS && units[0]) sprintf(pv->stringrep, "%ld %s", ival, units); else sprintf(pv->stringrep, "%ld", ival); break; case DBF_FLOAT: val = (&((struct dbr_time_float*)(pv->data))->value)[index]; prec = ((struct dbr_ctrl_float*)(pv->info))->precision; units = ((struct dbr_ctrl_float*)(pv->info))->units; goto printdouble; case DBF_DOUBLE: val = (&((struct dbr_time_double*)(pv->data))->value)[index]; prec = ((struct dbr_ctrl_double*)(pv->info))->precision; units = ((struct dbr_ctrl_double*)(pv->info))->units; printdouble: if (prec > 17) prec = -17; if (prec < -17) prec = -17; if (flags & PV_WITHUNITS && units[0]) { if (prec >= 0) sprintf(pv->stringrep, "%.*f %s", prec, val, units); else sprintf(pv->stringrep, "%.*g %s", -prec, val, units); } else { if (prec >= 0) sprintf(pv->stringrep, "%.*f", prec, val); else sprintf(pv->stringrep, "%.*g", -prec, val); } break; case DBF_STRING: return (&((struct dbr_time_string*)(pv->data))->value)[index]; case DBF_ENUM: ival = (&((struct dbr_time_enum*)(pv->data))->value)[index]; enuminfo = (struct dbr_ctrl_enum*)(pv->info); if (ival < enuminfo->no_str) return enuminfo->strs[ival]; sprintf(pv->stringrep, "%ld", ival); break; default: return ""; } return pv->stringrep; } static void getCallback(struct event_handler_args args) { epicsPV pv = args.usr; printf("updating %s %ld elements\n", ca_name(pv->channel), args.count); if (pv->status == ECA_IOINPROGRESS) { pendingGets--; } pv->status = args.status; if (args.status != ECA_NORMAL) { /* Something went wrong. */ fprintf(stderr, "getCallback %s: %s\n", ca_name(pv->channel), ca_message(args.status)); return; } if (args.chid != pv->channel) { fprintf(stderr, "INTERNAL ERROR in updateValueCallback %s: got unexpected chid\n", ca_name(pv->channel)); return; } if (args.type != dbf_type_to_DBR_TIME(pv->usedDatatype)) { fprintf(stderr, "INTERNAL ERROR in updateValueCallback %s: got unexpected type\n", ca_name(pv->channel)); return; } memcpy(pv->data, args.dbr, dbr_size_n(args.type, args.count)); } int cagetList(epicsPV pv1, ...) { va_list ap; epicsPV pv; unsigned long faults = 0; double timeoutSec = 2.0; double wait = 0.001; /* Prepate get requests */ va_start(ap, pv1); pv = pv1; while (pv) { if (!pv->channel) { /* not assigned */ pv->status = ECA_BADCHID; faults ++; } else { if (ca_state(pv->channel) == cs_never_conn) { /* try to connect for the first time */ timeoutSec = PVwaitForConnect(pv, timeoutSec); } if (ca_state(pv->channel) == cs_conn) { pv->status = ca_array_get_callback( dbf_type_to_DBR_TIME(pv->usedDatatype), ca_element_count(pv->channel), pv->channel, getCallback, pv); if (pv->status != ECA_NORMAL) { /* io error */ fprintf(stderr, "%s can't do get: %s\n", ca_name(pv->channel), ca_message(pv->status)); faults ++; } else { pv->status = ECA_IOINPROGRESS; pendingGets++; } } else { pv->status = ECA_DISCONN; faults++; } } pv = va_arg(ap, epicsPV); } va_end(ap); /* Wait for values */ timeoutSec = 2.0; ca_poll(); while (timeoutSec != 0.0 && pendingGets != 0) { if (timeoutSec > 0.0 && timeoutSec < wait) wait = timeoutSec; printf("waiting for %g seconds\n", wait); ca_pend_event(wait); if (timeoutSec > 0.0) timeoutSec -= wait; wait *= 1.2; if (wait > 0.5) wait = 0.5; } /* Check results */ va_start(ap, pv1); pv = pv1; while (pv) { if (pv->status == ECA_IOINPROGRESS) { pv->status = ECA_TIMEOUT; faults++; pendingGets--; } pv = va_arg(ap, epicsPV); } va_end(ap); printf ("pendingGets = %ld, faults = %ld\n", pendingGets, faults); return faults; } /* epicsPV& epicsPV::put(const void* value, unsigned long elements, caDatatype datatype, double timeoutSec) { if (!channel) throw runtime_error("epicsPV::put: PV not linked to a channel"); switch (connectionState()) { case cs_conn: break; case cs_never_conn: timeoutSec = waitForConnect(timeoutSec, this); if (connectionState() == cs_conn) break; default: throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" "+connectionStateStr()); } if (!hasWriteAccess()) throw runtime_error(string("epicsPV::put: ")+ca_name(channel)+" not writable"); cout << "putting " << name() << ", elements: " << elements << ", datatype: " << datatype << " "; switch (datatype) { case caTypeDouble: cout << *(const double*)value; break; case caTypeLong: cout << *(const long*)value; break; case caTypeString: cout << (const char*)value; break; default:; } cout << endl; SEVCHK(ca_array_put(datatype, elements, channel, value), "epicsPV::put"); ca_pend_io(timeoutSec); return *this; } */