573 lines
15 KiB
C
573 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;
|
|
static unsigned long pendingPuts = 0;
|
|
epicsTimeStamp PVinvalidTimestamp = {0,0};
|
|
|
|
const char* PVdataTypeStrings[9] = {
|
|
"STRING",
|
|
"SHORT",
|
|
"FLOAT",
|
|
"ENUM",
|
|
"CHAR",
|
|
"LONG",
|
|
"DOUBLE",
|
|
"NO_ACCESS",
|
|
"UNDEFINED"
|
|
};
|
|
|
|
const char* PVconnectionStateStrings [4] = {
|
|
"never connected",
|
|
"disconnected",
|
|
"connected",
|
|
"closed"
|
|
};
|
|
|
|
epicsPV PVcreateWithTypeAndCallback(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->units = "";
|
|
pv->precision = 0;
|
|
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;
|
|
}
|
|
pv->units = "";
|
|
pv->precision = 0;
|
|
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);
|
|
switch (pv->usedDatatype)
|
|
{
|
|
case DBF_CHAR:
|
|
pv->units = pv->info_CHAR->units;
|
|
break;
|
|
case DBF_SHORT:
|
|
pv->units = pv->info_SHORT->units;
|
|
break;
|
|
case DBF_LONG:
|
|
pv->units = pv->info_LONG->units;
|
|
break;
|
|
case DBF_FLOAT:
|
|
pv->units = pv->info_FLOAT->units;
|
|
pv->precision = pv->info_FLOAT->precision;
|
|
break;
|
|
case DBF_DOUBLE:
|
|
pv->units = pv->info_DOUBLE->units;
|
|
pv->precision = pv->info_DOUBLE->precision;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
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->units = "";
|
|
pv->precision = 0;
|
|
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;
|
|
}
|
|
|
|
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 (&pv->CHAR->value)[index];
|
|
case DBF_SHORT:
|
|
return (&pv->SHORT->value)[index];
|
|
case DBF_LONG:
|
|
return (&pv->LONG->value)[index];
|
|
case DBF_FLOAT:
|
|
return (&pv->FLOAT->value)[index];
|
|
case DBF_DOUBLE:
|
|
return (&pv->DOUBLE->value)[index];
|
|
case DBF_ENUM:
|
|
return (&pv->ENUM->value)[index];
|
|
case DBF_STRING:
|
|
return atof((&pv->STRING->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 (&pv->CHAR->value)[index];
|
|
case DBF_SHORT:
|
|
return (&pv->SHORT->value)[index];
|
|
case DBF_LONG:
|
|
return (&pv->LONG->value)[index];
|
|
case DBF_FLOAT:
|
|
return (&pv->FLOAT->value)[index];
|
|
case DBF_DOUBLE:
|
|
return (&pv->DOUBLE->value)[index];
|
|
case DBF_ENUM:
|
|
return (&pv->ENUM->value)[index];
|
|
case DBF_STRING:
|
|
return atoi((&pv->STRING->value)[index]);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const char* PVtoStringElement(epicsPV pv, int flags, unsigned long index)
|
|
{
|
|
double val;
|
|
long ival;
|
|
int prec;
|
|
|
|
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 = (&pv->CHAR->value)[index];
|
|
goto printint;
|
|
case DBF_SHORT:
|
|
ival = (&pv->SHORT->value)[index];
|
|
goto printint;
|
|
case DBF_LONG:
|
|
ival = (&pv->LONG->value)[index];
|
|
printint:
|
|
if (flags & PV_WITHUNITS && pv->units[0])
|
|
sprintf(pv->stringrep, "%ld %s", ival, pv->units);
|
|
else
|
|
sprintf(pv->stringrep, "%ld", ival);
|
|
break;
|
|
case DBF_FLOAT:
|
|
val = (&pv->FLOAT->value)[index];
|
|
goto printdouble;
|
|
case DBF_DOUBLE:
|
|
val = (&pv->DOUBLE->value)[index];
|
|
printdouble:
|
|
prec = pv->precision;
|
|
if (prec > 17) prec = -17;
|
|
if (prec < -17) prec = -17;
|
|
if (flags & PV_WITHUNITS && pv->units[0])
|
|
{
|
|
if (prec >= 0)
|
|
sprintf(pv->stringrep, "%.*f %s", prec, val, pv->units);
|
|
else
|
|
sprintf(pv->stringrep, "%.*g %s", -prec, val, pv->units);
|
|
}
|
|
else
|
|
{
|
|
if (prec >= 0)
|
|
sprintf(pv->stringrep, "%.*f", prec, val);
|
|
else
|
|
sprintf(pv->stringrep, "%.*g", -prec, val);
|
|
}
|
|
break;
|
|
case DBF_ENUM:
|
|
ival = (&pv->ENUM->value)[index];
|
|
if (ival < pv->info_ENUM->no_str)
|
|
return pv->info_ENUM->strs[ival];
|
|
sprintf(pv->stringrep, "%ld", ival);
|
|
break;
|
|
case DBF_STRING:
|
|
return (&pv->STRING->value)[index];
|
|
default:
|
|
return "<not accessible>";
|
|
}
|
|
return pv->stringrep;
|
|
}
|
|
|
|
static void getCallback(struct event_handler_args args)
|
|
{
|
|
epicsPV pv = args.usr;
|
|
|
|
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;
|
|
}
|
|
memcpy(pv->data, args.dbr, dbr_size_n(args.type, args.count));
|
|
if (pv->STRING->severity > lastEpicsAlarmSev)
|
|
{
|
|
pv->STRING->severity = epicsSevNone;
|
|
}
|
|
if (pv->STRING->status > lastEpicsAlarmCond)
|
|
{
|
|
pv->STRING->status = epicsAlarmNone;
|
|
}
|
|
}
|
|
|
|
int cagetList(double timeoutSec, epicsPV pv1, ...)
|
|
{
|
|
va_list ap;
|
|
epicsPV pv;
|
|
unsigned long faults = 0;
|
|
double wait = 0.001;
|
|
double connectTimeout = 2.0;
|
|
|
|
/* 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 */
|
|
connectTimeout = PVwaitForConnect(pv, connectTimeout);
|
|
}
|
|
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 */
|
|
ca_poll();
|
|
while (timeoutSec != 0.0 && pendingGets != 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;
|
|
}
|
|
|
|
/* 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);
|
|
return faults;
|
|
}
|
|
|
|
static void putCallback(struct event_handler_args args)
|
|
{
|
|
epicsPV pv = args.usr;
|
|
|
|
printf("completing caput %s\n", ca_name(pv->channel));
|
|
if (pv->status == ECA_IOINPROGRESS)
|
|
{
|
|
pendingPuts--;
|
|
}
|
|
pv->status = args.status;
|
|
if (args.status != ECA_NORMAL)
|
|
{
|
|
/* Something went wrong. */
|
|
fprintf(stderr, "putCallback %s: %s\n",
|
|
ca_name(pv->channel), ca_message(args.status));
|
|
return;
|
|
}
|
|
}
|
|
|
|
int caputDoubleList(double timeoutSec, int flags, epicsPV pv1, double value1, ...)
|
|
{
|
|
va_list ap;
|
|
epicsPV pv;
|
|
double value;
|
|
unsigned long faults = 0;
|
|
double wait = 0.001;
|
|
double connectTimeout = 2.0;
|
|
|
|
/* Prepate put requests */
|
|
va_start(ap, value1);
|
|
pv = pv1;
|
|
value = value1;
|
|
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 */
|
|
connectTimeout = PVwaitForConnect(pv, connectTimeout);
|
|
}
|
|
if (ca_state(pv->channel) == cs_conn)
|
|
{
|
|
pv->status = ca_put_callback(
|
|
DBR_DOUBLE,
|
|
pv->channel,
|
|
&value,
|
|
putCallback, pv);
|
|
if (pv->status != ECA_NORMAL)
|
|
{
|
|
/* io error */
|
|
fprintf(stderr, "%s can't do put: %s\n",
|
|
ca_name(pv->channel), ca_message(pv->status));
|
|
faults ++;
|
|
}
|
|
else
|
|
{
|
|
pv->status = ECA_IOINPROGRESS;
|
|
pendingPuts++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pv->status = ECA_DISCONN;
|
|
faults++;
|
|
}
|
|
}
|
|
pv = va_arg(ap, epicsPV);
|
|
value = va_arg(ap, double);
|
|
}
|
|
va_end(ap);
|
|
|
|
if (timeoutSec == 0) return faults;
|
|
|
|
/* Wait for acknowledge */
|
|
ca_poll();
|
|
while (timeoutSec != 0.0 && pendingPuts != 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;
|
|
}
|
|
|
|
/* Check results */
|
|
if (!pendingPuts) return faults;
|
|
va_start(ap, value1);
|
|
pv = pv1;
|
|
while (pv)
|
|
{
|
|
if (pv->status == ECA_IOINPROGRESS)
|
|
{
|
|
pv->status = ECA_TIMEOUT;
|
|
faults++;
|
|
pendingPuts--;
|
|
}
|
|
pv = va_arg(ap, epicsPV);
|
|
va_arg(ap, double);
|
|
}
|
|
va_end(ap);
|
|
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;
|
|
}
|
|
*/
|