diff --git a/epicsadapter.c b/epicsadapter.c new file mode 100644 index 0000000..57f31bb --- /dev/null +++ b/epicsadapter.c @@ -0,0 +1,273 @@ +/** +* This is a general purpose adapter between SICS and EPICS. It provides callbacks +* which allows Hipadaba nodes to be connected to EPICS PV's for both reading and +* writing. +* +* copyright: see file COPYRIGHT +* +* Mark Koennecke, October-November 2014 +*/ +#include +#include +#include +#include +#include +#include + +/* +* we have a SICS tasks which polls EPICS regularly. +*/ +static long epicsTaskID = -1L; +static pMP readPipe = NULL; +static epicsMessageQueueId writeQueue; + + +typedef struct { + char pvname[64]; + int connected; + pHdb node; + chid pvchid; + chtype pvtype; +}EpicsPriv, *pEpicsPriv; + +typedef struct { + SConnection *pCon; + char message[512]; +} WriteMessage, *pWriteMessage; + +/*====================================================================================== + Code for reading PV's + ======================================================================================*/ +/*--------------------------------------------------------------------------------------*/ +static int EpicsTask(void *userData) +{ + ca_poll(); + return 1; +} +/*---------------------------------------------------------------------------------------*/ +static int makeEPICSContext(void *message, void *userData) +{ + int status; + pEpicsPriv priv = NULL; + + if(epicsTaskID < 0){ + status = ca_context_create(ca_disable_preemptive_callback); + if(status != ECA_NORMAL){ + priv = (pEpicsPriv)message; + SetHdbProperty(priv->node,"geterror", "Failed to create EPICS context"); + return MPSTOP; + } + epicsTaskID = TaskRegisterN(pServ->pTasker, + "epics", + EpicsTask, + NULL,NULL,NULL,1 + ); + } + return MPCONTINUE; +} +/*--------------------------------------------------------------------------------------*/ +static int epicsConnectPV(void *message, void *userData) +{ + pEpicsPriv priv = NULL; + int status; + + priv = (pEpicsPriv)message; + + status = ca_create_channel(priv->pvname,NULL,NULL,10,&priv->pvchid); + if(status != ECA_NORMAL){ + SetHdbProperty(priv->node,"geterror", "Failed to connect to PV"); + return MPSTOP; + } + status = ca_pend_io(3.); + if(status != ECA_NORMAL){ + SetHdbProperty(priv->node,"geterror", "Timeout connecting to PV"); + return MPSTOP; + } + return MPCONTINUE; +} +/*--------------------------------------------------------------------------------------*/ +static void epicsDataCallback(struct event_handler_args args) +{ + pEpicsPriv priv = NULL; + hdbValue v; + + priv = (pEpicsPriv)args.usr; + + if(args.status == ECA_NORMAL){ + switch(priv->node->value.dataType){ + case HIPTEXT: + free(priv->node->value.v.text); + priv->node->value.v.text = strdup((char *)args.dbr); + break; + case HIPINT: + priv->node->value.v.intValue = *(int *)args.dbr; + break; + case HIPFLOAT: + priv->node->value.v.doubleValue = *(double *)args.dbr; + break; + case HIPINTAR: + case HIPINTVARAR: + v = MakeHdbIntArray(args.count,(int *)args.dbr); + copyHdbValue(&v,&priv->node->value); + break; + case HIPFLOATAR: + case HIPFLOATVARAR: + v = MakeHdbFloatArray(args.count,(double *)args.dbr); + copyHdbValue(&v,&priv->node->value); + break; + } + traceIO("epics","Received data for %s", priv->node->name); + NotifyHipadabaPar(priv->node,NULL); + } +} +/*--------------------------------------------------------------------------------------*/ +static int epicsSubscribePV(void *message, void *userData) +{ + pEpicsPriv priv = NULL; + int status; + chtype subType = DBR_STRING; + evid eid; + + priv = (pEpicsPriv)message; + switch(priv->node->value.dataType){ + case HIPTEXT: + subType = DBR_STRING; + break; + case HIPINT: + case HIPINTAR: + case HIPINTVARAR: + subType = DBR_LONG; + break; + case HIPFLOAT: + case HIPFLOATAR: + case HIPFLOATVARAR: + subType = DBR_DOUBLE; + break; + } + + status = ca_create_subscription(subType,0,priv->pvchid, + DBE_VALUE,epicsDataCallback,priv,&eid); + if(status != ECA_NORMAL){ + SetHdbProperty(priv->node,"geterror", "Failed to subscribe to PV"); + return MPSTOP; + } + + + return MPCONTINUE; +} +/*--------------------------------------------------------------------------------------*/ +static void createEPICSReadPipe() +{ + readPipe = MakeMP(); + AppendMPFilter(readPipe,makeEPICSContext,NULL,NULL); + AppendMPFilter(readPipe,epicsConnectPV,NULL,NULL); + AppendMPFilter(readPipe,epicsSubscribePV,NULL,NULL); +} +/*--------------------------------------------------------------------------------------*/ +static void connectPV(pHdb node, pEpicsPriv priv) +{ + int status; + + SetHdbProperty(node,"geterror", NULL); + priv->node = node; + status = MPprocess(readPipe, priv); + if(status == MPCONTINUE){ + priv->connected = 1; + } +} +/*-------------------------------------------------------------------------------------- + This is the Hipadaba callback function + --------------------------------------------------------------------------------------*/ + static hdbCallbackReturn EPICSReadCallback(pHdb currentNode, + void *userData, + pHdbMessage message) + { + pEpicsPriv priv = (pEpicsPriv)userData; + hdbDataMessage *mm = NULL; + SConnection *con = NULL; + char *geterror; + char error[256]; + + assert(priv != NULL); + + mm = GetHdbGetMessage(message); + if (mm != NULL) { + con = mm->callData; + + if(priv->connected != 1){ + connectPV(currentNode, priv); + } + geterror = GetHdbProp(currentNode, "geterror"); + if (geterror != NULL) { + snprintf(error,sizeof(error),"ERROR: %s", geterror); + SCWrite(con, error, eError); + if (mm->v->dataType == HIPTEXT) { + if (mm->v->v.text != NULL) { + free(mm->v->v.text); + } + mm->v->v.text = strdup(error); + } + return hdbAbort; + } + return hdbContinue; + } + + return hdbContinue; + } + /*------------------------------------------------------------------------------------*/ + static int EpicsConnectRead(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; + } + + priv = calloc(1,sizeof(EpicsPriv)); + priv->node = node; + strncpy(priv->pvname,par[1]->value.v.text,sizeof(priv->pvname)); + SetHdbProperty(node,"readpv", par[1]->value.v.text); + AppendHipadabaCallback(node,MakeHipadabaCallback(EPICSReadCallback, priv,free)); + + SCSendOK(con); + + return 1; + } +/*============================================================================================== + 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 + 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. +================================================================================================*/ + + +/*============================================================================================== + SICS Hydraulics +================================================================================================*/ + +int MakeEpicsAdapter(SConnection * con, SicsInterp * sics, + void *object, int argc, char *argv[]) +{ + pSICSOBJ self = NULL; + pHdb child, par; + + self = MakeSICSOBJv("epicsadapter", "EpicsAdapter", HIPNONE, usMugger); + createEPICSReadPipe(); + + child = AddSICSHdbPar(self->objectNode, + "connectread", usMugger, MakeSICSFunc(EpicsConnectRead)); + AddSICSHdbPar(child, "node", usMugger, MakeHdbText("")); + AddSICSHdbPar(child, "pvname", usMugger, MakeHdbText("")); + + AddCommand(pServ->pSics, "epicsadapter", InterInvokeSICSOBJ, KillSICSOBJ, self); + return 1; +} diff --git a/psi.c b/psi.c index cec469c..ec24329 100644 --- a/psi.c +++ b/psi.c @@ -130,6 +130,7 @@ static void AddPsiCommands(SicsInterp * pInter) SCMD("MakeEiger", InitEiger); SCMD("MakeEigerMono", InitEigerMono); PCMD("cnvrt", CnvrtAction); + SCMD("MakeEpicsAdapter", MakeEpicsAdapter); /* * Tcl 8.5 has implemented the clock command in tcl rather then C.