412 lines
13 KiB
C
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;
|
|
}
|