Copy of old lessons from afs webpage
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user