Files
caClientLessons/caLesson6/caClientWrapperC++/epicsPV.cc
T

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;
}