|
[Next] [Previous] [Top] [Contents] [Index] Channel Access Client Tutorial 4. Monitoring a PVIn the previous chapter, we used connection handlers to notify the user when a PV was no longer connected and when it had been reconnected. We also used an event handler to inform the user of the success/failure of a put operation.
All such events are executed asynchronously from the main thread of a program. For instance, if a program calls
On multi-threaded systems such as VMS or VxWorks, this is not a problem, since one asynchronous event at a time is allowed to preempt the main thread of the client program, but on single-threaded systems such as UNIX, the client program must explicitly allow an event handler to execute. In our last program, we accomplished this by calling
Another and better way to allow asynchronous events to take place within a single-threaded client program is to use an I/O multiplexor like fdmgr.c which is part of the EPICS distribution. This multiplexor basically allows a single-threaded environment to simulate a multi-threaded environment. Many high-level EPICS client applications that use Channel Access such as the Display Manager and the Archiver use
This chapter examines two types of events: monitor events and access rights events. The first is triggered when a PV's value and/or status changes, depending on the mask used in the call that installs the monitor on the PV, and the second is triggered when the current user's access rights change--for example, when the user no longer has access to write to a channel. While doing so, this chapter will demonstrate how to use the file descriptor manager Monitoring a PV for Changes
ca_add_masked_array_event() or one of its aliases and passing the PV's channel ID to it. The client program must also specify an event handler, which will be called when a monitor event occurs.There are three types of events that a client program can monitor using Channel Access:
As an example of the first type of event, suppose that the PV is the VAL field of an EPICS analog input record and that the MDEL field of that record specifies 2.5. If the current value of the record is 25, a monitor event will occur if the VAL field's value drops below 22.5 or rises above 27.5. The second type of event would work similarly, but instead of the MDEL field, the value of the ADEL field is used as the deadband. An example of the third type of event would occur if the current alarm state of the record changed from LOW to HIGH, i.e., if the STAT field of an analog record changed from LOW to HIGH.
In order for a client program to monitor a PV, it must first establish a connection to it with
int ca_add_masked_array_event ( /* Name IO Value*/ /* ---- -- -----*/ chtype, /* TYPE R requested external channel type */ unsigned long, /* COUNT R array element count */ chid, /* CHID R channel index */ void (*)(struct event_handler_args), /* USRFUNC R the address of a user supplied function */ void *, /* USRARG R An argument copied to the above function */ ca_real, /* P_DELTA R Generate events after +delta excursions */ ca_real, /* N_DELTA R Generate events after -delta excursions */ ca_real, /* TIMEOUT R Generate events after timeout sec */ evid *, /* EVIDPTR W An id to refer to this event by */ long /* MASK R event trigger type */ );Since a monitor event retrieves the value of a PV just like a get operation, many of these arguments work as they do for ca_get_callback() or its aliases. The first argument is simply a constant representing the database request buffer type, such as DBR_ENUM, DBR_FLOAT, or DBR_GR_DOUBLE (See Chapter 2., Reading PV Values, for more information on using database types). The second argument is the number of elements to be read from an array PV. For scalar PVs, it should specify 1. Note that for array PVs, there is no offset, so the first element read is always the first element of the PV's array. The third argument is the PV's identifier, and the fourth is the name of the event handler defined by the client program. The fifth argument, USRARG, is simply an address provided by the client program, which holds an object that the event handler can access.
The next three arguments--P_DELTA, N_DELTA, and TIMEOUT--should be passed to the function as floating point zeros. They have not yet been implemented, so passing another argument will have no effect. As long as your calls to
EVIDPTR should be a pointer-to-type
evid *pEvidThis is an event identifier that identifies the particular monitor on the particular PV. Client programs can use event IDs to later delete the monitor on that PV when it is no longer needed. By passing the event ID to the call ca_clear_event(), the monitor can be deleted and no more monitor events will be triggered for that PV. Using an event ID is optional; the client program can simply pass NULL as this argument; however, the program will then be unable to delete or clear the monitor, and it will remain until the program terminates.The last argument is a bitmask which specifies the type of events which will trigger the monitor. The three types of events are explained above--when the value changes by more than MDEL, when the value changes by more than ADEL, and when the alarm status changes. The bitmask can represent one or more of these event types. The mask can be a logical mask, but it usually consists of one or more of the following constants:
DBE_VALUE | DBE_LOG. ca_add_array_event() and ca_add_event() are simplifications of ca_add_masked_array_event() which the client program can use if it doesn't need all the features of ca_add_masked_array_event().
In the event handler which is called when a specified event occurs--when the value changes by more than the deadband, for instance--the handler can print the new value of the PV, or perform any task the program needs. The example program in this chapter has an event handler that prints the PV's value. Monitoring a PV for Changes in Access Rights
Here, it's only important to know that the current user of the a client application may have read and write access to a PV, read access only, write access only, or neither. It's possible for these access rights to change at run-time while the client application is running. By installing an access rights handler on a PV, the user of the client program can determine what access rights he/she currently has. A change in access rights is considered an event, just like a change in value can be a monitor event. The access rights handler is installed on an individual PV. When the access rights on that PV change, the access rights handler is called. The access rights handler is also called when it is first installed on a PV. It's important to note that if no access rights handler is installed on a PV, the user will have no way of knowing that he/she does/doesn't have access rights. For instance, if suddenly the user loses write access to a PV and no access rights handler has been installed on the PV, the user will have no way of knowing that any put operation will not work, that is, not until after the operation has already failed. This may be acceptable for some applications, while for others it may not be. Therefore, whether a client application should bother with an access rights handler depends on the application.
The call that places an access rights handler on a PV is An Example Program
As stated at the beginning of this chapter, a Channel Access client program that runs in a single-threaded environment can use the EPICS file descriptor manager
Our program uses several calls in fdmgr.h in addition to several Channel Access client calls to monitor a channel for value changes, alarm state changes, and access rights changes. Whenever any of these events occurs, the program will be notified that an asynchronous event has occurred and will call Here is the main part of the program including the preprocessor directives, function prototypes, and global declarations.
#include <stdio.h> #include <cadef.h> #include <fdmgr.h> #include <alarmString.h> /* for severity strings */ #ifndef ca_poll #define ca_poll ca_pend_event(1.0e-9) #endif #define FALSE 0 #define TRUE 1 /* some time values for timeouts */ static struct timeval twenty_seconds = {20, 0}; static struct timeval one_second = { 0,1000000}; chid my_chid; /* channel access id */ fdctx *pfdctx; /* file descriptor manager id */ void arch_init(); void fd_register(void *pfdctx, int fd, int condition); void capollfunc(void *pParam); void caFDCallBack(void *pUser); void caConnectionHandler(struct connection_handler_args arg); void caEventHandler(struct event_handler_args arg); void access_rights_handler(struct access_rights_handler_args arg); void main(int argc, char **argv) { unsigned short new_set; char *name; chtype type; int status; /* initialize channel access */ SEVCHK(ca_task_initialize(), "ca_task_initialize: CA init failure.\n"); /* Set up file descriptor mngr(fdmgr) */ pfdctx = fdmgr_init(); if (!pfdctx) { printf("fdmgr_init failed.\n"); exit(-1); } /* Get CA file descriptor for fdmgr */ SEVCHK(ca_add_fd_registration(fd_register, pfdctx), "fd registration failed.\n"); fdmgr_add_timeout(pfdctx, &one_second, capollfunc, (void *)NULL); new_set = 1; if(argc != 2){ printf("usage: monitor <channel_name>\n"); exit (-1); } name = argv[1]; /* loop indefinitely until user terminates process */ for(;;){ if (new_set){ /* establish connection */ SEVCHK(ca_search(name, &my_chid), NULL); status = ca_pend_io(0.1); if (status == ECA_TIMEOUT){ printf("not connected\n"); exit(-1); } else{ printf("%s: connected\n", ca_name(my_chid)); type = ca_field_type(my_chid); /* install monitor */ SEVCHK(ca_add_array_event(dbf_type_to_DBR_STS(type), ca_element_count(my_chid), my_chid, caEventHandler, NULL, 0.0, 0.0, 0.0, (evid *)NULL), NULL); /* install access rights monitor */ status = ca_replace_access_rights_event(my_chid, access_rights_handler); SEVCHK(status, "ca_replace_access_rights: bad request"); } new_set = 0; } /* wait for an event or 20 second timeout */ fdmgr_pend_event(pfdctx, &twenty_seconds); } }Firstly, in order to use fdmgr, a program must include the fdmgr.h header file (line 4). The other include directives should look familiar by now. The ifndef directive defines a macro called ca_poll(), a convenient macro provided as of release R3.13.0 in cadef.h. ca_poll() is defined as ca_pend_event() with a very brief timeout period.
The program then initializes two
In
After the File Descriptor Manager and Channel Access are initialized, our program calls
void fd_register(void *pfdctx, int fd, int opened)
{
if(opened){
fdmgr_add_callback(pfdctx, fd, fdi_read, caFDCallBack, NULL);
}
else{
fdmgr_clear_callback(pfdctx, fd, fdi_read);
}
return;
}
The first argument to this function is the pointer passed as the second argument in the call to ca_add_fd_registration(), the second argument is an integer that will be used as the file descriptor, and the third argument is an integer which will be non-zero if a file descriptor has been opened or zero if closed. The second and third arguments are given their values by fdmgr. Our simple function checks to see if a new file descriptor has been placed into service or if one has been removed. In the first case, it calls fdmgr_add_callback(), and in the second, fdmgr_clear_callback(). The first call is used to specify a function to be called back whenever the registered file descriptor becomes active--in our case, whenever an event handler is waiting to execute. fdmgr_add_callback() accepts five arguments:
caFDCallBack() is simply a routine we have supplied that calls the macro ca_poll(), which is defined as a call to ca_pend_event() with 1.0e-9 as the timeout value.
void caFDCallBack(void *pUser)
{
ca_poll;
return;
}
Thus, our program is set up to call ca_pend_event(), which allows the execution of any event handlers.
After control passes back to
static void capollfunc(void *pParam)
{
/* pend for handlers, then issues next timeout */
ca_poll;
fdmgr_add_timeout(pfdctx, &fifteen_seconds, capollfunc, (void *)NULL);
return;
}
capollfunc() simply calls ca_poll(), after which it calls fdmgr_add_timeout(), which will sleep for fifteen seconds and call capollfunc(). Thus, our program calls ca_pend_event() with a small timeout value every fifteen seconds.
In order for Channel Access to work efficiently, it must call
After the call to
The call
The first argument passed to
The third argument is simply the channel identifier or chid for the PV. The fourth argument is the name of an event handler defined by the client program. The fifth argument is a pointer to any type that can be accessed by the handler. Our call passes NULL as this argument, which is also acceptable. The next three arguments should be three floating-point zeros, either (float)0 or 0.0. These arguments are for upward compatibility with future changes in the application interface. For now, they should be included in all calls to
The last argument of
The event handler specified by
Our event handler, listed below, is very simple. It prints the value of the channel retrieved from the database, and is thus identical to the event handler in ChapterFigure 2, except that it does not check the return status before it prints the values of the
{
int i;
/* union db_access_val defined in db_access.h */
union db_access_val *pBuf;
/* cast pointer to appropriate type or member of db_access_val */
pBuf = (union db_access_val *)args.dbr;
printf("channel %s: ", ca_name(args.chid));
switch (args.type){
case DBR_STS_STRING:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sstrval.status],
alarmSeverityString[pBuf->sstrval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%s\t", *(&(pBuf->sstrval.value) + i));
if ((i+1)%6 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_SHORT:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sshrtval.status],
alarmSeverityString[pBuf->sshrtval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-10d", *(&(pBuf->sshrtval.value) + i));
if ((i+1)%8 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_FLOAT:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sfltval.status],
alarmStatusString[pBuf->sfltval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("-10.4f", *(&(pBuf->sfltval.value) + i));
if ((i+1)%8 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_ENUM:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->senmval.status],
alarmSeverityString[pBuf->senmval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%d ", *(&(pBuf->senmval.value) + i));
}
printf("\n");
break;
case DBR_STS_CHAR:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->schrval.status],
alarmSeverityString[pBuf->schrval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-5d", *(&(pBuf->schrval.value) + i));
if ((i+1)%15 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_LONG:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->slngval.status],
alarmSeverityString[pBuf->slngval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-15d", *(&(pBuf->slngval.value) + i));
if((i+1)%5 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_DOUBLE:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sdblval.status],
alarmSeverityString[pBuf->sdblval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-15.4f", *(&(pBuf->sdblval.value) + i));
}
printf("\n");
break;
default:
printf("data type was not dbr_sts_nnn.\n");
break;
}
return;
}
After our program installs a monitor and an event handler on the PV, it then installs an access rights event handler on the PV (lines 80-82). It calls ca_replace_access_rights_event() to install the access rights event handler. ca_replace_access_rights_event() accepts the PV chid as its first argument and the name of the event handler as its second.
The access rights event handler--which must be defined by the client program--must take as its only argument the structure
struct access_rights_handler_args{
struct channel_in_use chid; /* Channel id */
caar ar; /* New access rights state*/ };
The second member, ar, is a caar structure which contains two bit fields: read_access and write_access. Each will be 0,1 if the user currently has/doesn't have the access right. For example, if read_access is 1 and write_access is 0, the user can read the PV's value but cannot change it. However, our program's event handler instead uses two macros ca_read_access() and ca_write_access(), which accomplish the same thing; each accepts the channel's identifier as their only argument and returns zero if the current user doesn't have access, and non-zero if the current user does.
void access_rights_handler(struct access_rights_handler_args arg)
{
if(ca_read_access(arg.chid))
printf("Access rights: read access\n");
else
printf("Access rights: no read access\n");
if(ca_write_access(arg.chid))
printf("Access rights: write access\n");
else
printf("Access rights: no write access\n");
return;
}
Our handler merely prints a message, informing the user of his/her access rights. All access rights event handlers are called when they are first installed, in addition to whenever the access rights change. A client program can use the handler to take any necessary action.
Our program then calls
Since our program loops infinitely, the user can only stop it by pressing Control-C or whatever keyboard interrupt will terminate the program on the user's system. That's it--a program that will monitor a PV for alarm state changes, value changes, and access rights changes; and will immediately inform the user of those changes. Remember that in order for Channel Access to function properly, the Channel Access client should poll the server at least every 15 seconds; that is, the longest period a program should go without polling the database is 15 seconds. Shorter time periods may be required for many applications. A poll is best done by calling
|
|
L O S A L A M O S N A T I O N A L
L A B O R A T O R Y Copyright © 1996 UC Disclaimer
|