Files
sics/site_ansto/counterdriv.c
Douglas Clowes b61c088873 Update monitor response handling
r1056 | dcl | 2006-08-10 16:44:01 +1000 (Thu, 10 Aug 2006) | 2 lines
2012-11-15 12:45:59 +11:00

629 lines
17 KiB
C

/** \file counterdriv.c
* \brief Sics counter driver for the ANSTO beam monitor.
*
* A SICS counter can manage several monitors, this is the driver
* for the beam monitor developed at ANSTO
*
* Copyright: see file Copyright.txt
*
* Ferdi Franceschini July 2006
*
*/
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <time.h>
#include "fortify.h"
#include <string.h>
#include <sics.h>
#include <rs232controller.h>
#include <countdriv.h>
#include "anstoutil.h"
#define FAILURE 0
#define SUCCESS 1
/*@-incondefs@*/
/*@observer@*//*@dependent@*/ Tcl_Interp *InterpGetTcl(SicsInterp *pSics);
int readRS232(prs232 self, /*@out@*/void *data, /*@out@*/int *dataLen);
void getRS232Error(int iCode, /*@out@*/ char *errorBuffer, int errorBufferLen);
/*@observer@*//*@dependent@*/ pCounterDriver CreateCounterDriver(char *name, char *type);
void KillRS232(/*@only@ frees pData */ void *pData);
/*@+incondefs@*/
typedef struct {
int length;
char body[8192];
} BUFFER;
typedef struct {
prs232 controller;
int state;
int errorCode;
char *errorMsg; /**< Points to memory for error messages */
char *host;
int iPort;
float dummy_threshold;
BUFFER buffer;
unsigned long long counter_value;
} BeamMon, *pBeamMon;
static int MonSend(CounterDriver *cntrData, char *pText, char *pReply, int iReplyLen);
static void flog(char flag, char* pText) {
FILE* file;
file = fopen("flog.txt", "a");
if (file) {
struct timeval tv;
gettimeofday(&tv, NULL);
fprintf(file, "%02d:%02d:%02d:%06d::%c::%s",
tv.tv_sec % (24 * 3600) / 3600,
tv.tv_sec % (3600) / 60,
tv.tv_sec % 60,
tv.tv_usec,
flag,
pText);
if (strchr(pText, '\n') == NULL)
fputc('\n', file);
fclose(file);
}
}
/** \brief Writes a line to the Monitor
* used for sending commands.
*
* \param *cntrData provides access to a monitor's data
* \param *text pointer to NULL terminated text to send.
* \return
* - SUCCESS
* - FAILURE
*/
static int MonWrite(pBeamMon self, char* text) {
int len;
int status;
len = strlen(text);
if (len > 0)
flog('>', text);
status = writeRS232(self->controller, text, len);
if (status != 1) {
self->errorCode = status;
return FAILURE;
}
return SUCCESS;
}
/** \brief Reads a line from the Monitor
* used for sending commands.
*
* \param *cntrData provides access to a monitor's data
* \param *text pointer to NULL terminated text to receive.
* \param *pLen inout pointer to length of text to receive.
* \return
* - SUCCESS
* - FAILURE
*/
static int MonRead(pBeamMon self, char* text, int *pLen) {
int i, status, retries=20;
for (i=0; i<retries; i++) {
status = readRS232TillTerm(self->controller, text, pLen);
switch (status) {
case 1:
if (pLen > 0)
flog('<', text);
return SUCCESS;
case TIMEOUT:
flog('<', "*TIMEOUT*");
self->errorCode = status;
continue;
default:
flog('<', "*ERROR*");
self->errorCode = status;
return FAILURE;
}
}
flog('<', "***TIMEOUT***");
return FAILURE;
}
static void HandleReport(CounterDriver *cntrData, BUFFER* bp)
{
//READ xxxx 00:00:00.000000 0.000000 0 0.00
//0123456789012345678901234567890
BeamMon* self = NULL;
self = (BeamMon *) cntrData->pData;
/* TODO better than this */
char* cp = &bp->body[26];
char* ep;
cntrData->fTime = strtod(cp, &ep);
cp = ep;
switch (toupper(bp->body[5])) {
case 'I': /* idle */
case 'S': /* stopped */
self->state = HWIdle;
break;
case 'R': /* running */
self->state = HWBusy;
break;
case 'P': /* paused */
self->state = HWPause;
break;
default:
self->state = HWFault;
break;
}
if (toupper(bp->body[8] == 'G')) /* Gated */
self->state = HWNoBeam;
self->counter_value = strtoull(cp, &ep, 10);
cntrData->fLastCurrent = self->counter_value;
char str[100];
snprintf(str, sizeof(str), "READ %s, %f, %f, %f\n",
cntrData->eMode == eTimer ? "eTimer" :
cntrData->eMode == ePreset ? "ePreset" : "Unknown",
cntrData->fPreset,
cntrData->fLastCurrent,
cntrData->fTime);
flog('.', str);
}
static int MonDrainInput(CounterDriver *cntrData) {
int iRet, len;
char reply[1024];
BeamMon* self = NULL;
self = (BeamMon *) cntrData->pData;
while (availableRS232(self->controller)) {
len = sizeof(reply);
iRet = MonRead(self, reply, &len);
if (iRet == FAILURE) {
return iRet;
}
}
return SUCCESS;
}
static int MonHandleInput(CounterDriver *cntrData, BUFFER* bp) {
BeamMon* self = NULL;
int iRet, len;
int iLen = 0;
int jLen = 0;
char reply[1024];
char* pTerm;
self = (BeamMon *) cntrData->pData;
/* TODO handle the line */
flog('<', bp->body);
const char et[] = "EVENT TERMINAL";
if (strncasecmp(bp->body, et, sizeof(et) - 1) == 0) {
if (self->state == HWBusy)
self->state = HWIdle;
}
const char eri[] = "EVENT RANGE IN";
if (strncasecmp(bp->body, eri, sizeof(eri) - 1) == 0)
if (self->state == HWPause)
self->state = HWBusy;
const char ero[] = "EVENT RANGE OUT";
if (strncasecmp(bp->body, ero, sizeof(ero) - 1) == 0)
if (self->state == HWBusy)
self->state = HWPause;
const char rpt[] = "READ";
if (strncasecmp(bp->body, rpt, sizeof(rpt) - 1) == 0) {
if (self->state == HWBusy) {
HandleReport(cntrData, &self->buffer);
MonWrite(self, "SICS READ");
}
}
bp->length = 0;
return 1;
}
static int MonLookForInput(CounterDriver *cntrData, int timeout) {
BeamMon* self = NULL;
int iRet, len;
int iLen = 0;
int jLen = 0;
char reply[1024];
char* pTerm;
self = (BeamMon *) cntrData->pData;
while (availableRS232(self->controller)) {
len = sizeof(reply);
iRet = MonRead(self, reply, &len);
if (iRet == FAILURE) {
return iRet;
}
iLen = 0;
while (iLen < len) {
char* pTerm = strstr(&reply[iLen], "\r\n");
if (pTerm)
jLen = &pTerm[2] - &reply[iLen];
else
jLen = len - iLen;
strncpy(&self->buffer.body[self->buffer.length],
&reply[iLen],
jLen);
self->buffer.length += jLen;
self->buffer.body[self->buffer.length] = '\0';
iLen += jLen;
if (pTerm) {
/* handle the line */
MonHandleInput(cntrData, &self->buffer);
}
self->buffer.length = 0;
}
}
return 1;
}
/** \brief Returns the counter status,
* implements the GetStatus method in the MotorDriver interface.
*
* \param *cntrData provides access to a monitor's data
* \param *fControl is set to the current value of the counting control variable. This can either be the counting time already passed or the count rate of the control monitor in ePreset mode.
* \return
* - HWIdle The counter has finished and is idle.
* - HWFault hardware fault or the counter returned an absurd value.
* - HWNoBeam There is no incident beam.
* - HWPause Counting has been paused.
* - HWBusy The counter is busy counting.
*/
static int MonGetStatus(CounterDriver *cntrData, float *fControl) {
BeamMon *self = NULL;
int status;
self = (BeamMon *) cntrData->pData;
MonLookForInput(cntrData, 0);
status = MonSend(cntrData, "SICS READ", self->buffer.body, sizeof(self->buffer.body));
if (status == SUCCESS) {
/* TODO */
MonHandleInput(cntrData, &self->buffer);
}
if (cntrData->eMode == eTimer)
*fControl = cntrData->fTime;
else
*fControl = cntrData->fLastCurrent;
switch (self->state) {
case HWIdle:
return HWIdle;
case HWNoBeam:
return HWNoBeam;
case HWPause:
return HWPause;
case HWBusy:
return HWBusy;
case HWFault:
default:
return HWFault;
}
}
/** \brief Starts counting in the current mode and with the current preset
*
* \param *cntrData provides access to a monitor's data
*/
static int MonStart(CounterDriver *cntrData) {
BeamMon *self = NULL;
char str[100];
snprintf(str, sizeof(str), "START %s, %f, %f, %f\n",
cntrData->eMode == eTimer ? "eTimer" :
cntrData->eMode == ePreset ? "ePreset" : "Unknown",
cntrData->fPreset,
cntrData->fLastCurrent,
cntrData->fTime);
flog('.', str);
self = (BeamMon *) cntrData->pData;
MonDrainInput(cntrData);
if (cntrData->eMode == eTimer) {
MonSend(cntrData, "SICS SET TE_CHECK=TIMER",
self->buffer.body, sizeof(self->buffer.body));
} else {
MonSend(cntrData, "SICS SET TE_CHECK=COUNTER",
self->buffer.body, sizeof(self->buffer.body));
}
snprintf(str, sizeof(str), "SICS SET TERMINAL=%llu",
(unsigned long long) cntrData->fPreset);
MonSend(cntrData, str,
self->buffer.body, sizeof(self->buffer.body));
MonSend(cntrData, "SICS START",
self->buffer.body, sizeof(self->buffer.body));
self->state = HWBusy;
return SUCCESS;
}
/** \brief Pauses a counting operation
*
* \param *cntrData provides access to a monitor's data
*/
static int MonPause(CounterDriver *cntrData) {
BeamMon *self = NULL;
self = (BeamMon *) cntrData->pData;
if (MonSend(cntrData, "SICS PAUSE",
self->buffer.body, sizeof(self->buffer.body)) == SUCCESS) {
self->state = HWPause;
return SUCCESS;
}
return FAILURE;
}
/* \brief Continues a paused counting operation.
*
* \param *cntrData provides access to a monitor's data
*/
static int MonContinue(CounterDriver *cntrData) {
BeamMon *self = NULL;
self = (BeamMon *) cntrData->pData;
if (MonSend(cntrData, "SICS CONTINUE",
self->buffer.body, sizeof(self->buffer.body)) == SUCCESS) {
self->state = HWBusy;
return SUCCESS;
}
return FAILURE;
}
/** \brief Cancels a counting operation. This is an emergency stop used when interrupting an operation.
* \param *cntrData provides access to a monitor's data
*/
static int MonHalt(CounterDriver *cntrData) {
BeamMon *self = NULL;
self = (BeamMon *) cntrData->pData;
if (MonSend(cntrData, "SICS STOP",
self->buffer.body, sizeof(self->buffer.body)) == SUCCESS) {
self->state = HWBusy;
return SUCCESS;
}
return FAILURE;
}
/** \brief Reads the counter and the monitors in the lCounts array.
*
* \param *cntrData provides access to a monitor's data
*/
static int MonReadValues(CounterDriver *cntrData) {
BeamMon *self = NULL;
flog('.', "MonReadValues");
self = (BeamMon *) cntrData->pData;
if (MonSend(cntrData, "SICS READ",
self->buffer.body, sizeof(self->buffer.body)) == SUCCESS) {
HandleReport(cntrData, &self->buffer);
lCounts[0] = self->counter_value;
}
return SUCCESS;
}
/* \brief Called when an error condition is reported by a counter operation.
*
* \param *cntrData provides access to a monitor's data
* \param *iCode error code returned to logical counter.
* \param *error error message
* \param iErrLen maximum error message length allowed.
* \return SUCCESS or FAILURE
*/
static int MonGetError(CounterDriver *cntrData, int *iCode, char *error, int iErrLen) {
BeamMon *self = NULL;
flog('.', "MonGetError");
self = (BeamMon *) cntrData->pData;
/* Allocate iErrLen bytes for error messages */
if (self->errorMsg == NULL) {
self->errorMsg = (char *) malloc(iErrLen);
if (self->errorMsg == NULL) {
return FAILURE;
}
}
error = self->errorMsg;
*iCode = self->errorCode;
switch (*iCode) {
case 0:
strncpy(error, "UNKNOWN ERROR: counterdriv did not set an errorcode.", iErrLen);
break;
case NOTCONNECTED:
case TIMEOUT:
case BADSEND:
case BADMEMORY:
case INCOMPLETE:
getRS232Error(*iCode, error, iErrLen);
break;
default:
snprintf(error, iErrLen, "Unknown error code: %d, returned from counterdriv", *iCode);
}
self->errorCode = 0;
return SUCCESS;
}
/* \brief Tries to fix problem associated with iCode error reported by MonGetError.
*
* \param *cntrData provides access to a monitor's data
* \param *iCode error code from MonGetError.
* \return This code tells the logical counter if an error is recoverable.
* - COREDO try to redo the last count operation.
* - COTERM count operation failed, give up.
*/
static int MonTryAndFixIt(CounterDriver *cntrData, int iCode) {
BeamMon *self = NULL;
flog('.', "MonTryAndFixIt");
self = (BeamMon *) cntrData->pData;
assert(self != NULL);
switch(iCode){
case NOTCONNECTED:
initRS232(self->controller);
return COREDO;
case BADSEND:
case TIMEOUT:
case BADMEMORY: /* Won't happen if MonConnect sets the send terminator */
case INCOMPLETE:
return COREDO;
}
return COTERM;
}
/*\brief Sets a parameter to a given value.
*
* \param *cntrData provides access to a monitor's data
* \param *name the name of the parameter to set.
* \param iCter counter to set
*/
static int MonSet(CounterDriver *cntrData, char *name, int iCter, float fVal) {
BeamMon *self = NULL;
char str[100];
snprintf(str, 100, "MonSet(%s, %d, %f)", name, iCter, fVal);
flog('.', str);
self = (BeamMon *) cntrData->pData;
if(strcmp(name,"threshold") == 0){
//TODO set threshold
self->dummy_threshold = fVal;
}
/* TODO*/
return SUCCESS;
}
static int MonGet(CounterDriver *cntrData, char *name, int iCter, float *fVal) {
BeamMon *self = NULL;
self = (BeamMon *) cntrData->pData;
if(strcasecmp(name,"threshold") == 0){
//TODO get threshold
*fVal = self->dummy_threshold;
}
/* TODO*/
char str[100];
snprintf(str, 100, "MonGet(%s, %d, %f)", name, iCter, *fVal);
flog('.', str);
return SUCCESS;
}
static int MonSend(CounterDriver *cntrData, char *pText, char *pReply, int iReplyLen) {
BeamMon *self = NULL;
int status;
self = (BeamMon *) cntrData->pData;
MonDrainInput(cntrData);
MonWrite(self, pText);
if ((status = MonRead(self, pReply, &iReplyLen)) == SUCCESS)
{
pReply[iReplyLen] = '\0';
return SUCCESS;
}
return status;
}
static void KillMon(pCounterDriver cntrData) {
pBeamMon self = NULL;
self = (pBeamMon) cntrData->pData;
if (self) {
if (self->host)
free(self->host);
if (self->errorMsg)
free(self->errorMsg);
}
}
/*@only@*/ prs232 createRS232(char *host, int iPort);
/** \brief Open a connection to the motor controller
* \param *pCon (r) connection object.
* \param *host (r) Beam Monitor host address or name.
* \param port Beam Monitor port number
* \return controller structure
*/
/*@null@*/ /*@only@*/ static prs232 MonConnect(/*@dependent@*/SConnection *pCon, char *host, int port) {
prs232 controller=NULL;
char pError[ERRLEN];
int msecTimeout = 5000; /* 5000msec timeout */
controller=createRS232(host,port);
if (controller==NULL) {
snprintf(pError, ERRLEN,
"ERROR: failed to create controller for %s at port %d",
host, port);
SCWrite(pCon,pError,eError);
return NULL;
}
if ( initRS232(controller) != 1) {
snprintf(pError, ERRLEN,
"ERROR: failed to connect to %s at port %d",
host, port);
SCWrite(pCon,pError,eError);
KillRS232(controller);
return NULL;
}
setRS232ReplyTerminator(controller,"\n");
setRS232SendTerminator(controller,"\r\n");
setRS232Timeout(controller, msecTimeout);
return controller;
}
/*@observer@*//*@null@*/ pCounterDriver CreateMonCounter(/*@observer@*/SConnection *pCon, /*@observer@*/char *name, char *params) {
BeamMon *newCtr = NULL;
pCounterDriver pCntDriv = NULL;
char *pPtr = NULL;
Tcl_Interp *interp;
interp = InterpGetTcl(pServ->pSics);
newCtr = (BeamMon *)malloc(sizeof(BeamMon));
if(NULL == newCtr){
(void) SCWrite(pCon,"ERROR: no memory to allocate counter driver",
eError);
return NULL;
}
newCtr->controller = NULL;
newCtr->state = HWIdle;
newCtr->errorCode = 0;
newCtr->errorMsg = NULL;
newCtr->host = NULL;
newCtr->iPort = 0;
newCtr->dummy_threshold = 0;
pCntDriv = CreateCounterDriver(name, "anstomonitor");
if(!pCntDriv) {
free(newCtr);
return NULL;
}
pCntDriv->GetStatus = MonGetStatus;
pCntDriv->Start = MonStart;
pCntDriv->Pause = MonPause;
pCntDriv->Continue = MonContinue;
pCntDriv->Halt = MonHalt;
pCntDriv->ReadValues = MonReadValues;
pCntDriv->GetError = MonGetError;
pCntDriv->TryAndFixIt = MonTryAndFixIt;
pCntDriv->Set = MonSet;
pCntDriv->Get = MonGet;
pCntDriv->Send = MonSend;
pCntDriv->iNoOfMonitors = 1;
memset(newCtr, 0, sizeof(BeamMon));
newCtr->state = HWIdle;
/*@-mustfreeonly@ pData = NULL after CreateCounterDriver is called */
pCntDriv->pData = newCtr;
/*@+mustfreeonly@*/
/* Get hostname and port from the list of named parameters */
if ((pPtr=getParam(pCon, interp, params,"port",1)) == NULL) {
KillMon(pCntDriv);
return NULL;
}
sscanf(pPtr,"%d", &newCtr->iPort);
if ((pPtr=getParam(pCon, interp, params,"host",1)) == NULL) {
KillMon(pCntDriv);
return NULL;
}
newCtr->host = strdup(pPtr);
newCtr->controller = MonConnect(pCon, newCtr->host, newCtr->iPort);
newCtr->dummy_threshold = 1.7e6;
return pCntDriv;
}