|
[Next] [Previous] [Top] [Contents] [Index] Channel Access Client Tutorial 3. Writing to PVs and Using Connection HandlersFrom the previous chapter we learned that there are two basic functions to read a PV's value:ca_array_get_callback() and ca_array_get(). Both of these calls have several aliases. For example, the aliases of ca_array_get() are ca_get(), ca_bget(), and ca_rget(). Likewise, there are two basic functions available to write a value to a PV: ca_array_put() and ca_array_put_callback() and each of these has several aliases. For instance, ca_bput() is an alias of ca_array_put(). This chapter will demonstrate further the use of callback functions.
This chapter will also demonstrate connection handlers, event handlers which are specified by a call to An Example Program
#include <cadef.h> #define MAXLEN 20 #define POLL 1.0e-9 #define SHRT_DLY 0.1 #define LNG_DLY 0.25 struct Channel{ dbr_string_t name; chid chan; struct Channel *next; struct Channel *prev;} *pFirst = NULL, *pLast; void connect_handler1(struct connection_handler_args); void connect_handler2(struct connection_handler_args); void an_event_handler(struct event_handler_args); struct Channel *search(char *Name); int main() { int status, i, j, k; dbr_float_t *pValue; dbr_string_t Value; dbr_string_t Name[MAXLEN]; struct Channel *pCurrent; unsigned long Nelem; /* initialize Channel Access task */ SEVCHK(ca_task_initialize(), "Unable to initialize"); /* Get channel names and establish connection for each.*/ /* Allocate memory for first node, and then for each */ /* addtional node after connection is established. Loop */ /* until user enters"quit". */ for (printf("Enter a channel name.\n"), pCurrent=(struct Channel *)calloc(1, sizeof(struct Channel)); gets(pCurrent->name) != NULL && strcmp(pCurrent->name, "quit") != 0; printf("Enter a channel name or \"quit\" to stop.\n")){ status = ca_search_and_connect(pCurrent->name, /* name string */ &(pCurrent->chan),/* chid */ connect_handler1, /* connection handler */ pCurrent /* user-supplied address */ ); SEVCHK(status, NULL); ca_pend_event(LNG_DLY); if (ca_state(pCurrent->chan) != cs_conn) printf("couldn't establish connection to %s; ignoring\n", pCurrent->name); else pCurrent =(struct Channel *) calloc(1,sizeof(struct Channel)); } /* Install a different connection handler on each channel. */ if(pFirst != NULL){ for (pCurrent = pFirst; pCurrent != NULL; pCurrent = pCurrent->next){ status=ca_change_connection_event(pCurrent->chan, connect_handler2); SEVCHK(status, "ca_change_connection_event: couldn't change handler"); } ca_flush_io(); /* Loop until "quit" is entered. Get Channel name. If */ /* found, prompt for values; else skip rest of loop. */ /* Call ca_put_callback(). */ for (i = 0, printf("%s\n%s\n", "Enter the PV's name", "Enter \"quit\" to stop"); gets(Name) != NULL && strcmp(Name, "quit") !=0; printf("%s\n", "Enter PV name or \"quit\" to stop")){ ca_pend_event(POLL); pCurrent = search(Name); if(pCurrent == NULL){ printf("Cannot find channel name in list. Disconnected?\n"); continue; } Nelem = ca_element_count(pCurrent->chan); if(Nelem == 1){ printf("Enter value.\n"); if(gets(Value) != NULL){ status = ca_put_callback(DBR_STRING, /* external type */ pCurrent->chan, /* chid */ Value, /* string of value */ an_event_handler, /* callback */ pCurrent /* user supplied address */); SEVCHK(status, NULL); } } else if (Nelem > 1){ printf("Enter up to %u values or q to quit.\n", Nelem); pValue = (float *) calloc(Nelem, sizeof(*pValue)); for(k = 0; (j = scanf("%f", pValue+k)) == 1&& j != EOF && k < Nelem; k++) ; j = getchar(); status = ca_array_put_callback(DBR_FLOAT, /* external type */ Nelem, /* no. of elements to be sent */ Current->chan, /* chid */ pValue, /* array of values */ an_event_handler, /* callback */ pCurrent /* user supplied address */ ); SEVCHK(status, NULL); } ca_pend_event(SHRT_DLY); } return(0); }After the appropriate header files are included, and prototypes and variables declared (lines 1-21), Channel Access is initialized (line 22). Then the program enters a for loop which prompts the user for a PV name each iteration and attempts to establish a connection with the specified PV using ca_search_and_connect() (lines 23-36). The function ca_search_and_connect() is similar to ca_search(): it will attempt to establish a channel to the specified PV. However, in contrast ca_search() it can specify a connection handler, which is an event handler that is called when the channel's connection state changes. This occurs when a new channel is established, or when a channel is disconnected or reconnected. Our connection handler is called connect_handler1() and will be discussed later on. Here, it's only important to know that the handler will be called if Channel Access can establish a channel for the specified PV and that it will add the PV to a linked list of connected PVs.
After
cs_conn, the channel isn't connected. Our program prints a message if this is the case. Otherwise the program allocates memory for another node and continues. Using the macro ca_state() is one way to check a channel's connection state and is most useful within the main() part of the program or within a function that is not a connection handler. In connection handlers, there is an easier way to check the connection state.
After the user enters "quit" and the for loop ends, another for loop is entered (lines 37-40). The purpose of this loop is to replace the connection handler installed on each PV in the previous loop, with a different connection handler called
The
After the request buffer is flushed, the program enters another for loop (lines 42-72). The purpose of this loop is to prompt the user for a PV name, look for the PV in the linked list containing all currently connected PVs, and if found prompt the user for a value or values and write those values to the PV. After it gets the PV name from the user, the program searches for the PV in the linked list by calling
After the PV is found, the program calls the macro
Next, 1. Connection HandlersOur program uses a doubly-linked list to store each PV's name and channel ID. Thus, later on in the program when the user enters the PV's name, the list can be traversed for a match with that name; when a match is found, the chid in the same node can be used for any other operations on the PV. The node structure is defined globally beforemain() (lines 6-10), where pFirst and pLast are the nodes used to keep track of the first and last elements in the list. pCurrent points to the current node and is declared in main().
int ca_search_and_connect ( /* Name IO Value */ /* ---- -- ----- */ char *, /* NAME R NULL term ASCII channel name string */ chid *, /* CHIDPTR RW channel index written here */ void (*)(struct connection_handler_args), /* PFUNC R the address of user's connection handler */ void * /* PUSER R placed in the channel's puser field */ );The above prototype is the format that appears in the Reference Manual and is fairly easy to understand: the Name is simply the name of the argument, while IO simply indicates if Channel Access reads the value of the argument (R), writes a value into the argument (W), or both (RW).
Our call to
Note that after our program calls
Connection handlers are called when a channel's connection state changes, either when it is disconnected and becomes connected, or when it's connected and becomes disconnected. Note that when we use
Connection handlers can perform any task. The first connection handler,
Here's the code for
void connect_handler1(struct connection_handler_args connect_args)
{
struct Channel *pCurrent;
/* set point to user-supplied address */
pCurrent = ca_puser(connect_args.chid);
/* if connection up, intialize list or add node */
if(connect_args.op == CA_OP_CONN_UP){
if(pFirst == NULL){
pFirst = pCurrent;
pFirst->prev = NULL;
pLast = pCurrent;
}
pLast->next = pCurrent;
pCurrent->prev = pLast;
pCurrent->next = NULL;
pLast = pCurrent;
printf("%s is connected.\n", pCurrent->name);
}
/* if connection down, print message. This block */
/* is unlikely to ever be used. */
else
printf("%s is not connected.\n", pCurrent->name);
return;
}
Firstly, the pointer passed as the last argument to connect_handler1(), a pointer to the current node, is accessed by a call to the macro ca_puser(), which returns the address passed to the handler from the calling routine. The address is stored in the channel ID structure also passed to the handler. The member puser holds the address, so alternatively, the address returned by ca_puser() can be accessed with this member as in:
pCurrent=connect_args.chid.puser;Next, our handler checks the channel's connection state:
if(connect_args.op == CA_OP_CONN_UP)This is the usual way an event handler will check a channel's connection state, by checking the op member of the connection_handler_args structure which is automatically passed to all connection handlers. To check to see if the state is down, there is the constant CA_OP_CONN_DOWN. Here is the definition of the connection_handler_args structure:
struct connection_handler_args{
struct channel_in_use *chid; /* Channel id */
long op; /* External codes for CA op */
};
The structure is very simple and consists of two members: the channel ID or chid structure associated with the PV and the op member that indicates the success/failure of the operation.The rest of the connection handler's code initializes the list. The disconnect message in the else statement at the end of the handler is unlikely to ever be used, since we are going to install another handler for each channel.
The second connection handler,
if (connect_args.op == CA_OP_CONN_DOWN)
relink list
delete node
else if(connect_args.op == CA_OP_CONN_UP){
add node to list
void connect_handler2(struct connection_handler_args connect_args)
{
struct Channel *pCurrent;
struct Channel *pPrevious, *pNext;
/* if connection down, free memory of user */
/* supplied address, and delete nodes from list. */
if (connect_args.op == CA_OP_CONN_DOWN){
pCurrent = ca_puser(connect_args.chid);
if(pCurrent != pLast && pCurrent != pFirst){
pPrevious = pCurrent->prev;
pNext = pCurrent->next;
pPrevious->next = pNext;
pNext->prev = pPrevious;
free(pCurrent);
}
else if (pCurrent == pLast && pCurrent != pFirst){
pLast = pCurrent->prev;
pLast->next = NULL;
free(pCurrent);
}
else if (pCurrent == pFirst && pCurrent != pLast){
pFirst = pCurrent->next;
pFirst->prev = NULL;
free(pCurrent);
}
else if(pCurrent == pFirst && pCurrent == pLast){
pFirst = NULL;
pLast = NULL;
free(pCurrent);
}
printf("%s: channel disconnected.\n", ca_name(connect_args.chid));
}
/* if re-connected, re-allocate memory */
else if(connect_args.op == CA_OP_CONN_UP){
pCurrent = (struct Channel *) calloc(1, sizeof(struct Channel));
connect_args.chid->puser = pCurrent;
strcpy(pCurrent->name, ca_name(connect_args.chid));
pCurrent->chan = connect_args.chid;
if (pFirst == NULL){
pFirst = pCurrent;
pFirst->prev = NULL;
pLast = pCurrent;
pCurrent->next = NULL;
}
else{
pLast->next = pCurrent;
pCurrent->prev = pLast;
pCurrent->next = NULL;
pLast = pCurrent;
}
printf("%s: channel re-connected.\n", pCurrent->name);
}
return;
}
2. Writing to a PVOur program usesca_put_callback() to write to scalar PVs (lines 54-58) and ca_array_put_callback() to write to array PVs (lines 65-70). To write to a PV, one can a use a non-callback function such as ca_array_put() or any of its aliases: ca_bput(), ca_rput(), and ca_put(). These functions are used similar to the way ca_bget() was used in the last chapter, except that they don't generate queries, so one cannot verify if they failed or not by checking to see if ca_pend_io() times out, because ca_pend_io() will immediately return if no queries are outstanding. Alternatively, one can use the callback function ca_array_put_callback() and verify the success/failure in an event handler.
When a client program calls
A client program can then flush the In this call for the scalar PV, its first argument is the external data type that is to be sent to the database. If the type does not match the PV's native type, the server will perform the necessary conversion(s). In this case, we are sending it a string pointer, thus the database request type is DBR_STRING. The section Data Types in the previous chapter fully explains the different data types available, and some of the ways a client program can use them. The third argument is the PV's chid, and the fourth is the pointer to the data to be sent to the server, in this case a string. The next argument is the name of an event handler to be called when the put operation completes. The last argument is a pointer to any object. This pointer can then be accessed from within the event handler.
Basically,
The event handler called by our program is called
void an_event_handler(struct event_handler_args handler_args)
{
if (handler_args.status != ECA_NORMAL){
printf("channel %s: put operation not sucessful.\n",
ca_name(handler_args.chid));
SEVCHK(handler_args.status, NULL);
}
else
printf("channel %s: put operation completed.\n");
return;
}
If the operation's status is ECA_NORMAL, a message indicating that the operation was successfully completed is printed.
The other members of
If the PV entered by the user is an array PV, i.e., consists of more than one element, the program allows the user to enter up to n values where n is the maximum number of elements of the PV. The user can type in a 'q' if he/she wants to send less than the maximum number of elements. It is important to note that Channel Access does not allow a put operation to specify an offset from the beginning of the array. If, for example, the user enters five values to be sent to a PV. Our variable
Our program gets the values entered by the user as floating-point numbers. Thus, the call to
To flush the request, our program then calls
Well, that's our program, except for the
struct Channel *search(char *Name)
{
struct Channel *pCurrent;
int flag = 0;
if(pFirst != NULL){
for(pCurrent = pFirst; pCurrent != NULL; pCurrent = pCurrent->next){
if(strcmp(pCurrent->name, Name) == 0){
flag = 1;
break;
}
}
}
if(flag)
return (pCurrent);
else
return (NULL);
}
For more information on data types, see the section Data Types in the previous chapter. You may also find the section Event Handlers helpful.3. Exception HandlersAs with get operations, a client program can send values to PVs using a client call that specifies an event handler or using one that does not specify an event handler. Which one is better depends on the application.
The following "put" calls do not specify event handlers. The basic call is
ca_pend_io(), ca_pend_event(), or ca_flush_io(). However, the programmer must be aware that these calls do not generate queries. Thus, the success or failure of these calls cannot be checked by the flushing them with ca_pend_io() and then checking to see if ca_pend_io() times out. Thus, in the following piece of code, ca_pend_io() will never time out. Remember that when ca_pend_io() flushes a request or requests that do not generate queries, it simply flushes the request buffer and returns without performing any other tasks.
status = ca_put(DBR_FLOAT, chand_id, pFloat);
SEVCHK(status, "invalid request");
status = ca_pend_io(2.0)
if (status = ECA_TIMEOUT)
printf("ca_put() failed.\n");
That doesn't mean there's no way to tell if a put operation that specifies no event handler has failed. For all failed operations, an exception handler is executed. An exception is an event that occurs when the server cannot perform a request for whatever reason. For any Channel Access task, there can be only one exception handler. All tasks are initialized with a default exception handler, which merely prints a message describing the error and the severity of the error. For some errors, the message prints the host on which the error occurred. For other errors, it may also print the line number in the client program's source file where the error occurred. For example, the following exception message occurred when the server could not perform a put request by a client program:
CA.Client.Diagnostic..............................................
Message: "Could not perform a database value put for that channel"
Severity: "Warning" Context: "Could not perform a database value put for that channel"
Source File: ezee1.c Line Number: 218
..................................................................
A client program can replace the default exception handler by calling ca_add_exception_event(). This call accepts two arguments: (1) the name of the exception handler defined by the client program and (2) a pointer to any object, which the exception handler can then access.
Like other event handlers, an exception handler accepts a structure defined in cadef.h as its only argument. The structure for exception handlers is called
struct exception_handler_args{
void *usr; /* User argument supplied when event added */
struct channel_in_use *chid; /* Channel id */
long type; /* Requested type for the operation */
long count; /* Requested count for the operation */
void *addr; /* User's address to write results of CA_OP_GET */
long stat; /* Channel access std status code */
long op; /* External codes for channel access operations */
char *ctx; /* A character string containing context info */
};
Except for ctx and addr, most of these members should be self-explanatory. ctx is a string that indicates the host where the error occurred. The member addr is simply an address where the Channel Access software writes the result of a get operation, so it can be used to identify the particular operation that failed.
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
|