/** * 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 #undef INLINE /* already defined bz tcl.h, breaks EPICS includes */ #include #include #include #include /* One of those: alarmString.h cannot be doubly included into the same application */ #include /* extern char *epicsAlarmConditionStrings[]; */ /* * 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) { pWriteMessage 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); } free(mes); } 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,TASK_PRIO_MED ); } 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(0.2); 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; char error[256]; priv = (pEpicsPriv)args.usr; if(args.status == ECA_NORMAL){ switch(priv->node->value.dataType){ case HIPTEXT: free(priv->node->value.v.text); if(args.type == DBR_STRING){ priv->node->value.v.text = strdup((char *)args.dbr); } else if(args.type == DBR_CHAR) { priv->node->value.v.text = strndup((char *)args.dbr,args.count); } 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; } 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); } } /*--------------------------------------------------------------------------------------*/ 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: if(ca_field_type(priv->pvchid) == DBR_CHAR){ subType = DBR_CHAR; } else { 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|DBE_ALARM,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]; enum channel_state cs; assert(priv != NULL); mm = GetHdbGetMessage(message); if (mm != NULL) { con = mm->callData; 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); 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)); connectPV(node,priv); SCSendOK(con); return 1; } /*============================================================================================== Writing Things. Writing can block, thus it has to run in its own thread. This raises the 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(pWriteMessage)); } } /*---------------------------------------------------------------------------------------------- To my surprise this is never called. May be, when the thread terminates anyway, epics does not know anymore that the callback existed or how to call it. ------------------------------------------------------------------------------------------------*/ 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(0.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"); snprintf(error,sizeof(error),"Writing data for PV %s", wp->pvName); writeEpicsMessage(NULL,error); ca_pend_io(0); goto cleanup; cleanup: if(wp->pCon != NULL){ SCDeleteConnection(wp->pCon); } ReleaseHdbValue(&wp->v); free(wp); free(pv); ca_clear_channel(cid); /* ca_context_destroy(); */ } /*----------------------------------------------------------------------------------------------*/ 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; } if(mm->callData != NULL){ 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 ================================================================================================*/ static int EpicsConvertAlarm(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { pHdb node = NULL; char value[256]; if(nPar < 1){ SCWrite(con,"ERROR: need alarm code to convert", eError); return 0; } snprintf(value,sizeof(value),"%s", epicsAlarmConditionStrings[par[0]->value.v.intValue]); SCWrite(con,value,eValue); return 1; } /*----------------------------------------------------------------------------------------------*/ 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(); writeQueue = epicsMessageQueueCreate(64,sizeof(pWriteMessage)); 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("")); child = AddSICSHdbPar(self->objectNode, "convertalarm", usSpy, MakeSICSFunc(EpicsConvertAlarm)); AddSICSHdbPar(child, "stat", usSpy, MakeHdbInt(1.)); AddCommand(pServ->pSics, "epicsadapter", InterInvokeSICSOBJ, KillSICSOBJ, self); return 1; }