EPICS通道访问介绍以及练习

文章详细介绍了EPICS通道访问的概念,包括搜索和连接过程、Beacon机制、虚拟电路管理和断开,以及重要的环境变量。此外,还阐述了3.13和3.14版本的区别,提供了编写CA客户端的基本步骤,并给出了一段使用EPICS通道访问API进行简单数据获取的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提纲

1) 通道访问概念

2)通道访问API

3) 简单的CA客户端

4)使用回调的简单CA客户端

EPICS概要

搜索和连接过程

搜索请求

1)搜索请求由一系列UDP包组成

  • 只发送给EPICS_CA_ADDR_LIST
  • 从短时间间隔开始,每次加倍
  • 直到它变得大于5秒,接着它保持在5s
  • 在100个包后停止或者它得到了响应
  • 在它见到一个异常beacon或者创建一个新PV前,不再尝试
  • 发完所有100个包的全部时间大约8分钟

2)服务程序必须为每个包进行一次存在测试

3)通常在首个活前几个包时连接

4)不存在的PVs产生大量流量:尝试消除它们

Beacons

1) 一个beacon是一个由服务程序发送的UDP广播包。

2)当它健康时,每个服务程序以规则间隔发送一个UDP广播包(像一个心跳)

EPICS_CA_BEACON_PERIOD默认15秒。

3)当它启动时,每个服务程序广播一个启动序列的UDP beacons

  • 从短间隔开始(25ms, 对于vxWorks 75ms)
  • 每次间隔加倍
  • 直到它变得大于15s,接着它保持在15s。发出大于10个beacons和40s变成准备状态

4) 客户端监视这些beacons

  • 确定连接状态,是否再次发送搜索

虚电路断开

1) 3.13和早期3.14

  • 挂断消息或者30秒内无来自服务程序的响应。
  • 如果不是一个挂断,则客户端发送"你在吗"的请求。
  • 如果5秒无响应,TCP连接被关闭
  • MEDM窗口变白。
  • 客户端再次发送搜索请求

2)3.14.5以及以后

  • 来自服务程序的挂断消息。
  • TCP连接被关闭
  • MEDM变白
  • 客户端再次发送搜索请求

虚拟电路无响应

3.14.5以及以后

1)30秒内无来自服务程序的响应。

2)客户端接着发送"你在吗"的请求

3)如果5秒内无响应,TCP连接不被关闭,至少若干小时

4)MEDM窗口变白

5)客户端不重新发送搜索请求,有利于网络风暴

6)不调用ca_poll的客户端经常见到虚电路断开,即使服务程序可能是正常的。

  • 为3.13编写但使用3.14的客户端会遇到问题
  • 在以后版本中可能被修改。

重要的环境变量

1) EPICS_CA_ADDR_LIST

a.决定搜索哪里
b.是一个列表(由空格分隔)

如:"192.168.1.255 172.16.2.20 10.30.55.50"

c.默认是主机上所有网卡的广播地址

当服务器和客户端在相同子网时有效

d.广播地址
  • 发送给一个子网上所有服务器
  • 示例:192.168.1.255
  • 在UNIX上使用ifconfig -a查找它

2)  EPICS_CA_AUTO_ADDR_LIST

  • YES:在搜索中包含以上默认地址
  • NO:不要再默认地址上搜索
  • 如果你设置了EPICS_CA_ADDR_LIST, 通常设置这个为NO。

 其它重要的环境变量

1)CA客户端
  • EPICS_CA_ADDR_LIST
  • EPICS_CA_AUTO_ADDR_LIST
  • EPICS_CA_CONN_TMO
  • EPICS_CA_BEACON_PERIOD
  • EPICS_CA_REPEATER_PORT
  • EPICS_CA_SERVER_PORT
  • EPICS_CA_MAX_ARRAY_BYTES
  • EPICS_TS_MIN_WEST
2)CA服务器
  • EPICS_CAS_SERVER_PORT
  • EPICS_CAS_AUTO_BEACON_ADDR_LIST
  • EPICS_CAS_BEACON_PERIOD
  • EPICS_CAS_BEACON_PORT
  • EPICS_CAS_INFT_ADDR_LIST
  • EPICS_CAS_IGNORE_ADDR_LIST

3.13和3.14类似

  • 为了使为3.13编写的客户端在没有编码修改下对3.14有效,付出了非常大的努力
  • 甚至像MEDM的大型程序只需要进行少量的修改
  • 这表示已有程序一般不需要被重新编写。
  • 相比较,在切换到3.14中通道访问服务器需要很多更改

3.13和3.14不同

1) 3.14是线程的

你的程序不是必须是线程的。

2)3.14对某些功能有不同的名称。

  • ca_context_create对应ca_task_initialize
  • ca_context_destroy对应ca_task_exit
  • ca_create_channel对应ca_search_and_connect
  • ca_create_subscription对应ca_add_event
  • ca_clear_subscription对应ca_clear_event
  • 新函数可能有更多功能,通常与线程相关
  • 我们将使用新名称。

3)3.14对丢失连接有不同机制

  • 虚电路无响应(3.13中不可用)。
  • 虚电路断开

编写一个通道访问客户端的基本过程

1)初始化通道访问

ca_task_initialize或ca_context_create

2)搜索

ca_search_and_conntect或ca_create_channel

3)进行读或写

ca_get或ca_put

4)监视

ca_add_event或ca_create_subscription

5)让通道访问有机会运行

ca_poll, ca_pend_io, ca_pend_event

6)关闭通道访问

ca_task_exit或ca_context_destroy

所有C或C++程序必须包含cadef.h:#include <cadef.h>

库函数介绍

1)ca_context_create

enum ca_preemptive_callback_select{
    ca_disable_preemptive_callback,
    ca_enable_preemptive_callback
};

int ca_context_create(enum ca_preemptive_callback_select SELECT);
  • 在任何其它调用前被调用一次
  • 设置通道访问
  • 除非你想要使用多线程,否则SELECT=ca_disable_preemptive_callback
  • 为了3.13兼容性,也可以使用ca_task_initialize()

2) ca_context_destroy

void ca_context_destroy()
  • 在退出你的程序前,应该被调用。
  • 关闭通道访问。
  • 为了3.13兼容性,也可以使用ca_task_exit()

3) ca_create_channel

typedef void caCh(struct connection_handler_args ARGS);
int ca_create_channel(
    const char * PVNAME,
    caCh * CALLBACK,
    void * PUSER,
    capri PRIORITY,
    chid * PCHID
);

a) 设置一个通道访问并且开始搜索过程。

b) PVNAME是过程变量的名称。

c) CALLBACK是你连接回调(或者NULL)

  • 当连接状态变化时,包括首次连接,将调用这个回调
  • 有关这个通道的消息被包含在ARGS中
  • 如果你不需要一个回调,使用NULL

d) PUSER是一种传递其它参数的方式

  • 你拥有的任何东西被存储在这个地址中
  • 它被存储在chid中
  • 在C++中,它经常是对应一个类的this指针
  • 如果你不需要它,使用NULL

e) 使用PRIORITY=CA_PRIORITY_DEFAULT

f) 一个chid是一个指向一个不透明struct的指针(地址),通道访问使用它存储有关这个通道大部分信息。chanId与chid相同(typdef chid chanId)

g) PCHID是chid指针的地址(使用&CHID)

  • 在进行调用前,你需要为chid分配空间
  • 通道访问将为这个struct分配空间并且返回它的地址

h) 使用宏访问chid中的信息

  • ca_name(CHID):获取这个过程变量的名称
  • ca_state(CHID):获取连接状态
  • ca_puser(CHID):获取你指定的PUSER

i) ARGS struct在连接回调中包含chid

j) 为了3.13兼容性也可以使用ca_search_and_connect()

4) ca_clear_channel

int ca_clear_channel(chid CHID);
  • 关闭一个通道访问并且回收资源。
  • 在退出这个程序前,应该被调用。
  • CHID与在ca_create_channel中使用相同的chid

5) ca_array_get

int ca_array_get(
    chtype TYPE,
    unsigned long COUNT,
    chid CHID,
    void * PVALUE
);

a) 从过程变量请求一个标量或者值的数组

b) 一般之后为ca_pend_io

c) TYPE是你变量的外部类型

  • 使用db_access.h中DBR_XXX类型之一
  • 例如:DBR_DOUBLE或DBR_STRING

d) COUNT:要读取的数组元素数目。

e) CHID:来自ca_create_channel的通道标识符。

f) PVALUE:你想要获取值存入的位置。必须有足够空间保存这些值。

6) ca_array_get_callback

typedef void (*pCallback)(struct event_handler_args ARGS);
int ca_array_get_callback(
    chtype TYPE,
    unsigned long COUNT,
    chid CHID,
    pCallback USERFUNC,
    void * USERARG
);

a) 使用一个回调,从一个过程变量请求一个标量或者值的数组。

b) TYPE:是你变量的外部类型。

  • 使用db_access.h中DBR_XXXX类型之一。
  • 例如:DBR_DOUBLE或DBR_STRING

c) COUNT:要读取的数组元素数目

d) CHID:是来自ca_create_channel的通道标识符。

e) USERFUNC:在操作结束时要被运行的你的回调的名称。

f) USERARGS:一种传递其它信息给这个回调的方式

  • struct event_handler_args有一个void * usr成员

7) ca_array_put

int ca_array_put(
    chtype TYPE,
    unsigned long COUNT,
    chid CHID,
    const void * PVALUE
);

a) 请求写一个标量或者值数组到一个过程变量

b) 一般之后为ca_pend_io

c) TYPE是你提供的外部类型

  • 使用db_access.h中DBR_XXXX类型之一
  • 例如:DBR_DOUBLE或DBR_STRING

d) COUNT是要写的数组元素的数目。

e) CHID是来自ca_create_channel的通道标识符。

f) PVALUE是从哪里寻找要被写的值。

8) ca_array_put_callback

typedef void (*pCallback)(struct event_handler_args ARGS);
int ca_array_put_callback(
    chtype TYPE,
    unsigned long COUNT,
    chid CHID,
    const void * PVALUE,
    pCallback USERFUNC,
    void * USERARG
);

a) 请求写一个标量或值数组到一个过程变量

b) TYPE是你变量的外部类型

  • 使用db_access.h中DBR_XXXX类型之一
  • 例如:DBR_DOUBLE或DBR_STRING

c) COUNT:要写的数组元素数目。

d)CHID:来自ca_create_channel的通道标识符。

e) PVALUE:查找要被写值的位置。

f) USERFUNC:当操作结束时,要被运行的你的回调的名称。

g) USERARG:一种传递其它信息给这个回调的方法。

  • struct event_handler_args有一个void * usr成员。

9) ca_create_subscription

typedef void (*pCallback)(struct event_handler_args ARGS);
int ca_create_subscriptino(
    chtype TYPE,
    unsigned long COUNT,
    chid CHID,
    unsigned long MASK,
    pCallback USERFUNC,
    void * USERARG,
    evid * PEVID
);

a) 指定一个回调函数,当这个过程变量经历显著状态变化时,被调用。

  • 值,警报状态,警报严重性
  • 这是监视一个过程变量的方法

b) TYPE是你想要返回的外部类型

  • 使用db_access.h中DBR_XXXX类型之一
  • 例如:DBR_DOUBLE或DBR_STRING

c) COUNT是要监视的数组元素数目

d) CHID是来自ca_create_channel的通道标识符。

e) MASK有对应于请求的事件触发类型的位集合

  • DBE_VALUE:值变化。
  • DBE_LOG:超过存档死区。
  • DBE_ALARM:警报状态变化

f) USERFUNC在状态变化发生时要被运行的你的回调的名称。

g) USERARG是一种传递其它信息给回调的方法。

  • struct event_handler_args有一个void *成员。

h) PEVID是一个evid(event id)的地址。

  • 在进行调用前你需要为evid分配空间
  • 类似chid
  • 仅用于清理订阅(如果不需要,可以是NULL)

10) ca_clear_subscription

int ca_clear_subscription(evid EVID);
  • 用于移除一个monitor回调
  • EVID是来自ca_create_subscription的evid

11)ca_add_exception_event

typedef void (* pCallback)(struct exception_handler_args ARGS);
int ca_add_exception_event(
    pCallback USERFUNC,
    void *USERARG
);

a)用于替代默认的exception处理程序。

b) USERFUNC是在一个异常发生时要被运行的你的回调的名称。

  • 使用NULL移除回调。

c) USERARG是一种传递其它信息给这个回调的方式。

  • struct exception_handler_args有一个void * usr成员

请求处理

1) 先前的例程是请求

  • 它们仅排队这个操作
  • 它们基本不出错。返回值基本总是ECA_NORMAL,但它们应该被检查。

2)仅在一下之一被调用时,这些请求才被处理。

  • ca_pend_io:在请求被处理前阻塞。
  • ca_pend_event:阻塞指定的时间。
  • ca_poll:仅处理当前工作。

3)如果这些例程没有被调用,请求不被处理并且后台任务也不被处理。

4)规则是这些之一应该每100ms被调用:允许后台任务运行(beacons等)。

1) ca_pend_io

int ca_pend_io(double TIMEOUT);

a)清空发送缓存。

b) 在以下发生前,最多阻塞TIMEOUT秒 

  • 待处理gets结束
  • 没有回调已经连接的搜索

c) 当gets和搜索结束时,返回ECA_NORMAL

d)返回ECA_TIMEOUT,否则

  • 表示某过程出错
  • get请求可以被再次发出
  • 在ca_clear_channel后,搜索请求可以被再次发出。

e) 通道访问后台任务被执行

  • 除非没有待处理的I/O请求

f) 与不使用回调的搜索,gets和puts一起使用。

2) ca_pend_event

int ca_pend_event(double TIMEOUT);

a) 清空发送缓存。

b) 在TIMEOUT秒内运行后台任务。

  • 在TIMEOUT秒耗尽时才返回。

c) 当你的程序不是必须做任何其它事情时使用这个函数。

d) 使用ca_pend_event替代sleep

3) ca_poll

int ca_poll();

a) 清空发送缓存。

b) 仅运行待处理任务。

  • 当没有待处理任务时退出,否则类似于ca_pend_event

c) 当你的程序有要做的其它事情时,使用这个函数。

  • 例如:大多数GUI程序。

d)确认它至少每100ms被调用。

4) CHID宏

chtype ca_field_type(CHID);
unsigned ca_element_count(CHID);
char * ca_name(CHID);
void * ca_puser(CHID);
void ca_set_puser(chid CHID, void * puser);
enum channel_state{
    cs_never_conn, //有效chid,服务器未找到或者不可用
    cs_prev_conn,  //有效chid,先前连接到服务器
    cs_conn,       //有效chid,连接了服务器
    cs_closed      //用户删除了通道
};
char * ca_host_name(CHID);
char * ca_read_access(CHID);
char * ca_write_access(CHID)

5) ca_connection_handler_args

struct ca_connection_handler_args{
    chidId chid; // 通道id
    long op;     // CA_OP_CONN_UP/CA_OP_CONN_DOWN
};
  • 在连接回调中使用
  • 注意:使用了chanId而不是chid,某些编译器不认chid chid
  •  

6) event_handler_args

typedef struct event_handler_args{
    void * usr;  // 提供给请求的用户参数
    chanId chid;  //通道ID
    long type;  //返回项的类型
    long count;   //返回项的元素数目
    const void * dbr; //指向返回项的指针
    int status;       //请求op的ECA_XXX状态
};
  • 在get, put和monitor回调中使用
  • 如果status不是ECA_NORMAL,不要使用dbr中的值。

7) 通道访问API函数

 

简单的CA客户端

1) 使用以下数据库文件,此数据库文件由两个记录组成一个wavefrom记录和一个stringin记录。

record(waveform, "$(USER):wfin") {
  field(DESC, "A Example Waveform")
  field(SCAN, "Passive")
  field(NELM, "10")
  field(FTVL, "LONG")
}

record(stringin, "$(USER):StrIn") {
  field(DESC, "A Example StringIn")
  field(SCAN, "Passive")
  field(VAL, "HelloWorld")
  field(PINI, "YES")
}

2)将以上数据库加载到一个IOC中产生一个两个记录实例:

epics> dbl
TEST:StrIn
TEST:wfin

3) 用EPICS base自带的通道访问命令测试以上两个记录:

orangepi@orangepi4-lts:~/host_program/host/hostApp$ caget TEST:StrIn
TEST:StrIn                     HelloEveryOne
orangepi@orangepi4-lts:~/host_program/host/hostApp$ caget TEST:wfin
TEST:wfin 10 1 2 3 4 5 6 7 8 9 10

4) 用以上讲解的通道访问API函数写一个自己的通道访问程序,源代码如下:

orangepi@orangepi4-lts:~/host_program/host/hostApp$ cat simpleget.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* EPICS header */
#include "cadef.h"
#define epicsAlarmGLOBAL
#include "epicsEvent.h"
#include "epicsMutex.h"

#define TIMEOUT         1.0
#define SCA_OK          1
#define SCA_ERR         0
#define MAX_STRING      40

int main(int argc, char **argv)
{
        int stat;
        chid pCh;
        char pvname[20] = {0};

        char svalue[100];
        int  ivalue[20];
        void * pvalue;

        if (argc != 2){
                printf("Usage: %s channelname\n", argv[0]);
                exit(1);
        }

        strcpy(pvname, argv[1]);
        printf("PVNAME: %s\n", pvname);

        /* Initialize channel access */
        stat = ca_context_create(ca_disable_preemptive_callback);

        if (stat != ECA_NORMAL){
                printf("ca_context_create failed:\n%s\n",ca_message(stat));
                exit(1);
        }
        else{
                printf("channel accesss initialized successfully\n");
        }

        /* create the pv */
        stat = ca_create_channel(pvname, NULL, NULL, CA_PRIORITY_DEFAULT, &pCh);

        if (stat != ECA_NORMAL){
                printf("ca_create_channel failed:\n%s\n", ca_message(stat));
                goto EXIT;
        }
        else{
                printf("PV for channel name %s created successfully\n", pvname);
        }


        /* call ca_pend_io to process the search */
        stat = ca_pend_io(TIMEOUT);
        if (stat != ECA_NORMAL)
        {
                printf("search for PV:[%s] timed out after %g sec", pvname, TIMEOUT);
                goto DESTROY;
        }

        /* Macro TEST */
        printf("MACRO TEST:\n");
        int request_type = ca_field_type(pCh);
        printf("ca_field_type(CHID): %d\n", request_type);
        long request_count = ca_element_count(pCh);
        printf("ca_element_count(CHID): %ld\n", request_count);
        printf("ca_name(CHID):%s\n", ca_name(pCh));
        printf("ca_state(CHID): %d\n", ca_state(pCh));
        printf("ca_host_name(CHID):%s\n",ca_host_name(pCh));
        printf("ca_read_access(CHID):%d\n", ca_read_access(pCh));
        printf("ca_write_access(CHID):%d\n", ca_write_access(pCh));

        printf("DBR_STRING: %d\n", DBR_STRING);
        printf("DBR_LONG: %d\n", DBR_LONG);

        /* Request Get */
        if (request_type == DBR_STRING){
                pvalue = svalue;
        }
        else if(request_type == DBR_LONG){
                pvalue = ivalue;
        }
        else{
                printf("NO SUPPORT TYPE: %d\n", request_type);
                goto DESTROY;
        }

        /* Request the get */
        stat = ca_array_get(request_type, request_count, pCh, pvalue);

        if (stat != ECA_NORMAL){
                printf("ca_array_get failed:\n%s\n", ca_message(stat));
                goto DESTROY;
        }

        /* call ca_pend_io to get the values */
        stat = ca_pend_io(TIMEOUT);
        if (stat != ECA_NORMAL){
                printf("get %s timed out after %g sec\n%s\n", pvname, TIMEOUT, ca_message(stat));
                goto DESTROY;
        }

        if (request_type == DBR_STRING){
                printf("PV[%s] ===> VAlUE[%s]\n", pvname, svalue);
        }
        else if (request_type == DBR_LONG){
                int i, ret = 0;
                for (i = 0; i < request_count; i++){
                        ret += sprintf(svalue + ret, "%d ", ivalue[i]);
                }

                printf("PV[%s] ==> VALUE[%s]\n", pvname, svalue);
        }

DESTROY:
        stat = ca_clear_channel(pCh);
        if (stat != ECA_NORMAL){
                printf("ca_clear_channel failed [%s] \n%s\n",pvname,ca_message(stat));
                goto EXIT;
        }

        /* clean up channel */
EXIT:   ca_context_destroy();
        printf("channel access context destroyed and exits the program\n");


        return 0;
}

编译以上程序代码,并且对以上记录执行,结果如下:

orangepi@orangepi4-lts:~/host_program/host/hostApp$ O.linux-aarch64/simpleget TEST:StrIn
PVNAME: TEST:StrIn
channel accesss initialized successfully
PV for channel name TEST:StrIn created successfully
MACRO TEST:
ca_field_type(CHID): 0
ca_element_count(CHID): 1
ca_name(CHID):TEST:StrIn
ca_state(CHID): 2
ca_host_name(CHID):192.168.50.184:5064
ca_read_access(CHID):1
ca_write_access(CHID):1
DBR_STRING: 0
DBR_LONG: 5
PV[TEST:StrIn] ===> VAlUE[HelloEveryOne]
channel access context destroyed and exits the program
orangepi@orangepi4-lts:~/host_program/host/hostApp$ O.linux-aarch64/simpleget TEST:wfin
PVNAME: TEST:wfin
channel accesss initialized successfully
PV for channel name TEST:wfin created successfully
MACRO TEST:
ca_field_type(CHID): 5
ca_element_count(CHID): 10
ca_name(CHID):TEST:wfin
ca_state(CHID): 2
ca_host_name(CHID):192.168.50.184:5064
ca_read_access(CHID):1
ca_write_access(CHID):1
DBR_STRING: 0
DBR_LONG: 5
PV[TEST:wfin] ==> VALUE[1 2 3 4 5 6 7 8 9 10 ]
channel access context destroyed and exits the program

通过以上实例程序编写和测试,可以进一步加强我们对EPICS通道访问函数的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值