/* This is a histogram memory driver for the 2005-6 version of the histogram memory software based on RTAI-Linux and an embedded WWW-server for communications. For all http work the ghttp library from the gnome project is used. This HM is meant to be used in conjunction with a counter module chained through the hmcontrol module. No need to handle counters here when hmcontrol can do the chaining. copyright: see file COPYRIGHT Mark Koennecke, January 2005 (original SINQ version) Mark Lesha, 9 October 2006 (ANSTO version) ----------------------------------------------------------------------*/ #include #include #include #include #include #include #include extern char *trim(char *); /*=================================================================== The request strings to append to the computer address ====================================================================*/ static char startdaq[] = {"/admin/startdaq.egi"}; static char stopdaq[] = {"/admin/stopdaq.egi"}; static char pausedaq[] = {"/admin/pausedaq.egi"}; static char continuedaq[] = {"/admin/continuedaq.egi"}; static char statusdaq[] = {"/admin/textstatus.egi"}; static char gethm[] = {"/admin/readhmdata.egi"}; static char configure[] = {"/admin/configure.egi"}; static char preset[] = {"/admin/presethm.egi"}; /*==================================================================== error codes ======================================================================*/ #define BADURL -701 #define HTTPERROR -702 #define NOBODY -703 #define BODYSHORT -704 #define NOTIMPLEMENTED -705 #define SERVERERROR -706 #define BADSTATUS -707 #define BADAUTH -708 /*===================================================================== our driver private data structure ======================================================================*/ typedef struct { ghttp_request *syncRequest; char hmAddress[512]; char userName[132]; char passWord[132]; char hmError[512]; int errorCode; int pause; int failCount; int asyncRunning; }anstoHttp, *pAnstoHttp; /*------------------------------------------------------------------*/ static int anstoHttpGetPrepare(pAnstoHttp self, char *request){ char url[512]; ghttp_status httpStatus; if(self->asyncRunning){ while((httpStatus = ghttp_process(self->syncRequest)) == ghttp_not_done){ } self->asyncRunning = 0; } self->errorCode = 0; ghttp_clean(self->syncRequest); memset(self->hmError,0,512*sizeof(char)); snprintf(url,511,"%s%s",self->hmAddress,request); ghttp_set_type(self->syncRequest,ghttp_type_get); ghttp_set_header(self->syncRequest,"connection","keep-alive"); if(ghttp_set_uri(self->syncRequest,url) < 0){ self->errorCode = BADURL; return 0; } ghttp_set_authinfo(self->syncRequest,self->userName, self->passWord); ghttp_set_sync(self->syncRequest,ghttp_sync); return 1; } /*-------------------------------------------------------------------*/ static int anstoHttpCheckResponse(pAnstoHttp self){ char *pPtr = NULL; int len; self->failCount = 0; pPtr = ghttp_get_body(self->syncRequest); if(pPtr == NULL) // MJL check ghttp_get_body for NULL return return 1; // empty response is okay len = ghttp_get_body_len(self->syncRequest); if(len > 511){ len = 510; } if(strstr(pPtr,"ERROR") != NULL){ memset(self->hmError,0,512*sizeof(char)); strncpy(self->hmError,pPtr, len); self->errorCode = HTTPERROR; return 0; } else if(strstr(pPtr,"Authentication Error") != NULL){ memset(self->hmError,0,512*sizeof(char)); strncpy(self->hmError,pPtr, len); self->errorCode = BADAUTH; return 0; } return 1; } /*-------------------------------------------------------------------*/ static int anstoHttpGet(pAnstoHttp self, char *request){ ghttp_status httpStatus; char *pPtr = NULL; if(!anstoHttpGetPrepare(self,request)){ return 0; } /* * try two times: a reconnect is no error */ ghttp_prepare(self->syncRequest); httpStatus = ghttp_process(self->syncRequest); if(httpStatus != ghttp_done){ ghttp_close(self->syncRequest); anstoHttpGetPrepare(self,request); httpStatus = ghttp_process(self->syncRequest); } if(httpStatus != ghttp_done){ strncpy(self->hmError,"Reconnect", 511); self->errorCode = SERVERERROR; return 0; } else { int crstat=anstoHttpCheckResponse(self); return crstat; } return 1; } /*====================================================================*/ static int AnstoHttpConfigure(pHistDriver self, SConnection *pCon, pStringDict pOpt, SicsInterp *pSics){ char hmname[512]; char confCommand[512], url[512]; pAnstoHttp pPriv = NULL; int status, iInit; float fVal; char *confData = NULL; ghttp_status httpStatus; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); ///SCWrite(pCon,"In AnstoHttpConfigure",eError); // MJL DEBUG // Check for FAT value sets /* * The HM computer address */ if(StringDictGet(pOpt,"hmaddress",pPriv->hmAddress, 511) != 1){ SCWrite(pCon, "ERROR: required configuration parameter hmaddress not found", eError); return 0; } /* * looser credentials */ if(StringDictGet(pOpt,"username",pPriv->userName, 131) != 1){ SCWrite(pCon, "ERROR: required configuration parameter username not found", eError); return 0; } if(StringDictGet(pOpt,"password",pPriv->passWord, 131) != 1){ SCWrite(pCon, "ERROR: required configuration parameter password not found", eError); return 0; } /* actual configuration. Check for flag INIT in options. We do not need to configure, if the HM has configured itself already. What is does, these days. */ status = StringDictGetAsNumber(pOpt,"init",&fVal); iInit = 0; if(status == 1) { if(fVal > 0.9 ) { iInit = 1; } } /* actually do configure */ if(iInit == 0){ memset(confCommand,0,512*sizeof(char)); if(StringDictGet(pOpt,"hmconfigscript",confCommand,511) != 1){ SCWrite(pCon, "ERROR: required parameter hmconfigscript not found!", eError); return 0; } status = Tcl_Eval(pSics->pTcl,confCommand); if(status != TCL_OK){ snprintf(confCommand,511,"ERROR: Tcl reported %s while evaluating hmconfigscript", Tcl_GetStringResult(pSics->pTcl)); SCWrite(pCon,confCommand,eError); return 0; } else { /* uplod new configuration to HM */ ghttp_clean(pPriv->syncRequest); snprintf(url,511,"%s%s",pPriv->hmAddress,configure); status = ghttp_set_uri(pPriv->syncRequest,url); if(status < 0){ SCWrite(pCon,"ERROR: invalid URI for HM request",eError); return 0; } status = ghttp_set_type(pPriv->syncRequest,ghttp_type_post); confData = (char *)Tcl_GetStringResult(pSics->pTcl); status = ghttp_set_body(pPriv->syncRequest,confData, strlen(confData)); ghttp_set_authinfo(pPriv->syncRequest, pPriv->userName, pPriv->passWord); ghttp_set_sync(pPriv->syncRequest,ghttp_sync); status = ghttp_prepare(pPriv->syncRequest); httpStatus = ghttp_process(pPriv->syncRequest); confData = (char *)ghttp_get_body(pPriv->syncRequest); if(httpStatus != ghttp_done){ confData = (char *)ghttp_get_error(pPriv->syncRequest); snprintf(confCommand,511,"ERROR: http error %s occurred", confData); SCWrite(pCon,confCommand,eError); return 0; } else if (confData) { // MJL check ghttp_get_body result for NULL if(strstr(confData,"ERROR") != NULL){ snprintf(confCommand,511,"%s",confData); SCWrite(pCon,confCommand,eError); return 0; } if(strstr(confData,"Authentication Error") != NULL){ snprintf(confCommand,511,"%s",confData); SCWrite(pCon,confCommand,eError); return 0; } } } } // MJL NOTE: Added extra init parameters here, these get committed // regardless of whether we are doing the first init or not // (i.e. will get committed even if init=1). // Need to do init on the histogram object (e.g. 'hm init') // in order to commit changed settings during operation. // Specifically, this is important if FAT settings need to be // modified during operation. To do this, the command // 'hm configure FAT_Xxxx vvv' needs to be performed, // where Xxxx is the name of the item in the FAT, and vvv is the desired value. // If the requested FAT variable doesn't exist or cannot be modified // during operation, the set fails but no indication is given. // // Now, to find out what entries are FAT_Xxxx format, /// by traversing the dictionary list. char pValue[256]; const char *pItem=NULL; do { pItem=StringDictGetNext(pOpt,pValue,256); if (pItem) { if (strncasecmp(pItem,"FAT_",4)==0) { // Try committing the setting to the histogram server SCWrite(pCon,"Commit to HS FAT:",eError); // MJL DEBUG SCWrite(pCon,(char *)pItem,eError); // MJL DEBUG SCWrite(pCon,pValue,eError); // MJL DEBUG // Make special http request to set the FAT parameter char modify_FAT_http_request[1024]; sprintf(modify_FAT_http_request,"/admin/selectdynamicfatmodify.egi?dynamicFATmodifyparamname=%s&dynamicFATmodifyparamvalue=%s", pItem+4,pValue); // Send the request. When one doesn't work, drop out of the loop. //SCWrite(pCon,"httpget",eError); // MJL DEBUG int status = anstoHttpGet(pPriv,modify_FAT_http_request); if(status != 1) return 0; //SCWrite(pCon,"Done httpget",eError); // MJL DEBUG } } } while(pItem); return 1; } /*--------------------------------------------------------------------*/ static int readStatus(pHistDriver pDriv){ char *pPtr = NULL, *pLinePtr; char line[132]; char name[80], value[80]; pAnstoHttp self = NULL; char *daqPtr = NULL, *daqValPtr = NULL; self = (pAnstoHttp)pDriv->pPriv; assert(self != NULL); pPtr = ghttp_get_body(self->syncRequest); if(pPtr == NULL){ strncpy(self->hmError,"No body in status response",131); self->errorCode = NOBODY; return 0; } pPtr = stptok(pPtr,line,132,"\n"); while(pPtr != NULL){ pLinePtr = line; pLinePtr = stptok(pLinePtr,name,80,":"); pLinePtr = stptok(pLinePtr,value,80,":"); strtolower(name); if(StringDictExists(pDriv->pOption,trim(name)) == 1){ StringDictUpdate(pDriv->pOption,trim(name),trim(value)); } else { StringDictAddPair(pDriv->pOption,trim(name),trim(value)); } pPtr = stptok(pPtr,line,131,"\n"); } return 1; } /*---------------------------------------------------------------------*/ // MJL we now force the status daq entry in the dictionary the explicit // expected value ("Started", "Stopped" or "Paused"), whenever any // of start, stop, pause or continue is done. I tried reading the status // back from the server, but because it can respond slowly a delay would // have been required, which is cumbersome. // This way, the status read via the list command should be accurate // provided there are no external factors involved (like users pushing // start/stop/pause buttons on web page, etc.) // It would be better to somehow force the status check so we would know // that the daq entry in the dictonary really reflects the histogrammer // status... we should discuss with Mark K sometime, maybe we can add this // feature to the base code. // static int AnstoHttpStatus(pHistDriver self,SConnection *pCon){ // pCon=NULL allowed pAnstoHttp pPriv = NULL; ghttp_status httpStatus; char daqStatus[20]; int status, len; char *pPtr = NULL; ///if (pCon) SCWrite(pCon,"In AnstoHttpStatus",eError); // MJL DEBUG pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); // MJL for the ANSTO histogram server we STILL need status checking to occur // even when in paused mode (our pause mode has a different functionality). // So the code below is removed. /// if(pPriv->pause == 1){ /// return HWPause; /// } if(pPriv->asyncRunning == 0){ status = anstoHttpGetPrepare(pPriv,statusdaq); ghttp_set_sync(pPriv->syncRequest,ghttp_async); ghttp_prepare(pPriv->syncRequest); if(status != 1){ ///if (pCon) SCWrite(pCon,"HWF1.",eError); // MJL DEBUG return HWFault; } pPriv->asyncRunning = 1; } httpStatus = ghttp_process(pPriv->syncRequest); switch(httpStatus){ case ghttp_error: ghttp_close(pPriv->syncRequest); anstoHttpGetPrepare(pPriv,statusdaq); ghttp_prepare(pPriv->syncRequest); httpStatus = ghttp_process(pPriv->syncRequest); if(httpStatus != ghttp_done){ strncpy(pPriv->hmError,"Reconnect", 511); pPriv->errorCode = SERVERERROR; ///if (pCon) SCWrite(pCon,"HWF2.",eError); // MJL DEBUG return HWFault; pPriv->asyncRunning = 0; } break; case ghttp_not_done: ///if (pCon) SCWrite(pCon,"HWB.",eError); // MJL DEBUG return HWBusy; break; case ghttp_done: pPriv->asyncRunning = 0; status = anstoHttpCheckResponse(pPriv); if(status != 1){ ///if (pCon) SCWrite(pCon,"HWF3.",eError); // MJL DEBUG return HWFault; } break; } status = readStatus(self); if(status != 1){ ///if (pCon) SCWrite(pCon,"HWF4.",eError); // MJL DEBUG return HWFault; } if(StringDictGet(self->pOption,"daq",daqStatus,20) != 1){ ///if (pCon) SCWrite(pCon,"Field 'daq' not found!!!",eError); // MJL DEBUG pPriv->errorCode = BADSTATUS; strncpy(pPriv->hmError,"ERROR: status does not contain DAQ field",511); return HWFault; } if(strstr(daqStatus,"Started") != NULL){ ///if (pCon) SCWrite(pCon,"DAQ Started.",eError); // MJL DEBUG return HWBusy; } else if(strstr(daqStatus,"Paused") != NULL){ ///if (pCon) SCWrite(pCon,"DAQ Paused.",eError); // MJL DEBUG return HWIdle; } else if(strstr(daqStatus,"Stopped") != NULL){ ///if (pCon) SCWrite(pCon,"DAQ Stopped.",eError); // MJL DEBUG return HWIdle; } else { pPriv->errorCode = BADSTATUS; snprintf(pPriv->hmError,511,"ERROR: invalid DAQ status %s",daqStatus); return HWFault; } return HWIdle; } /*--------------------------------------------------------------------*/ static int AnstoHttpStart(pHistDriver self, SConnection *pCon){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,startdaq); if(status != 1){ return HWFault; } // MJL force the daq status to the expected value... StringDictUpdate(self->pOption, "daq", "Started"); return 1; } /*---------------------------------------------------------------------*/ static int AnstoHttpHalt(pHistDriver self){ // hmm, why isn't there a pCon like all the other ones...? pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,stopdaq); if(status != 1){ return HWFault; } // MJL force the daq status to the expected value... StringDictUpdate(self->pOption, "daq", "Stopped"); return 1; } /*---------------------------------------------------------------------*/ static int AnstoHttpPause(pHistDriver self,SConnection *pCon){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,pausedaq); if(status != 1){ return HWFault; } pPriv->pause = 1; // MJL force the daq status to the expected value... StringDictUpdate(self->pOption, "daq", "Paused"); return 1; } /*---------------------------------------------------------------------*/ static int AnstoHttpContinue(pHistDriver self, SConnection *pCon){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,continuedaq); if(status != 1){ return HWFault; } pPriv->pause = 0; // MJL force the daq status to the expected value... StringDictUpdate(self->pOption, "daq", "Started"); return 1; } /*---------------------------------------------------------------------*/ static int AnstoHttpError(pHistDriver self, int *code, char *error, int errLen){ pAnstoHttp pPriv = NULL; int status; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); strncpy(error,pPriv->hmError, errLen); *code = pPriv->errorCode; return 1; } /*--------------------------------------------------------------------*/ static int AnstoHttpFixIt(pHistDriver self, int code){ pAnstoHttp pPriv = NULL; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); /* * not much to fix here: ghttplib already tries hard to fix * connection problems... But abort any pending transactions... */ ghttp_close(pPriv->syncRequest); if(code == SERVERERROR){ pPriv->failCount++; if(pPriv->failCount > 2){ return COTERM; } else { return COREDO; } } return COTERM; } /*--------------------------------------------------------------------*/ static int AnstoHttpGetData(pHistDriver self,SConnection *pCon){ /* * do noting, monitors are with the counter, histogram memory * caching and retrieval is handled via GetHistogram */ return 1; } /*-------------------------------------------------------------------*/ static int AnstoHttpGetHistogram(pHistDriver self, SConnection *pCon, int bank, int start, int end, HistInt *data){ char command[256]; HistInt *hmdata; pAnstoHttp pPriv = NULL; int status, len, i; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); snprintf(command,255,"%s?bank=%d&start=%d&end=%d",gethm,bank, start,end); status = anstoHttpGet(pPriv,command); if(status != 1){ return HWFault; } len = ghttp_get_body_len(pPriv->syncRequest); if(len < (end-start)*sizeof(int)){ pPriv->errorCode = BODYSHORT; strncpy(pPriv->hmError,"Not enough data received from HM",511); return HWFault; } hmdata = (HistInt *)ghttp_get_body(pPriv->syncRequest); if(hmdata == NULL){ // MJL check ghttp_get_body for NULL return pPriv->errorCode = NOBODY; strncpy(pPriv->hmError,"No body in HM data response",511); return HWFault; } for(i = 0; i < (end - start); i++){ data[i] = ntohl(hmdata[i]); } return 1; } /*--------------------------------------------------------------------*/ static int AnstoHttpSetHistogram(pHistDriver self, SConnection *pCon, int bank, int start, int end, HistInt *data){ pAnstoHttp pPriv = NULL; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); pPriv->errorCode = NOTIMPLEMENTED; strncpy(pPriv->hmError,"Not implemented",511); return HWFault; } /*---------------------------------------------------------------------*/ static long AnstoHttpGetMonitor(pHistDriver self, int i, SConnection *pCon){ return 0; } /*---------------------------------------------------------------------*/ static float AnstoHttpGetTime(pHistDriver self, SConnection *pCon){ return -999.99; } /*---------------------------------------------------------------------*/ static int AnstoHttpPreset(pHistDriver self, SConnection *pCon, int val){ pAnstoHttp pPriv = NULL; int status; char command[512]; pPriv = (pAnstoHttp)self->pPriv; assert(pPriv != NULL); snprintf(command,512,"%s?value=%d",preset,val); status = anstoHttpGet(pPriv,command); if(status != 1){ return HWFault; } return 1; } /*---------------------------------------------------------------------*/ static int AnstoHttpFreePrivate(pHistDriver self){ pAnstoHttp pPriv = NULL; pPriv = (pAnstoHttp)self->pPriv; if(pPriv == NULL){ return 1; } if(pPriv->syncRequest != NULL){ ghttp_request_destroy(pPriv->syncRequest); } free(pPriv); return 1; } /*-------------------------------------------------------------------*/ pHistDriver CreateAnstoHttpDriver(pStringDict pOption){ pHistDriver pNew = NULL; pAnstoHttp pInternal = NULL; /* create the general driver */ pNew = CreateHistDriver(pOption); if(!pNew){ return NULL; } /* add our options */ StringDictAddPair(pOption,"hmaddress","http://unknown.psi.ch:9090"); StringDictAddPair(pOption,"hmconfigurescript","hmconfigure"); /* initialise our private data structure */ pInternal = (pAnstoHttp)malloc(sizeof(anstoHttp)); if(!pInternal){ free(pNew); return NULL; } memset(pInternal,0,sizeof(anstoHttp)); pNew->pPriv = pInternal; pInternal->syncRequest = ghttp_request_new(); if(pInternal->syncRequest == NULL){ free(pNew); free(pInternal); return NULL; } /* configure all those functions */ pNew->Configure = AnstoHttpConfigure; pNew->Start = AnstoHttpStart; pNew->Halt = AnstoHttpHalt; pNew->GetCountStatus = AnstoHttpStatus; pNew->GetError = AnstoHttpError; pNew->TryAndFixIt = AnstoHttpFixIt; pNew->GetData = AnstoHttpGetData; pNew->GetHistogram = AnstoHttpGetHistogram; pNew->SetHistogram = AnstoHttpSetHistogram; pNew->GetMonitor = AnstoHttpGetMonitor; pNew->GetTime = AnstoHttpGetTime; pNew->Preset = AnstoHttpPreset; pNew->FreePrivate = AnstoHttpFreePrivate; pNew->Pause = AnstoHttpPause; pNew->Continue = AnstoHttpContinue; return pNew; }