#define epicsAlarmGLOBAL #include "epicsPV.h" using namespace std; class caContext { public: caContext(ca_preemptive_callback_select select=ca_disable_preemptive_callback) { ca_context_create(select); } ~caContext() { ca_context_destroy(); } }; static caContext globalContext; unsigned long epicsPV::nconnecting = 0; const char* epicsPV::dataTypeStrings[9] = { "STRING", "SHORT", "FLOAT", "ENUM", "CHAR", "LONG", "DOUBLE", "NO_ACCESS", "UNDEFINED" }; const char* epicsPV::connectionStateStrings [4] = { "never connected", "disconnected", "connected", "closed" }; epicsPV::epicsPV(const char* channelName, caDatatype preferredDatatype) { channel = NULL; data = NULL; info = NULL; usedDatatype = caTypeNative; requestedDatatype = caTypeNative; if (channelName) link(channelName, preferredDatatype); } epicsPV::~epicsPV() { unlink(); } void epicsPV::link(const char* channelName, caDatatype preferredDatatype) { int status; requestedDatatype = preferredDatatype; if (channel) unlink(); if (!channelName) return; status = ca_create_channel(channelName, epicsPV::connectCallback, this, CA_PRIORITY_DEFAULT, &channel); switch (status) { case ECA_NORMAL: nconnecting ++; return; case ECA_BADTYPE: throw invalid_argument("epicsPV::link: bad datatype"); case ECA_STRTOBIG: throw invalid_argument("epicsPV::link: channel name too long"); case ECA_ALLOCMEM: throw epicsExceptionOutOfMemory(); default: throw runtime_error("epicsPV::link: unknown error"); } } void epicsPV::unlink() { if (channel) { connectionDownEvent(); ca_clear_channel(channel); channel = NULL; free(data); data = NULL; free(info); info = NULL; if (usedDatatype != caTypeNative) { nconnecting --; } usedDatatype = caTypeNative; } } double epicsPV::waitForConnect(double timeoutSec, epicsPV* pv) { ca_poll(); double wait = 0.001; while ((timeoutSec != 0.0) && (pv ? pv->connectionState() == cs_never_conn : nconnecting != 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; } void epicsPV::connectCallback(connection_handler_args args) { epicsPV* pv = static_cast(ca_puser(args.chid)); if (args.op == CA_OP_CONN_UP) pv->connectionUp(); else pv->connectionDown(); } void epicsPV::connectionUp() { if (usedDatatype == caTypeNative) nconnecting --; if (requestedDatatype != caTypeNative) usedDatatype = requestedDatatype; else usedDatatype = static_cast(ca_field_type(channel)); data = realloc(data, dbr_size_n(dbf_type_to_DBR_TIME(usedDatatype),ca_element_count(channel))); if (!data) throw epicsExceptionOutOfMemory(); if (usedDatatype != DBF_STRING) { info = realloc(info, dbr_size_n(dbf_type_to_DBR_CTRL(usedDatatype),1)); if (!info) throw epicsExceptionOutOfMemory(); SEVCHK(ca_get(dbf_type_to_DBR_CTRL(usedDatatype), channel, info), "epicsPV::connectionUp"); } else { free(info); info = NULL; } connectionUpEvent(); } void epicsPV::connectionDown() { connectionDownEvent(); } void epicsPV::connectionUpEvent() { // User can overwrite this function } void epicsPV::connectionDownEvent() { // User can overwrite this function } const char* epicsPV::units() const { if (!info) return ""; switch (usedDatatype) { case DBF_CHAR: return static_cast(info)->units; case DBF_SHORT: return static_cast(info)->units; case DBF_LONG: return static_cast(info)->units; case DBF_FLOAT: return static_cast(info)->units; case DBF_DOUBLE: return static_cast(info)->units; default: return ""; } } int epicsPV::precision() const { if (!info) return 0; switch (usedDatatype) { case DBF_FLOAT: return static_cast(info)->precision; case DBF_DOUBLE: return static_cast(info)->precision; default: return 0; } } double epicsPV::toDouble (unsigned long index) const { if (!data) throw epicsExceptionNoData(); if (index >= elements()) throw epicsExceptionOutOfBounds(); switch (usedDatatype) { case DBF_CHAR: return (&static_cast(data)->value)[index]; case DBF_SHORT: return (&static_cast(data)->value)[index]; case DBF_LONG: return (&static_cast(data)->value)[index]; case DBF_FLOAT: return (&static_cast(data)->value)[index]; case DBF_DOUBLE: return (&static_cast(data)->value)[index]; case DBF_ENUM: return (&static_cast(data)->value)[index]; default: throw epicsExceptionInvalidConversion(); } } long epicsPV::toLong (unsigned long index) const { if (!data) throw epicsExceptionNoData(); if (index > elements()) throw epicsExceptionOutOfBounds(); switch (usedDatatype) { case DBF_CHAR: return (&static_cast(data)->value)[index]; case DBF_SHORT: return (&static_cast(data)->value)[index]; case DBF_LONG: return (&static_cast(data)->value)[index]; case DBF_FLOAT: return static_cast((&static_cast(data)->value)[index]); case DBF_DOUBLE: return static_cast((&static_cast(data)->value)[index]); case DBF_ENUM: return (&static_cast(data)->value)[index]; default: throw epicsExceptionInvalidConversion(); } } const char* epicsPV::toStr(int flags, unsigned long index) { int prec; double val; long ival; dbr_ctrl_enum* enuminfo; const char* units; if (!data) return ""; if (index > elements()) throw epicsExceptionOutOfBounds(); switch (usedDatatype) { case DBF_CHAR: ival = (&static_cast(data)->value)[index]; units = static_cast(info)->units; goto printint; case DBF_SHORT: ival = (&static_cast(data)->value)[index]; units = static_cast(info)->units; goto printint; case DBF_LONG: ival = (&static_cast(data)->value)[index]; units = static_cast(info)->units; printint: if (flags & withUnits && units[0]) sprintf(stringrep, "%ld %s", ival, units); else sprintf(stringrep, "%ld", ival); break; case DBF_FLOAT: val = (&static_cast(data)->value)[index]; prec = static_cast(info)->precision; units = static_cast(info)->units; goto printdouble; case DBF_DOUBLE: val = (&static_cast(data)->value)[index]; prec = static_cast(info)->precision; units = static_cast(info)->units; printdouble: if (prec > 17) prec = -17; if (prec < -17) prec = -17; if (flags & withUnits && units[0]) { if (prec >= 0) sprintf(stringrep, "%.*f %s", prec, val, units); else sprintf(stringrep, "%.*g %s", -prec, val, units); } else { if (prec >= 0) sprintf(stringrep, "%.*f", prec, val); else sprintf(stringrep, "%.*g", -prec, val); } break; case DBF_STRING: return (&static_cast(data)->value)[index]; case DBF_ENUM: ival = (&static_cast(data)->value)[index]; enuminfo = static_cast(info); if (ival < enuminfo->no_str) return enuminfo->strs[ival]; sprintf(stringrep, "%ld", ival); break; default: return ""; } return stringrep; } int epicsPV::getPVs(double timeoutSec, epicsPV* pv1, ...) { va_list ap; epicsPV* pv; int faults = 0; int status; va_start(ap, pv1); pv = pv1; while (pv) { if (!pv->channel) { // not assigned pv->status = ECA_BADCHID; faults ++; } else { if (pv->connectionState() == cs_never_conn) { // try to connect for the first time timeoutSec = waitForConnect(timeoutSec, pv); } SEVCHK(pv->status = ca_array_get(dbf_type_to_DBR_TIME(pv->usedDatatype), pv->elements(), pv->channel, pv->data), "caget"); if (pv->status) { // io error faults ++; } } pv = va_arg(ap, epicsPV*); } va_end(ap); SEVCHK(status = ca_pend_io(timeoutSec), "caget"); if (status) { // io timeout faults ++; } return faults; } epicsPV& epicsPV::get(double timeoutSec) { if (!channel) throw runtime_error("epicsPV::get: 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::get: ")+ca_name(channel)+" "+connectionStateStr()); } SEVCHK(ca_array_get(dbf_type_to_DBR_TIME(usedDatatype), ca_element_count(channel), channel, data), "epicsPV::get"); ca_pend_io(timeoutSec); return *this; } 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; }