Files
caClientLessons/caLesson6/caClientWrapperC/epicsPV2.c
T

561 lines
15 KiB
C

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#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 "<not connected>";
if (index > ca_element_count(pv->channel)) return "<out of bounds>";
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 "<not accessible>";
}
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;
}
*/