421 lines
12 KiB
C++
421 lines
12 KiB
C++
#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<epicsPV*>(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<caDatatype>(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<dbr_ctrl_char*>(info)->units;
|
|
case DBF_SHORT:
|
|
return static_cast<dbr_ctrl_short*>(info)->units;
|
|
case DBF_LONG:
|
|
return static_cast<dbr_ctrl_long*>(info)->units;
|
|
case DBF_FLOAT:
|
|
return static_cast<dbr_ctrl_float*>(info)->units;
|
|
case DBF_DOUBLE:
|
|
return static_cast<dbr_ctrl_double*>(info)->units;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
int epicsPV::precision() const
|
|
{
|
|
if (!info) return 0;
|
|
switch (usedDatatype)
|
|
{
|
|
case DBF_FLOAT:
|
|
return static_cast<dbr_ctrl_float*>(info)->precision;
|
|
case DBF_DOUBLE:
|
|
return static_cast<dbr_ctrl_double*>(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<dbr_time_char*>(data)->value)[index];
|
|
case DBF_SHORT:
|
|
return (&static_cast<dbr_time_short*>(data)->value)[index];
|
|
case DBF_LONG:
|
|
return (&static_cast<dbr_time_long*>(data)->value)[index];
|
|
case DBF_FLOAT:
|
|
return (&static_cast<dbr_time_float*>(data)->value)[index];
|
|
case DBF_DOUBLE:
|
|
return (&static_cast<dbr_time_double*>(data)->value)[index];
|
|
case DBF_ENUM:
|
|
return (&static_cast<dbr_time_enum*>(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<dbr_time_char*>(data)->value)[index];
|
|
case DBF_SHORT:
|
|
return (&static_cast<dbr_time_short*>(data)->value)[index];
|
|
case DBF_LONG:
|
|
return (&static_cast<dbr_time_long*>(data)->value)[index];
|
|
case DBF_FLOAT:
|
|
return static_cast<long>((&static_cast<dbr_time_float*>(data)->value)[index]);
|
|
case DBF_DOUBLE:
|
|
return static_cast<long>((&static_cast<dbr_time_double*>(data)->value)[index]);
|
|
case DBF_ENUM:
|
|
return (&static_cast<dbr_time_enum*>(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 "<not connected>";
|
|
if (index > elements()) throw epicsExceptionOutOfBounds();
|
|
switch (usedDatatype)
|
|
{
|
|
case DBF_CHAR:
|
|
ival = (&static_cast<dbr_time_char*>(data)->value)[index];
|
|
units = static_cast<dbr_ctrl_char*>(info)->units;
|
|
goto printint;
|
|
case DBF_SHORT:
|
|
ival = (&static_cast<dbr_time_short*>(data)->value)[index];
|
|
units = static_cast<dbr_ctrl_short*>(info)->units;
|
|
goto printint;
|
|
case DBF_LONG:
|
|
ival = (&static_cast<dbr_time_long*>(data)->value)[index];
|
|
units = static_cast<dbr_ctrl_long*>(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<dbr_time_float*>(data)->value)[index];
|
|
prec = static_cast<dbr_ctrl_float*>(info)->precision;
|
|
units = static_cast<dbr_ctrl_float*>(info)->units;
|
|
goto printdouble;
|
|
case DBF_DOUBLE:
|
|
val = (&static_cast<dbr_time_double*>(data)->value)[index];
|
|
prec = static_cast<dbr_ctrl_double*>(info)->precision;
|
|
units = static_cast<dbr_ctrl_double*>(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<dbr_time_string*>(data)->value)[index];
|
|
case DBF_ENUM:
|
|
ival = (&static_cast<dbr_time_enum*>(data)->value)[index];
|
|
enuminfo = static_cast<dbr_ctrl_enum*>(info);
|
|
if (ival < enuminfo->no_str)
|
|
return enuminfo->strs[ival];
|
|
sprintf(stringrep, "%ld", ival);
|
|
break;
|
|
default:
|
|
return "<not accessible>";
|
|
}
|
|
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;
|
|
}
|
|
|