From dadb71245a8a384295a28265fbb7b17829e811bc Mon Sep 17 00:00:00 2001 From: Mark Koennecke Date: Mon, 27 Oct 2014 12:14:56 +0100 Subject: [PATCH] First working version of the epics SICS adapter --- epicsadapter.c | 189 +++++++++++++++++++++++++++++++++++++++++++++++-- make_gen | 3 +- makefile_linux | 2 +- 3 files changed, 188 insertions(+), 6 deletions(-) diff --git a/epicsadapter.c b/epicsadapter.c index 57f31bb..204c21a 100644 --- a/epicsadapter.c +++ b/epicsadapter.c @@ -5,13 +5,14 @@ * * copyright: see file COPYRIGHT * -* Mark Koennecke, October-November 2014 +* Mark Koennecke, October 2014 */ #include #include #include #include #include +#include #include /* @@ -38,10 +39,26 @@ typedef struct { /*====================================================================================== Code for reading PV's ======================================================================================*/ -/*--------------------------------------------------------------------------------------*/ static int EpicsTask(void *userData) { + WriteMessage mes; + + /* + drive the main EPICS loop for subscriptions + */ ca_poll(); + + /* + process possible messages from the writing threads + */ + if(epicsMessageQueueTryReceive(writeQueue,&mes,sizeof(mes)) > 0){ + if(mes.pCon == NULL){ + traceIO("epics",mes.message); + } else { + SCWrite(mes.pCon,mes.message,eError); + SCDeleteConnection(mes.pCon); + } + } return 1; } /*---------------------------------------------------------------------------------------*/ @@ -90,6 +107,7 @@ static void epicsDataCallback(struct event_handler_args args) { pEpicsPriv priv = NULL; hdbValue v; + char error[256]; priv = (pEpicsPriv)args.usr; @@ -116,8 +134,13 @@ static void epicsDataCallback(struct event_handler_args args) copyHdbValue(&v,&priv->node->value); break; } + SetHdbProperty(priv->node,"geterror",NULL); traceIO("epics","Received data for %s", priv->node->name); NotifyHipadabaPar(priv->node,NULL); + } else { + snprintf(error,sizeof(error),"ERROR: %s for node %s", ca_message(args.status), priv->node->name); + traceIO("epics",error); + SetHdbProperty(priv->node,"geterror",error); } } /*--------------------------------------------------------------------------------------*/ @@ -146,7 +169,7 @@ static int epicsSubscribePV(void *message, void *userData) } status = ca_create_subscription(subType,0,priv->pvchid, - DBE_VALUE,epicsDataCallback,priv,&eid); + DBE_VALUE|DBE_ALARM,epicsDataCallback,priv,&eid); if(status != ECA_NORMAL){ SetHdbProperty(priv->node,"geterror", "Failed to subscribe to PV"); return MPSTOP; @@ -187,6 +210,7 @@ static void connectPV(pHdb node, pEpicsPriv priv) SConnection *con = NULL; char *geterror; char error[256]; + enum channel_state cs; assert(priv != NULL); @@ -197,6 +221,11 @@ static void connectPV(pHdb node, pEpicsPriv priv) if(priv->connected != 1){ connectPV(currentNode, priv); } + cs = ca_state(priv->pvchid); + if(cs != cs_conn){ + SCWrite(con,"ERROR: epics disconnected", eError); + return hdbAbort; + } geterror = GetHdbProp(currentNode, "geterror"); if (geterror != NULL) { snprintf(error,sizeof(error),"ERROR: %s", geterror); @@ -244,11 +273,157 @@ static void connectPV(pHdb node, pEpicsPriv priv) } /*============================================================================================== Writing Things. Writing can block, thus it has to run in its own thread. This raises the - question is how to propagate error messages. The solution is a EPICS message queue to which + question how to propagate error messages. The solution is a EPICS message queue to which writing threads post. The epics task will read this queue and do the actual printing in the SICS main thread. A convention: NULL means to print to trace. ================================================================================================*/ +typedef struct { + SConnection *pCon; + hdbValue v; + char pvName[64]; +} WritePar, *pWritePar; +/*----------------------------------------------------------------------------------------------*/ +static void writeEpicsMessage(void *target, char *txt) +{ + pWriteMessage wm = NULL; + wm = calloc(1,sizeof(WriteMessage)); + if(wm != NULL){ + if(target != NULL){ + wm->pCon = SCCopyConnection(target); + } + strncpy(wm->message,txt,sizeof(wm->message)); + epicsMessageQueueSend(writeQueue,wm,sizeof(WriteMessage)); + } +} +/*----------------------------------------------------------------------------------------------*/ +static void epicsEndCallback(struct event_handler_args args) +{ + char message[512]; + snprintf(message,sizeof(message),"%s finished with %s", (char *)args.usr, + ca_message(args.status)); + writeEpicsMessage(NULL,message); + free(args.usr); +} +/*----------------------------------------------------------------------------------------------*/ +static void EpicsWriteFunc(void *param) +{ + pWritePar wp = (pWritePar)param; + pWriteMessage wm = NULL; + int status; + chid cid; + char error[512]; + char *pv; + + status = ca_context_create(ca_disable_preemptive_callback); + if(status != ECA_NORMAL){ + writeEpicsMessage(wp->pCon,"ERROR: failed to create EPICS context for write"); + goto cleanup; + } + status = ca_create_channel(wp->pvName,NULL,NULL,10,&cid); + if(status != ECA_NORMAL){ + snprintf(error,sizeof(error),"ERROR: failed to create EPICS channel for %s", wp->pvName); + writeEpicsMessage(wp->pCon,error); + goto cleanup; + } + status = ca_pend_io(5.); + if(status != ECA_NORMAL){ + snprintf(error,sizeof(error),"ERROR: failed to connect EPICS channel for %s", wp->pvName); + writeEpicsMessage(wp->pCon,error); + goto cleanup; + } + + pv = strdup(wp->pvName); + switch(wp->v.dataType){ + case HIPINT: + status = ca_put_callback(DBR_LONG,cid,&wp->v.v.intValue,epicsEndCallback,pv); + break; + case HIPFLOAT: + status = ca_put_callback(DBR_DOUBLE,cid,&wp->v.v.doubleValue,epicsEndCallback,pv); + break; + case HIPTEXT: + status = ca_put_callback(DBR_STRING,cid,wp->v.v.text,epicsEndCallback, pv); + break; + case HIPINTVARAR: + case HIPINTAR: + status = ca_array_put_callback(DBR_LONG,wp->v.arrayLength, + cid, wp->v.v.intArray,epicsEndCallback,pv); + break; + case HIPFLOATVARAR: + case HIPFLOATAR: + status = ca_array_put_callback(DBR_DOUBLE,wp->v.arrayLength, + cid, wp->v.v.floatArray,epicsEndCallback,pv); + break; + } + if(status != ECA_NORMAL){ + snprintf(error,sizeof(error),"ERROR: failed to write to EPICS channel for %s with %d", wp->pvName, status); + writeEpicsMessage(wp->pCon,error); + goto cleanup; + } + writeEpicsMessage(wp->pCon,"OK"); + + ca_pend_io(0); + + goto cleanup; + cleanup: + SCDeleteConnection(wp->pCon); + ReleaseHdbValue(&wp->v); + free(wp); +} +/*----------------------------------------------------------------------------------------------*/ +static hdbCallbackReturn EPICSWriteCallback(pHdb currentNode, + void *userData, + pHdbMessage message) +{ + hdbDataMessage *mm = NULL; + pWritePar par = NULL; + + mm = GetHdbSetMessage(message); + if(mm != NULL){ + par = calloc(1,sizeof(WritePar)); + if(par == NULL){ + SCWrite(mm->callData,"ERROR: out of memory in EPICSWriteCallback", eError); + return hdbAbort; + } + par->pCon = SCCopyConnection(mm->callData); + cloneHdbValue(mm->v,&par->v); + strncpy(par->pvName,(char *)userData,sizeof(par->pvName)); + epicsThreadCreate("Write", + epicsThreadPriorityHigh, + epicsThreadStackMedium, + EpicsWriteFunc, + par); + } + + return hdbContinue; +} +/*------------------------------------------------------------------------------------*/ + static int EpicsConnectWrite(pSICSOBJ ccmd, SConnection * con, + Hdb * cmdNode, Hdb * par[], int nPar) + { + pHdb node = NULL; + pEpicsPriv priv = NULL; + + if(nPar < 2){ + SCWrite(con,"ERROR: need node and PV-name arguments to connectread", eError); + return 0; + } + + node = FindHdbNode(NULL,par[0]->value.v.text,con); + if(node == NULL){ + SCPrintf(con,eError,"ERROR: failed to locate node %s", par[0]->value.v.text); + return 0; + } + + SetHdbProperty(node,"writepv", par[1]->value.v.text); + RemoveSetUpdateCallback(node); + AppendHipadabaCallback(node,MakeHipadabaCallback(EPICSWriteCallback, + strdup(par[1]->value.v.text),free)); + + SCSendOK(con); + + return 1; + } /*============================================================================================== SICS Hydraulics @@ -262,12 +437,18 @@ int MakeEpicsAdapter(SConnection * con, SicsInterp * sics, self = MakeSICSOBJv("epicsadapter", "EpicsAdapter", HIPNONE, usMugger); createEPICSReadPipe(); + writeQueue = epicsMessageQueueCreate(64,sizeof(WriteMessage)); child = AddSICSHdbPar(self->objectNode, "connectread", usMugger, MakeSICSFunc(EpicsConnectRead)); AddSICSHdbPar(child, "node", usMugger, MakeHdbText("")); AddSICSHdbPar(child, "pvname", usMugger, MakeHdbText("")); + child = AddSICSHdbPar(self->objectNode, + "connectwrite", usMugger, MakeSICSFunc(EpicsConnectWrite)); + AddSICSHdbPar(child, "node", usMugger, MakeHdbText("")); + AddSICSHdbPar(child, "pvname", usMugger, MakeHdbText("")); + AddCommand(pServ->pSics, "epicsadapter", InterInvokeSICSOBJ, KillSICSOBJ, self); return 1; } diff --git a/make_gen b/make_gen index c57cf98..0d13ce5 100644 --- a/make_gen +++ b/make_gen @@ -25,7 +25,8 @@ OBJ=psi.o buffer.o ruli.o sps.o pimotor.o charbychar.o\ rebin.o sanslirebin.o lmd200.o slsvme.o julprot.o sinqhttpprot.o \ pmacprot.o pfeifferprot.o termprot.o phytron.o autowin.o eigera2.o \ tclClock.o tclDate.o tclUnixTime.o jvlprot.o \ - eigermono.o sputterprot.o zwickroll.o astriumnet.o poldifold.o + eigermono.o sputterprot.o zwickroll.o astriumnet.o poldifold.o \ + epicsadapter.o .SECONDARY.: sanslirebin.c diff --git a/makefile_linux b/makefile_linux index 100ea6a..1a0d19d 100644 --- a/makefile_linux +++ b/makefile_linux @@ -13,7 +13,7 @@ include $(SICSROOT)/sics/sllinux_def CC = gcc -CFLAGS = -I$(HDFROOT)/include -DHDF4 -DHDF5 $(NI) -I$(TCLINC) -Ihardsup \ +CFLAGS = -I$(HDFROOT)/include -I$(HDFROOT)/include/os/Linux -DHDF4 -DHDF5 $(NI) -I$(TCLINC) -Ihardsup \ -I$(SICSROOT)/sics -I.. -I. -MMD -DCYGNUS -DNONINTF $(DBG) \ $(DFORTIFY) -Wall -Wno-unused -Wunused-value -Wno-comment \ -Wno-switch -Werror