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

Channel Access Client Tutorial

5. Synchronous Group Operations and Other Client Calls

This chapter will cover the Channel Access calls that are used for synchronous group operations such as ca_sg_array_put() and ca_sg_array_get(). In addition, the second half of the chapter will touch upon some of the calls which were not used in this tutorial.

Synchronous Group Calls

Synchronous group calls are used to place a set of operations into a group, perform those operations, and verify the completion of those operations.

Unlike functions that specify event handlers such as ca_put_callback() or ca_get_callback(), synchronous group routines do not specify an event handler. Instead, ca_sg_get() and ca_sg_put() generate queries when a client program issues them. These queries remain outstanding until the operation successfully completes. Thus, a client program can use the calls ca_sg_block() or ca_sg_test() to check if any queries are outstanding. If queries remain outstanding, the client program can assume that all operations for the group have failed.

Using different synchronous groups and assigning a different group ID to each, a client program can keep track of the success/failure of operations on each of those groups. For instance, ca_sg_block() can be used to flush synchronous group requests for a specific group. ca_sg_block() basically works like ca_pend_io(): the client program passes it a floating point number that specifies a time period in seconds. It will block until all outstanding queries associated with the group have been answered or until the time period has elapsed, in which case it times out, returning ECA_TIMEOUT. The client program should then assume that all operations on the group's PVs have failed. ca_sg_block(), however, only operates on one specific group. In addition to a number of seconds, ca_sg_block() accepts a group ID as its other argument. It will flush only the requests issued for PVs that belong to that group and will only check for queries for those requests. Thusly, after several different groups have been created and have been given a unique identifier, ca_sg_put() calls and ca_sg_get() calls can be issued for each PV in those groups. Then, ca_sg_block() can be called to check for the success/failure of a specific group. This way a program can keep distinct different sets of PVs as well as the operations performed on them.

Let's look at another trivial program that will demonstrate synchronous group requests. This program establishes connections to four different PVs. It then creates two synchronous groups. It issues four ca_sg_array_get() requests, placing the requests for the first two PVs in synchronous group 1 and the second two in synchronous group 2. Then, it flushes the first group using ca_sg_block(). If ca_sg_block() returns ECA_TIMEOUT, an error message is printed and nothing else occurs. Otherwise, the retrieved values are printed. If the sum of these values is less than 100, ca_sg_block() is called to flush the second group. Again, if ca_sg_block() returns ECA_TIMEOUT, nothing else occurs; otherwise, the values from the second group of channels is added to the first group. Then, ca_sg_array_put() is called twice to send these new values back to the first two PVs. Both of these put operations are placed in the same group. ca_sg_block() flushes these requests, and the program checks to see if it returns ECA_TIMEOUT. If it does, then a message is printed; otherwise, the program prints the new values for the PV and then exits.

#include <stdio.h>

#include <cadef.h>

struct Channel{
	chid chan;
	dbr_string_t name;
	dbr_float_t value;
};

int main()
{
	int status1, status2, i = 0;
	struct Channel Array[4];
	CA_SYNC_GID gid1, gid2; /* synchronous group identifiers */
  
	SEVCHK(ca_task_initialize(), "ca_task_initialize: unable to initialize");
  
	/* get channel names and establish connections */
	printf("Enter up to 4 channels.\n");
	while (i < 4 && gets(Array[i].name) != NULL){
		SEVCHK(ca_search(Array[i].name, &(Array[i].chan)), "ca_search: bad request");
		status1 = ca_pend_io(0.5);
		if(status1 == ECA_TIMEOUT)
			printf("channel %s: couldn't establish connection. ", Array[i].name);
		else
			i++;
		if (i!=4)
			printf("Enter another channel.\n");
	}

	/* create two synchronous groups */
	status1 = ca_sg_create(&gid1);
	status2 = ca_sg_create(&gid2);
	if(status1 != ECA_NORMAL || status2 != ECA_NORMAL){
		printf("ca_sg_create(): memory allocation failure, exiting.\n");
		exit (-1);
	}

	/* make requests and verify their validity */
	SEVCHK (ca_sg_array_get(gid1, DBR_FLOAT, 1, Array[0].chan, &(Array[0].value)),
				"ca_sg_get(): bad request");
	SEVCHK (ca_sg_array_get(gid1, DBR_FLOAT, 1, Array[1].chan, &(Array[1].value)),
				"ca_sg_get(): bad request");
	SEVCHK(ca_sg_array_get(gid2, DBR_FLOAT, 1, Array[2].chan, &(Array[2].value)),
				"ca_sg_get(): bad request");
	SEVCHK(ca_sg_array_get(gid2, DBR_FLOAT, 1, Array[3].chan, &(Array[3].value)),
				"ca_sg_get(): bad request");

	/* flush buffer and check for ECA_TIMEOUT */
	status1 = ca_sg_block(gid1, 0.5);
	if (status1 == ECA_TIMEOUT)
		printf("ca_sg_get(): unsuccessful.\n");
	else{
		printf("%s = %.3f\t", Array[0].name, Array[0].value);
		printf("%s = %.3f\n", Array[1].name, Array[1].value);

		/* if combined values less than 100, get other values *
		/* and add to first values. Print new values.             */
		if (Array[0].value + Array[1].value <= 100){
			status2 = ca_sg_block(gid2, 0.5);
			if(status2 == ECA_TIMEOUT)
				printf("ca_sg_get(): unsuccessful.\n");
			else{
				Array[0].value += Array[2].value;
				Array[1].value += Array[3].value;
				SEVCHK(ca_sg_array_put(gid1, DBR_FLOAT, 1, Array[0].chan,
											&(Array[0].value)),
						"ca_sg_put: bad request");
				SEVCHK(ca_sg_array_put(gid1, DBR_FLOAT, 1, Array[1].chan,
											&(Array[1].value)),
						"ca_sg_put: bad request");
				status1 = ca_sg_block(gid2, 0.5);
				if(status1 == ECA_TIMEOUT)
					printf("ca_sg_get(): unsuccessful.\n");
				else{
					printf("%s = %.3f\t", Array[0].name, Array[0].value);
					printf("%s = %.3f\n", Array[1].name, Array[1].value);
				}
			}
		}
	}
	return(0);
}
After getting four PV names from the user and establishing connections to them using ca_search() (lines 14-23), our program calls ca_sg_create() twice to create two groups, passing each of them a unique group ID (lines 24-25). The two group identifiers were declared at the beginning of main() (line 11). CA_SYNC_GID is a derived type found in cadef.h

After two synchronous groups have been successfully created, the program issues four get requests for each channel using ca_sg_array_get(), which is similar to ca_array_get() explained in Chapter 2., Reading PV Values, except that ca_sg_array_get() must provide an extra argument as the first argument, which is the group ID. For example, in our request (lines 30-31), the call is passed gid1 as the group ID, DBR_FLOAT as the request type, the value 1 as the PV's element count, the PV's channel ID, and &(Array[0].value) as the address where the PV's value is to be written. Thus, our call places this request in synchronous group 1, and requests that the PV's value be converted to a floating-point number and written into Array[0].value. The remaining calls to ca_sg_array_get() make the requests to retrieve the values for the remaining three PVs, placing the last two requests in the second synchronous group (lines 32-37).

Our program then calls ca_sg_block() (line 38). ca_sg_block() is similar to ca_pend_io(). Its first argument is a group ID, and its second is a floating-point number which specifies a time out period. It blocks until all queries for that group have been answered or until the time out period has elapsed, in which case it returns ECA_TIMEOUT. Our program passes it the group ID gid1 and a time period of 0.5 seconds. If the call returns ECA_TIMEOUT, our program assumes the two operations belonging to the first synchronous group have failed, upon which our program prints a message and no other action is taken (lines 39-40).

If the operations in the first group are successful, their values are printed (lines 42-43). If the sum of these values is under 100, then the program calls ca_sg_block() for the second group, passing it gid2 and a time period of 0.5 seconds (line 45). If this call does not return ECA_TIMEOUT, then the sum of the third channel is added to the first and the sum of the fourth is added to the second (lines 49-50)

These new values are then sent back to the server, to the first two PVs, using ca_sg_array_put() (lines 51-59), which accepts the same arguments as ca_sg_array_get(). Of course, with ca_sg_array_put(), the pointer provided as the last argument is a pointer to a value that is to be written to the PV, not an address to which to write the PV's current value. The database request type, DBR_FLOAT, should be the same type as the value to be written, the same type as &(Array[0].value) and &(Array[1].value).

It is important to note that ca_sg_array_put() generates queries. Remember that ca_array_put() explained in Chapter 3 doesn't generate queries. Therefore, although ca_pend_io() cannot be used to check if the operation requested by ca_array_put() was successful, ca_sg_array_put(), on the other hand, can be checked for success/failure by checking the status code returned by ca_sg_block(). If ca_sg_block() returns ECA_TIMEOUT after flushing any ca_sg_array_put() requests, the client program should assume that the put operations have failed in the server, which our program does: if the ECA_TIMEOUT is returned, no action is taken; otherwise, the new values of the channels, the values sent to them, are printed.

Synchronous group operations are a convenient way for a client program to juggle different sets of channels or different sets of operations. Actually, it is more accurate to think of a synchronous group as a set of operations rather than a set of PVs. For instance, in our program when we issue a synchronous group request, the PV doesn't become a part of the group; instead, the operation becomes a part of the group and any operations added to that group can be verified simultaneously using ca_sg_block(). If necessary, an operation on the same PV can be requested within two different group IDs. The queries on that PV are associated with the different groups.

Other synchronous group requests are ca_sg_delete(), ca_sg_test(), and ca_sg_reset(). ca_sg_delete() simply clears a synchronous group, similar to the way ca_clear_channel() deletes the connection to a PV. ca_sg_delete() accepts a group ID as its only argument. ca_sg_test() can be used to check if there are any get or put operations still in progress on a particular group. If any are, it returns the status code ECA_IOINPROGRESS; otherwise, it returns ECA_IODONE. Its only argument is a group ID.

ca_sg_reset() clears any outstanding queries associated with a group, so that, for instance, other requests can be issued. An example of the use of these instructions would be the following code, which re-checks the group several times. If finally there are still outstanding queries associated with the group, ca_sg_reset() is called:

status = ca_sg_test(GID);
if (status == ECA_IOINPROGRESS){
	for (i = 0; i < 5; i++){
		status = ca_sg_block(GID, 0.5);
		if (status == ECA_IODONE)
			break;
	}
if(ca_sg_test(GID) == ECA_IOINPROGRESS)
	SEVCHK(ca_sg_reset(), "reset failed: bad GID");

Other Channel Access Calls

This tutorial has not mentioned all the available Channel Access calls. The remaining calls perform useful functions which the client program may or may not want to utilize. A few of them are for specific use with client programs that will run on the VxWorks operating system. None of these calls will be explained in detail, but they are mostly self-explanatory and the Channel Access Reference Manual explains the details of each. They are only mentioned here so that those new to Channel Access will know of their existence:
callpurpose
ca_test_io()Checks to see if any queries are outstanding. Only checks for outstanding queries from requests issued after last call to

ca_pend_io().

ca_replace_printf_handler()Changes the format of CA error messages.
ca_host_name()Returns the name of the host to which the channel is connected; for example, the name of the IOC that the database is loaded into.
ca_test_event()Can be used for event handlers. Install it with ca_dd_event(). When called, it prints information about the event and returns.
ca_modify_user_name()Replaces the default user name communicated to the CA server for a client.
ca_modify_host_name()Replaces the default host name communicated to the CA server for a client.
ca_import()VxWorks only--by default a Channel Access context has only one task. With this call, another task can be created within the context.
ca_import_cancel()VxWorks only--Reverses ca_import().
ca_channel_status()VxWorks only--Prints status for each channel associated with a specific VxWorks task. Can be run from a client program or the VxWorks shell command line.

Once again, see the Channel Access Reference manual for more information on any Channel Access call. Also look at the header file cadef.h. The Channel Access Reference Manual also has information on Channel Access that is not related to the client library, such as how to reconfigure certain aspects of Channel Access.

Synchronous Group Calls
Other Channel Access Calls

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.