Files
caClientLessons/caLesson6/caClientWrapperC/caLesson6.c
T

412 lines
13 KiB
C

/* caLesson6.c
by Dirk Zimoch, 2007
*/
#include <stdio.h>
#include <string.h>
/* include EPICS headers */
#include <cadef.h>
#define epicsAlarmGLOBAL
#include <alarm.h>
#include <epicsEvent.h>
#include <epicsMutex.h>
/* Strings describing the connection status of a channel.
See also enum channel_state in /usr/local/epics/base/include/cadef.h
*/
const char *channel_state_str[4] = {
"not found",
"connection lost",
"connected",
"closed"
};
/* Define a generic process variable (PV).
Each PV contains static information in info and
current value in data.
See /usr/local/epics/base/include/db_access.h for fields
of dbr_* structures.
*/
typedef struct epicsPV epicsPV;
typedef struct monitorCallback monitorCallback;
struct monitorCallback {
monitorCallback *next;
void *userdata;
void (*callbackfunction) (void *userdata, epicsPV* pv);
};
struct epicsPV {
chid channel;
int datatype;
epicsEventId monitorEvent;
epicsMutexId accessMutex;
monitorCallback* callbacks;
union {
struct dbr_sts_string string_data;
struct {
struct dbr_ctrl_long long_info;
struct dbr_sts_long long_data;
};
struct {
struct dbr_ctrl_double double_info;
struct dbr_sts_double double_data;
};
struct {
struct dbr_ctrl_enum enum_info;
struct dbr_sts_enum enum_data;
};
};
};
#define connectPVs(...) connectPVsFileLine(__FILE__,__LINE__,__VA_ARGS__, NULL)
int connectPVsFileLine(const char* file, int line, const char* name, ...)
{
va_list va;
epicsPV pv;
if (!ca_current_context) ca_context_create(ca_enable_preemptive_callback);
va_start(va, name);
while (name)
{
if (findPV(name))
{
fprintf(stderr, "connectPVs in %s line %d: channel \"%s\" already connected\n",
file, line, name);
continue;
}
pv = malloc(sizeof(epicsPV));
if (!pv)
{
fprintf(stderr, "connectPVs in %s line %d: out of memory\n",
file, line, name);
return ECA_ALLOCMEM;
}
ca_create_channel(name, connectCallback, pv,
CA_PRIORITY_DEFAULT, &pv->channel);
name = va_arg(va, char*)
}
va_end(va);
}
/* Print NAME = VALUE for PV of any type.
Precision and units are taken from info.
*/
void printPV(const epicsPV* pv)
{
int status, severity;
epicsMutexLock(pv->accessMutex);
if (ca_state(pv->channel) != cs_conn)
{
printf("%s <%s>\n",
ca_name(pv->channel),
channel_state_str[ca_state(pv->channel)]);
epicsMutexUnlock(pv->accessMutex);
return;
}
switch (pv->datatype)
{
case DBF_STRING:
printf("%s = %s",
ca_name(pv->channel),
pv->string_data.value);
status = pv->string_data.status;
severity = pv->string_data.severity;
break;
case DBF_LONG:
printf("%s = %.ld %s",
ca_name(pv->channel),
pv->long_data.value, pv->long_info.units);
status = pv->long_data.status;
severity = pv->long_data.severity;
break;
case DBF_DOUBLE:
printf("%s = %.*f %s",
ca_name(pv->channel),
pv->double_info.precision, pv->double_data.value, pv->double_info.units);
status = pv->double_data.status;
severity = pv->double_data.severity;
break;
case DBF_ENUM:
if (pv->enum_data.value < pv->enum_info.no_str)
printf("%s = %s",
ca_name(pv->channel), pv->info.strs[pv->enum_data.value]);
else
printf("%s = %d",
ca_name(pv->channel), pv->enum_data.value);
status = pv->enum_data.status;
severity = pv->enum_data.severity;
break;
default:
printf ("%s <unsupported data type>\n",
ca_name(pv->channel));
epicsMutexUnlock(accessMutex);
return;
}
epicsMutexUnlock(accessMutex);
if (severity != NO_ALARM)
{
printf(" <%s %s>",
epicsAlarmSeverityStrings[severity],
epicsAlarmConditionStrings[status]);
}
else
{
printf("\n");
}
}
int caget(epicsPV* pv)
{
int dbr_type;
if (ca_state((pv)->channel) != cs_conn)
{
printf ("caget %s: %s\n", PV_name(pv),
channel_state_str[PV_state(pv)]);
return ECA_DISCONN;
}
/* Allocate memory only once but read dynamic data every time */
dbr_type = dbf_type_to_DBR_STS(PV_type(pv));
printf ("caget %s: requesting dynamic data\n", PV_name(pv));
return ca_get(dbf_type_to_DBR_STS(pv->datatype);, pv->channel, pv->data);
}
/* Generic monitor event handler.
See /usr/local/epics/base/include/cadef.h for the definition of
struct event_handler_args.
This handler copies the new value into the PV and writes a message.
We get the address of the PV in the 'usr' field of 'args'
because we give that as the 4th argument to ca_add_event (see below).
In the multi threaded model, the monitor callback runs in a separate
thread. That means, the monitor function may be called at any time,
even while we are just accessing the PV from the main program. Thus,
we must protect all accesses to a PV with a mutext. Here, we use
just one global mutex. The next lesson will introduce a more
sophisticated solution.
*/
static void monitor(struct event_handler_args args)
{
epicsPV* pv = args.usr;
if (args.status != ECA_NORMAL)
{
/* Something went wrong. */
SEVCHK(args.status, "monitor");
return;
}
/* Lock PV to be thread safe */
epicsMutexLock(pv->accessMutex);
/* Copy the value to the 'data' field of the PV.
The current data, its type and the number of elements (for arrays) is
stored in several fields of 'args'.
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
memcpy(&pv->double_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
case DBR_STS_ENUM:
{
memcpy(&pv->enum_data, args.dbr, dbr_size_n(args.type, args.count));
break;
}
default:
{
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
epicsMutexUnlock(accessMutex);
/* Inform other threads about monitor */
epicsEventSignal(monitorEvent);
}
int main(int argc, char** args)
{
char recordname[28];
double search_timeout = 5.0; /* seconds */
double put_timeout = 1.0; /* seconds */
double get_timeout = 1.0; /* seconds */
int status;
CA_SYNC_GID gid; /* required for blocking put (see below) */
epicsDoublePV setvalue;
epicsDoublePV readvalue;
epicsEnumPV doneflag;
if (argc != 2)
{
fprintf(stderr, "usage: %s <prefix>\n"
"Where <prefix> is a prefix to :SET, :READ and :DONE.\n",
args[0]);
return 1;
}
/* Step1: Initialize channel access and search for all channels. */
/* Start EPICS multi-threaded */
ca_context_create(ca_enable_preemptive_callback);
sprintf(recordname, "%.19s:SET", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &setvalue.channel);
sprintf(recordname, "%.19s:READ", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &readvalue.channel);
sprintf(recordname, "%.19s:DONE", args[1]);
ca_create_channel(recordname, NULL, NULL, CA_PRIORITY_DEFAULT, &doneflag.channel);
SEVCHK(status = ca_pend_io(search_timeout), "searching channels");
if (status != ECA_NORMAL) goto end;
/* Setup an event for monitors */
monitorEvent = epicsEventCreate(epicsEventEmpty);
/* Setup a mutex semaphore to make PV access thread-safe */
accessMutex = epicsMutexCreate();
/* Step 2: Get available infos and setup monitor for DONE flag*/
ca_get(DBR_CTRL_DOUBLE, setvalue.channel, &setvalue.info);
ca_get(DBR_CTRL_DOUBLE, readvalue.channel, &readvalue.info);
ca_get(DBR_CTRL_ENUM, doneflag.channel, &doneflag.info);
ca_create_subscription(DBR_STS_DOUBLE, 1, readvalue.channel,
DBE_VALUE|DBE_ALARM, monitor, &readvalue, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, doneflag.channel,
DBE_VALUE|DBE_ALARM, monitor, &doneflag, NULL);
SEVCHK(status = ca_pend_io(search_timeout), "initializing channels");
if (status != ECA_NORMAL) goto end;
/* Create the "synchronous group id" (gid) used later for put. */
SEVCHK(status = ca_sg_create(&gid), "creating synchronous group");
if (status != ECA_NORMAL) goto end;
/* Step 3: Enter main loop */
while (1)
{
char userinput[40];
double newvalue;
/* Get current setting */
ca_get(DBR_STS_DOUBLE, setvalue.channel, &setvalue.data);
SEVCHK(ca_pend_io(search_timeout), ca_name(setvalue.channel));
printDoublePV(&setvalue);
/* Ask for new setting */
if (setvalue.info.lower_ctrl_limit < setvalue.info.upper_ctrl_limit)
{
printf("Enter new value (range %.*f~%.*f): ",
setvalue.info.precision, setvalue.info.lower_ctrl_limit,
setvalue.info.precision, setvalue.info.upper_ctrl_limit);
}
else
{
/* No limits known */
printf("Enter new value: ");
}
fflush(stdout);
fgets(userinput, sizeof(userinput), stdin);
if (sscanf(userinput, "%lf", &newvalue) != 1)
{
printf("Invalid input \"%s\". Need a number.\n", userinput);
continue;
}
/* Set new value and wait to complete.
This is a very important timing issue!
The records are build in a way that the DONE record
is set to "ACTIVE" before a put to the SET record completes.
Insider info: They are linked via FLNK and PP output links.
Timing:
ca_put (0)-(1)---(2)----(3)
:
ca_sg_put (0)-------(2)----(3)
:
"DONE" =====*=====* *========
| |
"ACTIVE" *=======*
Some time after the put (0), the device becomes active.
A normal put may return before (1), while (2), or after (3)
activity. A following reading of the DONE record cannot
distinguish between (1) and (3).
However, a synchonous put will not return before (2).
Thus, we can wait until the DONE record becomes "DONE".
If it returns late after activity finished (3), the DONE record
is already "DONE" and we don't need to wait. But we can never be
in the situation that the DONE record is not yet "ACTIVE" (1).
To implement a synchronous put, we use the "synchronous group"
mechanism. ca_sg_block does not return until all outstanding
ca_sg_* requests with the same gid have completed (or timeout).
Note that we put a bare double here, no DBR_CTRL_*.
*/
ca_sg_put(gid, DBR_DOUBLE, setvalue.channel, &newvalue);
SEVCHK(ca_sg_block(gid, put_timeout), ca_name(setvalue.channel));
/* Wait until activity is done.
This uses the monitor on the DONE record.
Remember that monitors are handled in the background, but only
while certain ca functions are active, e.g. ca_pend_event(),
ca_pend_io(), or ca_sg_block().
*/
/* First actively read current value of DONE record. */
ca_get(DBR_STS_ENUM, doneflag.channel, &doneflag.data);
SEVCHK(status = ca_pend_io(get_timeout), ca_name(doneflag.channel));
printEnumPV(&doneflag);
/* When not already done, wait for monitor events */
while (!doneflag.data.value)
{
/* wait for the next monitor event */
epicsEventWait(monitorEvent);
/* This really does nothing until the mext monitor comes.
Then, monitorEvent is triggered from the monitor callback
function (see above).
*/
}
printEnumPV(&doneflag);
}
/* Step 4: clean up */
ca_sg_delete(gid);
end:
ca_context_destroy();
epicsMutexDestroy(accessMutex);
epicsEventDestroy(monitorEvent);
return 0;
}