在本例中,我们将学习使用监视器在通道变化时读取它们而不是轮询它们。 当你需要知道快速变化时,使用监视器而不是高速率轮询。当一个值大约每分钟变化时,不必要浪费带宽每秒10次地查询它。使用任何查询速率,你总是有一个延时,并且你可能仍然错过短时间的峰。使用监视器,你将不会。并且它只在发生了一些“有趣”的事情时才产生网络流量。对于模拟值(即:DOUBLE),在记录中定义要求变化多少是"有趣"。对于其它类型,例如,ENUM, 每种变化都是"有趣"。要减小混乱程度,我们现在不使用PV和宏,而直接使用CA函数。
以下是源代码:
#include
/* 包含EPICS头 */
#include
#define epicsAlarmGLOBAL
#include
/*
这是一个用户定义的回调函数。当一个通道有一个新值时,调用这个函数。
struct event_handler_args的定义见${EPICS_BASE}/include/cadef.h。
typedef struct event_handler_args {
void *usr; // 请求提供的用户参数
chanId chid; // 通道标识符
long type; // 回项的类型
long count; // 返回项的元素数目
const void *dbr; // 指向返回项的指针
int status; //从服务器请求op的ECA_XXX状态
} evargs;
我们这里不使用字段'count'和'usr'。字段count对应数组,
而'usr'是你可以传递给监视器安装函数的任意指针。
*/
static void monitor(struct event_handler_args args)
{
if (args.status != ECA_NORMAL)
{
/* 出问题了 */
SEVCHK(args.status, "monitor");
return;
}
/*
我们查看以下数据类型。它应该是我们请求的类型之一。
*/
switch (args.type)
{
case DBR_STS_DOUBLE:
{
/*
struct dbr_sts_double{
dbr_short_t status; // 值的状态
dbr_short_t severity; // 静态严重性
dbr_long_t RISC_pad; // RISC对齐
dbr_double_t value; // 当前值
};
*/
const struct dbr_sts_double* data = args.dbr; //指向返回项
printf ("%s = %#g %s\n", //打印通道名称,通道值,以及严重性
ca_name(args.chid), data->value,
epicsAlarmSeverityStrings[data->severity]);
break;
}
case DBR_STS_ENUM:
{
/*
struct dbr_sts_enum{
dbr_short_t status; // 值的状态
dbr_short_t severity; // 警报严重性
dbr_enum_t value; // 当前值
};
*/
const struct dbr_sts_enum* data = args.dbr;
printf ("%s = %i %s\n", //打印通道名称,通道值,以及严重性
ca_name(args.chid),
data->value,
epicsAlarmSeverityStrings[data->severity]);
break;
}
default:
printf ("%s unsupported data type\n", ca_name(args.chid));
}
}
int main()
{
char* doubleName="TEST:Double1";
char* enumName="TEST:Enum1";
chid doubleChannel, enumChannel;
double search_timeout = 5.0; /* seconds */
/* 第一步:创建非抢占式回调上下文,初始化通道访问并且搜索所有通道 */
ca_context_create(ca_disable_preemptive_callback);
/*
ca_create_channel有参数多于ca)search,但我们在此不需要。
*/
ca_create_channel(doubleName, NULL, NULL, CA_PRIORITY_DEFAULT, &doubleChannel);
ca_create_channel(enumName, NULL, NULL, CA_PRIORITY_DEFAULT, &enumChannel);
SEVCHK(ca_pend_io(search_timeout), "ca_search");
/* 第二步:设置监视器 */
/*
用不同数据类型创建两个监视器。连接它们到相同的回调函数。
第6个参数将在处理程序参数中被传递给'usr'元素。我们在此不需要它。
*/
ca_create_subscription(DBR_STS_DOUBLE, 1, doubleChannel,
DBE_VALUE|DBE_ALARM, monitor, NULL, NULL);
ca_create_subscription(DBR_STS_ENUM, 1, enumChannel,
DBE_VALUE|DBE_ALARM, monitor, NULL, NULL);
/*
在这里,我们必须指定:想要标量,非数组(count=1)
我们对值和警报变化感兴趣。
*/
SEVCHK(ca_flush_io(), "ca_flush_io()");
/*
我们在这里使用了ca_flush_io(),因为没有东西要等待。我们仅发出请求。
注意:ca_pend_io(timeout)作用类似ca_flush_io,加上另外等待待处理应答。
*/
/* 第三步: 一直等待并且在后台进行通道访问 */
ca_pend_event(0.0);
/* 我们不会到达这里 */
printf("Done\n");
ca_context_destroy();
return 0;
}
编译以上程序,并且进行测试:
orangepi@orangepi4-lts:~/host_program/host/bin/linux-aarch64$ ./ca_ex4
TEST:Double1 = 27.5000 NO_ALARM
TEST:Enum1 = 1 NO_ALARM
TEST:Double1 = 28.0000 NO_ALARM
TEST:Enum1 = 0 NO_ALARM
TEST:Double1 = 28.5000 NO_ALARM
TEST:Enum1 = 1 NO_ALARM
TEST:Double1 = 29.0000 NO_ALARM
TEST:Enum1 = 0 NO_ALARM
TEST:Double1 = 29.5000 NO_ALARM
TEST:Enum1 = 1 NO_ALARM
TEST:Double1 = 30.0000 NO_ALARM
TEST:Enum1 = 0 NO_ALARM
TEST:Double1 = 30.5000 NO_ALARM
TEST:Enum1 = 1 NO_ALARM
TEST:Double1 = 31.0000 NO_ALARM
TEST:Enum1 = 0 NO_ALARM
^C
通道访问函数说明:
struct ca_connection_handler_args {
chanId chid; //通道标识符
long op; //CA_OP_CONN_UP或CA_OP_CONN_DOWN之一
};
typedef void ( caCh ) (struct connection_handler_args);
int ca_create_channel (const char *PVNAME,
caCh *USERFUNC, void *PUSER,
capri PRIORITY, chid *PCHID );
描述:这个函数创建一个CA通道。CA客户端库将在调用程序和CA服务器中一个指定名称过程变量之间尝试建立和维护一个虚电路。对ca_create_channel()的每次调用在CA客户端库以及也在CA服务器中分配资源。函数ca_clear_channel()用于释放这些资源。如果成功,这个例程写一个通道标识符到类型"chid"的用户变量。这个标识符可以与操作通道的任何通道访问调用一起使用。取决于网络状态和通道的位置,电路可能初始连接或者断开。仅在服务器的地址被确定,并且通道访问通过网络成功建立到服务器的虚电路,通道才进入一个连接状态。如果通道当前断开了,向服务器发送请求的通道访问例程将返回ECA_DISCONNCHID。当通道进入一个连接状态时,获取异步通告有两种例程。首先和最简单的方法需要你调用ca_pend_io(),并且在使用指定一个null连接回调函数指针创建的通道前,等待成功结束。第二个方法要求你提供一个有效回调函数指针注册一个连接处理程序。当通道的连接状态变化时,这个连接处理程序被调用。如果你安装了一个连接处理程序,则ca_pend_io()将不阻塞等待通道进入一个连接状态。函数ca_state(CHID)可以用于测试一个通道的连接状态。如果ca_pend_io()超时,用这个函数从无效连接连接区别有效连接。由于网络连接的固有瞬态特性,连接回调的顺序相对于程序进行ca_create_channel()调用的顺序不能被保证,
并且应用程序需要为一个连接通道在任何时候进入一个断开状态做好准备。
参数:
1)PVNAME:一个nil结尾的过程变量名字符串。EPICS过程控制功能块数据库变量名格式为"
2)USERFUNC:指向用户回调函数的可选指针,当连接状态变化时,被运行。如果一般通道访问用户不需要运行一个回调函数响应每次连接状态变化,他们可以决定设置这个字段为null或0。以下结构体通过值被传递给这个用户的连接回调函数。当通道连接时,op字段将被CA客户端库设置为CA_OP_CONN_UP;在通道断开时,设置为CA_OP_CONN_DOWN。 ca_puser()如果在你的回调处理程序中需要PUSER参数。
3)PUSER:这个void指针参量的值被保存在与指定通道相关联的存储区。一般通道访问用户可以设置这个字段为null或0.
4)PRIORITY:在服务器或网络中用于调度的优先级,0指定最低调度优先级而99最高。
5)PCHID:如果这个通道成功,用通道标识符重写用户提供的通道标识符存储区
返回:
ECA_NORMAL -正常的成功结束
ECA_BADTYPE -无效的DBR_XXXX类型
ECA_STRTOBIG -异常的大字符串
ECA_ALLOCMEM -不能分配内存
int ca_pend_io ( double TIMEOUT );
描述:这个函数清空发送缓存并且接着在待处理ca_get()请求结束前,以及在指定连接处理函数指针创建的通道首次连接前阻塞。如果返回ECA_NORMAL,则可以安全地认为所有待处理ca_get()请求成功地连接以及指定null连接处理程序函数指针首次连接。如果返回ECA_TIMEOUT,则必须认为所有先前的ca_get()请求并且可能合格的首次通道连接已经出错。如果返回ECA_TIMEOUT,则在get请求可能被再次发出,后面接着调用ca_pend_io()。尤其,这个函数将在上次ca_pend_io()或者ca客户端上下文创建后(无论哪个在更靠后)只阻塞发出的待处理的ca_get()请求,以及指定一个null连接处理程序函数指针创建的任何通道。
注意: 除非之前调用了ca_ca_create_channel(), 否则不应该为相同的过程变量再次发送ca_create_channel()请求。如果没有ca_get()或连接状态变化事件是待处理的,则ca_pend_io将冲刷发送缓存并且立即返回,而不处理任何待处理的通道访问后台活动。为ca_pend_io()指定的延迟应该考虑最差情况网络延时,诸如在重发延时前以太网冲突指数后退,这在过载网络上会很长时间。不同于ca_pend_event(),如果没有选择的IO请求正在发生,这个程序将不处理CA的后台活动。
参数:
TIMEOUT:指定超时间隔。一个为0的TIMEOUT间隔指定永远。
返回:
ECA_NORMAL - 正常的成功结束
ECA_TIMEOUT - 在指定的超时前,选取的IO请求没有结束
ECA_EVDISALLOW - Function inappropriate for use within an event handler
#include
int ca_test_io();
描述:在对ca_pend_io()的最后一次调用或者CA上下文初始化,无论哪个在后,这个函数测试所有ca_get()请求是否结束以及指定一个null连接回调函数指针创建的通道是否连接。它将报告发出待处理ca_get()请求以及指定null连接回调函数指针创建的通道的状态。
返回:
ECA_IODONE - 所有IO操作结束了
ECA_IOINPROGRESS - IO操作仍然进行中
int ca_pend_event ( double TIMEOUT );
int ca_poll ();
描述:当ca_pend_event()被调用时,发送缓存被冲刷,并且运行CA后台活动TIMEOUT秒。当调用ca_poll()时,发送缓存被冲刷, 并且处理任何待处理的CA后台活动。ca_pend_event()在指定超时以及所有未完成的通道访问工作被处理完前,ca_pend_event()函数不返回并且不同于从函数返回不表明有关正在发生的IO请求状态任何事情的ca_pend_io()。成功时,ca_pend_event()和ca_poll()返回ECA_TIMEOUT.这种行为可能不是凭直觉的,但保留它来确保后向兼容性。
参数:
TIMEOUT:以秒为单位在这个例程中阻塞的时长。0超时时间一直阻塞。
返回:
ECA_TIMEOUT - 操作超时。
ECA_EVDISALLOW - 不适合在一个回调处理程序中使用的函数。
#include
int ca_flush_io();
描述:
清空待处理IO请求给服务器。这个例程对与在服务器中工作并行执行客户端工作前清空请求的用户有用。当保存待处理请求的缓存满时,它们也被发送。
返回:
ECA_NORMAL - 正常成功结束。