819 lines
29 KiB
C
819 lines
29 KiB
C
/*
|
|
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 <stdlib.h>
|
|
#include <assert.h>
|
|
#include <HistDriv.i>
|
|
#include <tcl.h>
|
|
#include <ghttp.h>
|
|
#include <stptok.h>
|
|
#include <countdriv.h>
|
|
#include <nserver.h>
|
|
|
|
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 {
|
|
return anstoHttpCheckResponse(self);
|
|
}
|
|
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;
|
|
int i;
|
|
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 == 1){
|
|
for(i = 0; i < 2; i++){
|
|
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){
|
|
/* we may need to reconnect.....
|
|
*/
|
|
if(i == 0){
|
|
ghttp_close(pPriv->syncRequest);
|
|
continue;
|
|
}
|
|
/*
|
|
* no we have a real error
|
|
*/
|
|
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 != 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.
|
|
//
|
|
// Define codes for more detailed status info
|
|
// (returned via pextrastatus instead of retcode which is just HWBusy or HWIdle).
|
|
// These are needed so we can tell when a start/stop/pause operation actually completes.
|
|
#define ANSTO_HS_STATUS_INVALID 0 // e.g. couldn't retrieve a status because server is down
|
|
#define ANSTO_HS_STATUS_STARTED 1
|
|
#define ANSTO_HS_STATUS_PAUSED 2
|
|
#define ANSTO_HS_STATUS_STOPPED 3
|
|
#define ANSTO_HS_STATUS_STARTING 4
|
|
#define ANSTO_HS_STATUS_PAUSING 5
|
|
#define ANSTO_HS_STATUS_STOPPING 6
|
|
//
|
|
static int AnstoHttpStatus_Base(pHistDriver self,SConnection *pCon,int *pextrastatus){ // pCon=NULL allowed
|
|
pAnstoHttp pPriv = NULL;
|
|
ghttp_status httpStatus;
|
|
char daqStatus[20];
|
|
int status, len;
|
|
char *pPtr = NULL;
|
|
static int last_known_status=HWIdle; // assume idle initially
|
|
|
|
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 (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_INVALID;
|
|
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 (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_INVALID;
|
|
pPriv->asyncRunning = 0; // MJL bug fix 9/03/07
|
|
return HWFault;
|
|
}
|
|
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){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_INVALID;
|
|
return HWFault;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
status = readStatus(self);
|
|
if(status != 1){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_INVALID;
|
|
return HWFault;
|
|
}
|
|
|
|
if(StringDictGet(self->pOption,"daq",daqStatus,20) != 1){
|
|
pPriv->errorCode = BADSTATUS;
|
|
strncpy(pPriv->hmError,"ERROR: status does not contain DAQ field",511);
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_INVALID;
|
|
return HWFault;
|
|
}
|
|
///if (pCon) SCWrite(pCon,daqStatus,eError); // MJL DEBUG
|
|
//
|
|
// Basically we just diagnose whether the DAQ is running or not,
|
|
// but also return more detailed status via pextrastatus if supplied.
|
|
if(strstr(daqStatus,"Started") != NULL){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_STARTED;
|
|
return last_known_status=HWBusy;
|
|
} else if(strstr(daqStatus,"Paused") != NULL){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_PAUSED;
|
|
return last_known_status=HWIdle;
|
|
} else if(strstr(daqStatus,"Stopped") != NULL){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_STOPPED;
|
|
return last_known_status=HWIdle;
|
|
//
|
|
// The following are extra statuses which may
|
|
// (in the next histogram server version) be reported
|
|
// by the server, when the DAQ state is changing.
|
|
// As some DAE types are slow to start up, this
|
|
// is a valuable feature.
|
|
//
|
|
// Try to distinguish the HWBusy and HWIdle states
|
|
// based on the last known status. For example,
|
|
// when Stopping from Paused state it should report
|
|
// HWBusy if it was in Started state to begin with,
|
|
// otherwise HWIdle if it was in Paused or Stopped states.
|
|
} else if(strstr(daqStatus,"Starting") != NULL){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_STARTING;
|
|
return last_known_status; // if started already, is already HWBusy
|
|
} else if(strstr(daqStatus,"Stopping") != NULL){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_PAUSING;
|
|
return last_known_status; // return HWBusy only if it was started before
|
|
} else if(strstr(daqStatus,"Pausing") != NULL){
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_STOPPING;
|
|
return last_known_status; // return HWBusy if it was started before
|
|
// bad or missing status??
|
|
} else {
|
|
if (pextrastatus) *pextrastatus=ANSTO_HS_STATUS_INVALID;
|
|
pPriv->errorCode = BADSTATUS;
|
|
snprintf(pPriv->hmError,511,"ERROR: invalid DAQ status %s",daqStatus);
|
|
return HWFault;
|
|
}
|
|
|
|
return HWFault; // shouldn't get here
|
|
}
|
|
//
|
|
static int AnstoHttpStatus(pHistDriver self,SConnection *pCon){ // pCon=NULL allowed
|
|
return AnstoHttpStatus_Base(self,pCon,NULL); // pCon=NULL allowed
|
|
}
|
|
|
|
|
|
#define STATUS_READ_DELAY_USEC 200000 // re-check once per 0.2 second if possible
|
|
#define MAX_STATUS_READ_RETRIES 150 // wait up to 30 secs for status to change to desired state
|
|
#define MAX_BAD_STATUS_REQUESTS 3 // don't wait too long if too many bad status requests (probably server is down)
|
|
//
|
|
long long get_localtime_us()
|
|
// Get the time in us not in the US ;)
|
|
{
|
|
struct timeval tv;
|
|
gettimeofday(&tv,NULL);
|
|
return ((long long)tv.tv_sec)*1000000+tv.tv_usec;
|
|
}
|
|
//
|
|
static int AnstoHttpStatusWithRetries(pHistDriver self, int requiredstate,SConnection *pCon) // pCon=NULL allowed
|
|
{
|
|
int retries=0,initialentry=1,bad_status_requests=0;
|
|
int retcode,runloop;
|
|
do
|
|
{
|
|
int extrastatus;
|
|
// Request current status from the histogram server.
|
|
// The server usually responds within milliseconds.
|
|
retcode=AnstoHttpStatus_Base(self,pCon,&extrastatus);
|
|
retries++;
|
|
bad_status_requests+=(extrastatus==ANSTO_HS_STATUS_INVALID);
|
|
runloop=(retries<MAX_STATUS_READ_RETRIES)&&(initialentry||extrastatus!=requiredstate)
|
|
&&(bad_status_requests<MAX_BAD_STATUS_REQUESTS);
|
|
// Retry after a delay if status hasn't changed yet
|
|
// (DAE takes time to start up / shut down)
|
|
if (runloop) // Only wait if we're going to loop, otherwise drop out immediately
|
|
{
|
|
long long start_time_us=get_localtime_us();
|
|
do // make sure the yield in this loop gets called at least once
|
|
{
|
|
// Service other SICS tasks while we wait.
|
|
pTaskMan pTasker = GetTasker();
|
|
if (pTasker)
|
|
TaskYield(pTasker);
|
|
} while(get_localtime_us()<(start_time_us+STATUS_READ_DELAY_USEC));
|
|
}
|
|
initialentry=0;
|
|
} while(runloop);
|
|
return retcode;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------*/
|
|
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;
|
|
}
|
|
AnstoHttpStatusWithRetries(self,ANSTO_HS_STATUS_STARTED,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,ANSTO_HS_STATUS_STOPPED,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,ANSTO_HS_STATUS_PAUSED,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); // which is the same as restarting
|
|
if(status != 1){
|
|
return HWFault;
|
|
}
|
|
pPriv->pause = 0;
|
|
AnstoHttpStatusWithRetries(self,ANSTO_HS_STATUS_STARTED,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;
|
|
|
|
// For the ANSTO Histogram Server, the transfer size may be large
|
|
// and this can lead to transmission of the data from the server in chunks.
|
|
// This is apparent from the presence of 'xxxxx; chunk length nnnnnn' in the
|
|
// data when the transfer size is greater than 128K.
|
|
// e.g. a request for 64K elements is split into two chunks,
|
|
// and each has the header '20000; chunk length 131072'.
|
|
// However for some reason ghttp_get_body_len returns only the data size,
|
|
// even though there are header(s) added to the data.
|
|
// So, for the time being we play it safe and divide the transfer
|
|
// into chunks of less than 128KB.
|
|
//
|
|
// MJL 22/11/06: Found that AppWeb 2.1.0 supports HTTP 1.1 chunking,
|
|
// but this can be turned off by specifying HttpChunking off in the AppWeb
|
|
// config file, which is what we do currently, so the transfer size limitation code below
|
|
// is no longer needed. I've left it in with a much larger size restriction,
|
|
// just in case it's required later on for any reason (to improve transfer reliability?).
|
|
// Apparently the AppWeb 2.1.0 HTTP chunking feature is broken, because when the
|
|
// data is opened directly from the web server using an editor like KHexEdit, the open
|
|
// stalls for large data sizes unless chunking is disabled. This shows it is
|
|
// an AppWeb problem, not a ghttp problem.
|
|
// For AppWeb versions before 2.1.0, HTTP chunking is not available anyway.
|
|
|
|
pPriv = (pAnstoHttp)self->pPriv;
|
|
assert(pPriv != NULL);
|
|
|
|
// How this limit is set in the server is not obvious... anyway, it should not change.
|
|
// MJL 22/11/06: Turned HTTP 1.1 chunking off at the histogram server, so we no longer
|
|
// need a size restriction. Leave the code in just in case it's needed for other reasons,
|
|
// but set a larger maximum request size.
|
|
// MJL 19/3/07 Because some clients now perform requests without valid start and end parameters,
|
|
// the code below won't work properly in some cases. As a temporary fix, effectively disable it
|
|
// by setting MAX_HTTP_REQUEST_BYTES to a very large value (4GB = 1Gint).
|
|
// Note, actual transfer of such large data sizes in one hit may fail, not recommended.
|
|
//#define MAX_HTTP_REQUEST_BYTES 131072
|
|
//#define MAX_HTTP_REQUEST_BYTES 1048576
|
|
#define MAX_HTTP_REQUEST_BYTES (((long long)4096)*1048576) // max transfer size 4GB (but your mileage may vary)
|
|
// Do the HTTP data transfer in bite sized pieces
|
|
while(end>start)
|
|
{
|
|
int size=((end-start)>(MAX_HTTP_REQUEST_BYTES/sizeof(int)))
|
|
?(MAX_HTTP_REQUEST_BYTES/sizeof(int)):(end-start);
|
|
|
|
snprintf(command,255,"%s?bank=%d&start=%d&end=%d",gethm,bank,
|
|
start,start+size);
|
|
|
|
status = anstoHttpGet(pPriv,command);
|
|
if(status != 1){
|
|
return HWFault;
|
|
}
|
|
|
|
len = ghttp_get_body_len(pPriv->syncRequest);
|
|
if(len < size*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;
|
|
}
|
|
|
|
// MJL removed the ntohl, our histogram server doesn't apply htonl,
|
|
// so the data arrives in LE format. (Would have to buffer data
|
|
// at the server otherwise...)
|
|
// Doubt this will raise any conpatibility issues.
|
|
for(i = 0; i < size; i++){
|
|
//data[i] = ntohl(hmdata[i]);
|
|
data[i] = hmdata[i];
|
|
}
|
|
|
|
data+=size;
|
|
start+=size;
|
|
}
|
|
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://localhost:8080");
|
|
StringDictAddPair(pOption,"hmconfigurescript","anstohm_full.xml");
|
|
|
|
/* 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;
|
|
}
|