[Next] [Previous] [Top] [Contents] [Index]

Channel Access Client Tutorial

4. Monitoring a PV

In 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 ca_put_callback(), the program specifies an event handler in the call. When the put operation completes, the server causes the specified event handler to execute in the client. However, the client program does not know exactly when the event handler will run; thus, it must allow for it to execute at an undetermined point in the program. Usually for ca_put_callback() and ca_get_callback() a program can be sure that the operation will complete and the handlers called in a brief period of time, less than a tenth of a second from the time when the requests are sent to the server. Nevertheless, this is not always true. In addition, a program can never predict when a channel will become disconnected, and thus when its event handler will be run.

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 ca_pend_event() twice inside of a for loop. Once at the beginning of the for loop and once after each call to ca_put_callback(). This algorithm allows the connection handler and event handler to execute, yet it is rather clumsy. For example, if the user doesn't enter a PV name, the program will wait and wait until the user does enter a PV name. Thus, it is entirely possible for the channel to be broken during this wait and for the user to be unaware of the fact that it's disconnected. Quite simply, the program depends on the user to input PV names periodically in order to keep up with any asynchronous events and take care of any Channel Access background labor.

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 fdmgr to allow asynchronous events to execute.

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 fdmgr with Channel Access.

Monitoring a PV for Changes

The basic operations in Channel Access are reading a channel's value, changing a channel's value, and monitoring a process variable--reading, writing, and monitoring. Monitoring a PV means to keep track of a PV's value. However, the client program does not actively monitor the Channel itself. Rather, when a client program wants to monitor a PV, it installs a monitor on the PV by issuing a call to 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:

  1. When a PV's value changes by more than its monitor deadband; for the VAL field of many EPICS analog PVs, this deadband is specified in the record's MDEL field.

  2. When a PV's value changes by more than its archiver deadband; for the VAL field of many EPICS analog PV, this deadband is specified in the record's ADEL field.

  3. When the alarm status of the PV changes; for an EPICS PV, this occurs when the STAT field changes.

A client program can also monitor any combination of these three types of events. For EPICS binary PVs, the first two types of monitors are triggered when the state of the PV changes.

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 ca_search() or ca_search_and_connect(), after which it must issue a call to ca_add_masked_array_event(), ca_add_array_event(), or ca_add_event(). ca_add_masked_array_event() is the basic call, of which ca_add_array_event() and ca_add_event() are just aliases. Here is its basic interface:

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 ca_add_masked_array_event() specify floating-point zeros for these arguments, your code will be upward compatible. These arguments do not have to be specified in a call to ca_add_event(), but they do in ca_add_array_event().

EVIDPTR should be a pointer-to-type evid.

evid  *pEvid
This 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--when the channel's value changes by more than MDEL.

DBE_LOG--when the channel's value changes by more than ADEL.

DBE_ALARM--when the channel's alarm state changes.

The last argument must specify at least one of these constants. More than one can be specified by ORing two or three constants, as in

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().

ca_add_array_event() has the same arguments as ca_add_masked_array_event() except for the last argument, the mask. This call automatically specifies DBE_VALUE and DBE_ALARM as the mask.

ca_add_event() can be used for scalar PVs that don't need to specify the number of elements, that is, whose element count is always one. This call does not have the arguments P_DELTA, N_DELTA, and TIMEOUT which appear in the interface to ca_add_array_event() and 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

A client program can also be informed when the access rights to a PV have changed for the current user of the client program. Basically, there are two kinds of access rights to a PV: read access and write access. How exactly the user's access rights are determined depends on the software to which the server is attached. For instance, if the server is attached to an EPICS database, who gets access rights to a certain PV and when they have access rights to that PV depends on how the database is configured. Whatever the case, how exactly access rights are determined on the server side, is beyond the scope of this book.

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 ca_replace_access_rights_event(). It is simple to use because it only has two arguments: a channel identifier and the name of the event handler. Typically, an access rights event handler will want to inform the current user what access rights he or she now has or doesn't have. The example program in this chapter has an access rights handler. When we discuss that handler, we'll discuss the structure access_rights_handler_args structure which is automatically passed to the handler.

An Example Program

Let's examine a sample program that will demonstrate Channel Access monitor events, access rights events, and how to use the EPICS file descriptor manager.

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 fdmgr to run efficiently because this provides a way for the client process to allow asynchronous events to occur. On VxWorks and VMS, Channel Access event handlers preempt the main thread of execution, one handler at a time.

fdmgr is an I/O multiplexor and is layered over the select() system calls provided as part of the C libraries on most operating systems. Like Channel Access, fdmgr can be used by calling any of the routines whose prototypes are in fdmgr.h. To use it with Channel Access, a client program must call ca_add_fd_registration(). This call allows the client application to be informed whenever the client library places a new file descriptor into service or removes a file descriptor from service.

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 ca_pend_event() to allow the event handler to execute.

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 timeval structures (lines 14-15). timeval is a structure available on most systems in sys/types.h. Next, a chid variable is declared (line 17), and then a pointer to a defined type called fdctx is declared (18). fdctx is defined in fdmgr.h, and at least one pointer-to-type fdctx or variable of type fdctx should be declared by a program that is going to use fdmgr.

In main(), our program first calls ca_task_intialize() to initialize the Channel Access task (line 36), and then calls fdmgr_init()to initialize fdmgr (line 39). fdmgr_init() returns a pointer to an fdctx structure if it initializes successfully; otherwise, it returns NULL, in which case our program exits.

After the File Descriptor Manager and Channel Access are initialized, our program calls ca_add_fd_registration() (line 46), which accepts as its first argument the address of a routine defined by the client program, and as its second argument, a pointer-to-any-type, which will be passed as the first argument to the specified routine. The routine will then be called whenever the Channel Access client library places a file descriptor into service or removes one from service. Our routine is called fd_register() and is listed below.

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:

  1. A pointer to an fdctx type object.

  2. The file descriptor fd.

  3. An enumerated value that represents the "interest" in the file descriptor. In our case, we simply want to read from it, so we specify fdi_read. If we wanted to write to it, we would specify fdi_write. These values are defined in fdmgr.h.

  4. The name of the user-defined routine which will be called when the file descriptor becomes active. Our routine is called caFDCallBack() and is listed below. The user-defined routine can perform any task; however, it must return void, and accepts an a pointer to any object as its only argument. This argument is passed as the next argument.

  5. An address to be passed to the routine specified in the fourth argument. In our case, we pass NULL.

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 main(), our program calls fdmgr_add_timeout() (line 48), another fdmgr routine whose prototype is found in fdmgr.h. fdmgr_add_timeout() is a routine that sleeps for the amount of time specified by the second argument and then calls the routine specified by the third argument after the time has elapsed. The first argument must be a pointer to fdctx, the second must be the address of a timeval structure, the third must be the name of a function in the client program, and the fourth argument is a pointer to any type that will be passed to the specified function. Our program specifies the function capollfunc(), which is listed below:

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 ca_pend_event(), ca_pend_io(), or ca_sg_block() with a brief timeout period at least once every fifteen seconds. When any of these functions is called with such a small value--we use 1.0e-9--it is called a poll. Channel Access should be polled at least once every 15 seconds for maximum efficiency, though Channel Access will function if the program doesn't do so. In VxWorks, when the Channel Access modules are loaded onto the IOC, a separate task is spawned that calls ca_pend_event() at the necessary frequency; thus, the developer doesn't have to worry about polling the server. In single-threaded environments like UNIX, fdmgr_add_timeout() allows a program to poll the server.

After the call to fdmgr_add_timeout(), our program checks to see if the proper number of arguments have been provided, and then it enters an infinite for loop. On the first run, the program will try to establish a connection to the PV specified on the command line (lines 61-62). To do this, it calls ca_search(). If it fails to find the PV, the program exits; otherwise, it installs a monitor on the PV by calling ca_add_array_event() (lines 63-78).

The call ca_add_array_event() is an alias of ca_add_masked_array_event(). It calls ca_add_masked_array_event() with the mask DBE_VALUE and DBE_STAT. Thus, our call places a monitor on the specified PV that will trigger a monitor event if the PV's value changes or if its alarm state changes.

The first argument passed to ca_add_array_event() is the database request type. In our call, we use the macro dbf_type_to_DBR_STS() as the first call, passing the PV's native type to the macro. We used this macro in the example program of the previous chapter to pass the DBR_STS type that corresponds to the PV's native type. Thus, if the channel's type is DBF_LONG, the database request type passed to ca_add_array_event() would be DBR_STS_LONG. The second argument uses the ca_element_count() macro to determine the native element count of the channel; if the PV is scalar, the element count would be 1. Thusly, when an event occurs, the event handler will be able to retrieve all the elements of an array PV.

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 ca_add_masked_array() or ca_add_array_event().

The last argument of ca_add_array_event() should be a pointer to type evid, where evid is an event identifier much like a PV identifier. An event identifier would mostly be used in order to clear the monitor on a channel by passing it to ca_clear_event(). Since our program only uses one monitor and does not intend to remove it at a later point in the program, a NULL pointer has been passed as this argument.

The event handler specified by ca_add_array_event() will be called when the PV's value changes by more than the deadband or when the its alarm status changes. The event handler for a monitor event can be identical to the event handler specified in a call to ca_get_callback() in that both handlers are interested in the PV's value. The structure passed to both handlers is the same: event_handler_args, which is defined in cadef.h and which is explained in ChapterFigure 2.

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 DBR_STS structure.

{
	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 access_rights_handler_args, defined in cadef.h. This structure is fairly simple as it consists only of the PV's channel ID and another 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 fdmgr_pend_event() (line 87), another fdmgr routine. This call accepts a pointer to fdctx as its first argument and an address of a timeval structure. In this case, we have passed it pfdctx and the timeval structure for twenty seconds, twenty_seconds. Therefore, this call will wait twenty seconds or until some file descriptor activity is detected, upon which any callback functions are called and allowed to execute. For instance, if any file descriptor activity is detected, the function caFDCallBack() is called and executed.

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 ca_pend_event() with a minute time period such as 1.0e-9. Using fdmgr_add_timeout() is an easy way to do this on single-threaded environments.

Monitoring a PV for Changes
Monitoring a PV for Changes in Access Rights
An Example Program

Channel Access Client Tutorial - 4 NOV 1997
[Next] [Previous] [Top] [Contents] [Index]

| LANL | Lansce | UC | DOE |

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
Operated by the University of California for the US Department of Energy

Copyright © 1996 UC   Disclaimer   


For problems or questions regarding this web site contact George Vaughn.