/* 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); if (!anstoHttpGetPrepare(self,request)) return 0; // MJL return here if failed ghttp_prepare(self->syncRequest); // MJL be sure to call ghttp_prepare before each ghttp_process 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); /* * 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 // 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. int status = anstoHttpGet(pPriv,modify_FAT_http_request); if(status != 1) return 0; } } } 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; } /*---------------------------------------------------------------------*/ // The status code now allows pCon==NULL to be passed in. // This allows the status to be checked from all the start, stop, pause // and continue callbacks, so we can wait for the server to actually // change to the correct operating state before continuing. 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; 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){ 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; return HWFault; pPriv->asyncRunning = 0; } break; case ghttp_not_done: // MJL this is not a problem, the server is just slow. // return HWRedo not HWBusy (original) or falut return HWRedo; break; case ghttp_done: pPriv->asyncRunning = 0; status = anstoHttpCheckResponse(pPriv); if(status != 1){ return HWFault; } break; } status = readStatus(self); if(status != 1){ return HWFault; } if(StringDictGet(self->pOption,"daq",daqStatus,20) != 1){ pPriv->errorCode = BADSTATUS; strncpy(pPriv->hmError,"ERROR: status does not contain DAQ field",511); return HWFault; } ///if (pCon) SCWrite(pCon,daqStatus,eError); // MJL DEBUG if(strstr(daqStatus,"Started") != NULL){ return HWBusy; } else if(strstr(daqStatus,"Paused") != NULL){ return HWIdle; } else if(strstr(daqStatus,"Stopped") != NULL){ return HWIdle; } else { pPriv->errorCode = BADSTATUS; snprintf(pPriv->hmError,511,"ERROR: invalid DAQ status %s",daqStatus); return HWFault; } return HWFault; // shouldn't get here } #define MAX_STATUS_READ_RETRIES 50 #define STATUS_READ_DELAY_US 100000 // static int AnstoHttpStatusWithRetries(pHistDriver self, int requiredstate,SConnection *pCon) // pCon=NULL allowed { int retries,retcode,initialentry=1; for(retries=0;retriespPriv; assert(pPriv != NULL); status = anstoHttpGet(pPriv,startdaq); if(status != 1){ return HWFault; } AnstoHttpStatusWithRetries(self,HWBusy,pCon); return OKOK; } /*---------------------------------------------------------------------*/ 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; } AnstoHttpStatusWithRetries(self,HWIdle,NULL); // no pCon available :( return OKOK; } /*---------------------------------------------------------------------*/ 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; AnstoHttpStatusWithRetries(self,HWIdle,pCon); return OKOK; } /*---------------------------------------------------------------------*/ 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; AnstoHttpStatusWithRetries(self,HWBusy,pCon); return OKOK; } /*---------------------------------------------------------------------*/ 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 OKOK; } /*--------------------------------------------------------------------*/ 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 OKOK; } /*-------------------------------------------------------------------*/ 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 OKOK; } /*--------------------------------------------------------------------*/ 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; }