#ifndef INCPVAttributeH
#define INCPVAttributeH
#include
#include
#include
#include "NDAttribute.h"
/** 对通道访问使用本地类型 */
#define DBR_NATIVE -1
/** 从EPICS PV获取它的值的属性 */
class ADCORE_API PVAttribute : public NDAttribute {
public:
PVAttribute(const char *pName, const char *pDescription, const char *pSource, chtype dbrType);
PVAttribute(PVAttribute& attribute);
~PVAttribute();
PVAttribute* copy(NDAttribute *pAttribute);
virtual int updateValue();
/* 这些回调必须是public的,因为从C调用它们 */
void connectCallback(struct connection_handler_args cha);
void monitorCallback(struct event_handler_args cha);
int report(FILE *fp, int details);
private:
chid chanId; // 通道标识符
evid eventId; // 事件标识符
chtype dbrType; // 这个对象的请求类型
NDAttrValue callbackValue; // 存储除字符串外的所有类型的值
char *callbackString; // 专用于存储字符串类型的值
bool connectedOnce;
epicsMutexId lock;
};
#endif /*INCPVAttributeH*/
#include
#include
#include
#include
#include "PVAttribute.h"
static const char *driverName = "PVAttribute";
// 用于存储上下文环境
static ca_client_context *pCaInputContext = NULL;
/*
struct connection_handler_args {
chanId chid; // 通道id
long op; //CA_OP_CONN_UP或CA_OP_CONN_DOWN之一
};
*/
static void connectCallbackC(struct connection_handler_args cha);
/*
* 这个asynUser未被连接到任何设备。通过设置全局asynTrace标记位ASTN_TRACE_ERROR(默认),ASYN_TRACE_FLOW
* 等,它让人开启调试。
*/
static asynUser *pasynUserSelf = NULL;
/** PV属性的构造器。
* [in] pName :要被创建的属性的名称;不区分大小写.
* [in] pDescription :这个属性的描述字符串.
* [in] pSource :用于获取这个属性值的EPICS PV的名称 .
* [in] dbrType :要被使用的EPICS DBR_XXX类型(DBR_STRING, DBR_DOUBLE等).
* 除了正常的DBR类型,可以使用一个特殊的DBR_NATIVE,这表示使用这个PV的通道访问返回的本地数据类型
*/
PVAttribute::PVAttribute(const char *pName, const char *pDescription,
const char *pSource, chtype dbrType)
: NDAttribute(pName, pDescription, NDAttrSourceEPICSPV, pSource, NDAttrUndefined, 0),
dbrType(dbrType), callbackString(0), connectedOnce(false) // 本对象的请求类型为传入的dbrType
{
static const char *functionName = "PVAttribute";
// pasynUserSelf为空,创建一个asynUser对象
if (!pasynUserSelf) pasynUserSelf = pasynManager->createAsynUser(0,0);
/* 如果pCaInputContext为空,创建一个抢占式上下文环境 */
if (pCaInputContext == NULL) {
SEVCHK(ca_context_create(ca_enable_preemptive_callback),"ca_context_create");
while (pCaInputContext == NULL) { // 用pCaInputContext指向当前上下文环境
epicsThreadSleep(epicsThreadSleepQuantum());
pCaInputContext = ca_current_context(); // 获取上下文环境
}
}
/*
* 因为这个方法可以在与创建这个上下文环境的线程不同线程中被调用,
* 所以需要调用ca_attach_context将当前线程连接pCaInputContext指向的上下文环境
*/
ca_attach_context(pCaInputContext); // 连接到上下文环境
this->lock = epicsMutexCreate(); // 创建互斥量
if (!pSource) { // EPICS PV的字符串名称为空
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s: ERROR, must specify source string\n",
driverName, functionName);
return;
}
/* 创建通道访问,需要指定通道名称字符串,连接回调函数,通道访问使用者,优先级,以及存储通道访问标识符的位置
对这个PV设置连接回调: PV通道名字符串
int ca_create_channel (const char *PVNAME, caCh *USERFUNC, void *PUSER, capri PRIORITY, chid *PCHID );
*/
SEVCHK(ca_create_channel(pSource, connectCallbackC, this, 10 ,&this->chanId),
"ca_create_channel");
}
/** EPICS PV属性的拷贝构造函数
* [in] attribute:从这个PVAttriubte对象复制.
* 注意:这个复制不是"功能的",即:在PV变化时,它不更新,值被冻结。
*/
PVAttribute::PVAttribute(PVAttribute& attribute)
: NDAttribute(attribute)
{
dbrType = attribute.dbrType;
eventId = 0;
chanId = 0;
lock = 0;
}
PVAttribute::~PVAttribute()
{
// 清理和释放与通道访问有关的资源
if (this->chanId) SEVCHK(ca_clear_channel(this->chanId),"ca_clear_channel");
if (this->lock) epicsMutexDestroy(this->lock);
}
PVAttribute* PVAttribute::copy(NDAttribute *pAttr)
{
PVAttribute *pOut = (PVAttribute *)pAttr;
if (!pOut)
pOut = new PVAttribute(*this);
else {
// 注意:我们假设如果这个属性名相同,则来源PV和dbrType也相同
NDAttribute::copy(pOut);
}
return(pOut);
}
//为了进行C调用
static void monitorCallbackC(struct event_handler_args cha)
{
PVAttribute *pAttribute = (PVAttribute *)ca_puser(cha.chid);
if (!pAttribute) return;
pAttribute->monitorCallback(cha);
}
/** 当一个EPICS PV更改值时,monitor回调被调用
* 调用NDAttribute::setValue来存储这个新值
* [in] eha :由通道访问传递的事件处理程序参数结构体
*/
/*
这是一个用户定义的回调函数。当一个通道有一个新值时,调用这个函数。
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;
*/
void PVAttribute::monitorCallback(struct event_handler_args eha)
{
//chid chanId = eha.chid;
NDAttrDataType_t dataType = this->getDataType();
const char *functionName = "monitorCallback";
epicsMutexLock(this->lock);
asynPrint(pasynUserSelf, ASYN_TRACE_FLOW,
"%s:%s: PV=%s\n",
driverName, functionName, this->getSource());
if (eha.status != ECA_NORMAL) {
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s: CA returns eha.status=%d\n",
driverName, functionName, eha.status);
goto done;
}
/* 、特殊处理字符串 */
if (dataType == NDAttrString) {
if (this->callbackString) free(this->callbackString);
this->callbackString = epicsStrDup((char *)eha.dbr);
goto done;
}
switch (dataType) {
case NDAttrInt8:
callbackValue.i8 = *(epicsInt8 *)eha.dbr;
break;
case NDAttrUInt8:
callbackValue.ui8 = *(epicsUInt8 *)eha.dbr;
break;
case NDAttrInt16:
callbackValue.i16 = *(epicsInt16 *)eha.dbr;
break;
case NDAttrUInt16:
callbackValue.ui16 = *(epicsUInt16 *)eha.dbr;
break;
case NDAttrInt32:
callbackValue.i32 = *(epicsInt32*)eha.dbr;
break;
case NDAttrUInt32:
callbackValue.ui32 = *(epicsUInt32 *)eha.dbr;
break;
case NDAttrFloat32:
callbackValue.f32 = *(epicsFloat32 *)eha.dbr;
break;
case NDAttrFloat64:
callbackValue.f64 = *(epicsFloat64 *)eha.dbr;
break;
case NDAttrUndefined:
break;
default:
break;
}
done:
epicsMutexUnlock(this->lock);
}
int PVAttribute::updateValue()
{
//static const char *functionName = "updateValue"
void *pValue;
NDAttrDataType_t dataType = this->getDataType();
epicsMutexLock(this->lock);
if (dataType == NDAttrString)
pValue = callbackString;
else
pValue = &callbackValue;
this->setValue(pValue);
epicsMutexUnlock(this->lock);
return asynSuccess;
}
/*
struct connection_handler_args {
chanId chid; // 通道id
long op; //CA_OP_CONN_UP或CA_OP_CONN_DOWN之一
};
*/
static void connectCallbackC(struct connection_handler_args cha)
{
// ca_puser获取使用这个通道标识符的用户
PVAttribute *pAttribute = (PVAttribute *)ca_puser(cha.chid);
if (!pAttribute) return;
// 调用PVAttriubte对象的连接回调函数
pAttribute->connectCallback(cha);
}
/**
* 当一个EPICS PV连接或者断开连接时,连接回调被调用。如果它是一个连接事件,它调用
* ca_add_masked_array_event请求在值变化时时回调。
* [in] cha:由通道访问传递的连接处理程序参数结构体。
*/
void PVAttribute::connectCallback(struct connection_handler_args cha)
{
chid chanId = cha.chid; // 获取通道标识符
const char *functionName = "connectCallback";
chtype dbfType, dbrType=this->dbrType; // 获取这个对象的请求类型
int nRequest=1;
int elementCount;
NDAttrDataType_t dataType;
epicsMutexLock(this->lock);
if (chanId && (ca_state(chanId) == cs_conn)) { // 通道标识符不为0,并且这个通道状态为连接
dbfType = ca_field_type(chanId); // 获取通道的字段类型
if (connectedOnce) {// connectedOnce初始化为false,如果已经为true表示已经连接过了
goto done;
}
connectedOnce = true;
elementCount = ca_element_count(chanId); // 获取通道的元素数目
asynPrint(pasynUserSelf, ASYN_TRACE_FLOW, // 输出调试信息:驱动名,函数名,PV名称字符串,通道标识符的指针,PV的类型
// PV的元素数目,请求类型
"%s:%s: Connect event, PV=%s, chanId=%p, dbfType=%ld, elementCount=%d, dbrType=%ld\n",
driverName, functionName, this->getSource(), chanId, dbfType, elementCount, dbrType);
switch(dbfType) {// 通道的本地类型
case DBF_STRING: // 如果这个对象的请求类型是通道本地类型,则根据通道本地类型设置请求类型
if (this->dbrType == DBR_NATIVE) dbrType = DBR_STRING;
break;
case DBF_SHORT: /* Note: DBF_INT同DBF_SHORT */
if (this->dbrType == DBR_NATIVE) dbrType = DBR_SHORT;
break;
case DBF_FLOAT:
if (this->dbrType == DBR_NATIVE) dbrType = DBR_FLOAT;
break;
case DBF_ENUM:
if (this->dbrType == DBR_NATIVE) dbrType = DBR_SHORT;
break;
case DBF_CHAR:
if (this->dbrType == DBR_NATIVE) dbrType = DBR_CHAR;
/* 如果本地类型是DBF_CHAR,但请求类型是DBR_STRING,读取一个字符数组并且当成字符串 */
if (this->dbrType == DBR_STRING) {
dbrType = DBR_CHAR;
nRequest = elementCount;
}
break;
case DBF_LONG:
if (this->dbrType == DBR_NATIVE) dbrType = DBR_LONG;
break;
case DBF_DOUBLE:
if (this->dbrType == DBR_NATIVE) dbrType = DBR_DOUBLE;
break;
default:
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s: unknown DBF type = %ld\n",
driverName, functionName, dbfType);
goto done;
}
switch(dbrType) {//根据请求类型,设置属性的数据类型
case DBR_STRING:
dataType = NDAttrString;
break;
case DBR_SHORT:
dataType = NDAttrInt16;
break;
case DBR_FLOAT:
dataType = NDAttrFloat32;
break;
case DBR_ENUM:
dataType = NDAttrInt16;
break;
case DBR_CHAR:
dataType = NDAttrInt8;
/* 如果本地类型是DBF_CHAR,但请求类型是DBR_STRING,读取一个字符数组并且当成字符串 */
if (this->dbrType == DBR_STRING) dataType = NDAttrString;
break;
case DBR_LONG:
dataType = NDAttrInt32;
break;
case DBR_DOUBLE:
dataType = NDAttrFloat64;
break;
default:
asynPrint(pasynUserSelf, ASYN_TRACE_ERROR,
"%s:%s: unknown DBR type = %ld\n",
driverName, functionName, dbrType);
goto done;
}
asynPrint(pasynUserSelf, ASYN_TRACE_FLOW,// 调试信息:驱动名,函数名,PV名称,通道标识符地址,属性的数据类型
"%s:%s: Connect event, PV=%s, chanId=%p, type=%d\n",
driverName, functionName, this->getSource(), chanId, dataType);
this->setDataType(dataType);// 设置属性值的类型
/* 对这个PV设置值变化回调
int ca_add_masked_array_event (
chtype TYPE,
unsigned long COUNT,
chid CHID,
void (*USERFUNC)(struct event_handler_args ARGS),
void *USERARG,
float 0, //保留它们为了将来使用。如果你指定0.0,你的代码将保持向以后兼容。
float 0,
float 0,
evid *PEVID,
unsigned long MASK
);
*/
SEVCHK(ca_add_masked_array_event(
dbrType,
nRequest,
this->chanId,
monitorCallbackC,
this,
0.0,0.0,0.0,
&this->eventId, DBE_VALUE),"ca_add_masked_array_event"); //指定监视值变化
} else {
/* 这个一个断开连接的事件 */
asynPrint(pasynUserSelf, ASYN_TRACE_FLOW,
"%s:%s: Disconnect event, PV=%s, chanId=%p\n",
driverName, functionName, this->getSource(), chanId);
}
done:
epicsMutexUnlock(this->lock);
}
/** Reports on the properties of the PVAttribute object;
* calls base class NDAttribute::report() to report on the parameter value.
* \param[in] fp File pointer for the report output.
* \param[in] details Level of report details desired; currently does nothing in this derived class.
*/
int PVAttribute::report(FILE *fp, int details)
{
NDAttribute::report(fp, details);
fprintf(fp, " PVAttribute\n");
fprintf(fp, " dbrType=%s\n", dbr_type_to_text(this->dbrType));
fprintf(fp, " chanId=%p\n", this->chanId);
fprintf(fp, " eventId=%p\n", this->eventId);
return(ND_SUCCESS);
}
这里测试所用IOC程序是Motor模块中modules/motorMotorSim虚拟电机程序,这个程序运行后EPICS IOC下将有8个虚拟电机轴,名称从IOC:m1~IOC:m8。
以下是测试程序,这里仅使用了IOC:m1这个电机轴:
#include
#include
#include
#include "epicsThread.h"
#include "epicsString.h"
#include "PVAttribute.h"
int main()
{
const char * motorNames[2] = {"motor1_VAL", "motor1_RBV"};
const char * pSources[2] = {"IOC:m1", "IOC:m1.RBV"};
const char * motorDescription[2] = {"motor set value", "motor readback value"};
double value;
PVAttribute * pAttrs[2];
int i, j;
for (i = 0; i < 2; i++){
pAttrs[i] = new PVAttribute(motorNames[i], motorDescription[i], pSources[i], DBR_NATIVE );
epicsThreadSleep(0.1);
pAttrs[i]->updateValue();
printf("Report the information of %s:\n", motorNames[i]);
pAttrs[i]->report(stdout, 10);
}
for (i = 0; i < 10; i++){
epicsThreadSleep(0.1);
for (j = 0; j < 2; j++){
pAttrs[j]->updateValue();
pAttrs[j]->getValue(NDAttrFloat64, &value);
printf("%s: %lf\t", motorNames[j], value);
}
printf("\n");
}
return 0;
}
测试结果如下:
root@orangepi5:/home/orangepi/C_program/host_program/hostApp# caput IOC:m1 1; O.linux-aarch64/testPVAttribute
Old : IOC:m1 0
New : IOC:m1 1
Report the information of motor1_VAL:
NDAttribute, address=0x55ca357b40:
name=motor1_VAL
description=motor set value
source type=2
source type string=NDAttrSourceEPICSPV
source=IOC:m1
dataType=NDAttrFloat64
value=1.000000
PVAttribute
dbrType=DBR_invalid
chanId=0x55ca35dd10
eventId=0x7f8c000b70
Report the information of motor1_RBV:
NDAttribute, address=0x55ca37eca0:
name=motor1_RBV
description=motor readback value
source type=2
source type string=NDAttrSourceEPICSPV
source=IOC:m1.RBV
dataType=NDAttrFloat64
value=0.190000
PVAttribute
dbrType=DBR_invalid
chanId=0x55ca35dd48
eventId=0x7f8c000b98
motor1_VAL: 1.000000 motor1_RBV: 0.290000
motor1_VAL: 1.000000 motor1_RBV: 0.390000
motor1_VAL: 1.000000 motor1_RBV: 0.490000
motor1_VAL: 1.000000 motor1_RBV: 0.590000
motor1_VAL: 1.000000 motor1_RBV: 0.690000
motor1_VAL: 1.000000 motor1_RBV: 0.790000
motor1_VAL: 1.000000 motor1_RBV: 0.890000
motor1_VAL: 1.000000 motor1_RBV: 0.970000
motor1_VAL: 1.000000 motor1_RBV: 1.000000
motor1_VAL: 1.000000 motor1_RBV: 1.000000