Histmem now only calls TaskYield after SICS has finished initalising, this stops the statusfile task from being called early and overwriting the status file. Created ANSTO_MakeHistMemory command to install ANSTO_HistAction Added veto functions. Mapped the HistDriver interface Pause() and Continue() commands to AnstoHttpVeto and AnstoHttpNoVeto. Implemented an ANSTO_Histaction to call AnstoHttpPause when sent a "pause" subcommand. hmcontrol_ansto.c Call AnstoHttpPause() directly when Pause_HM_After_Count is set to preserver current behaviour. counterdriv.c Now sends the correct "SICS RESUME" command to resume a paused count. ansto_sctdriveadapter.c NEW This lets you create driveable objects from script-context controllers which have one node for setting a parameter and another node for reading the parameter (eg temperature controllers, choppers, velocity selectors) sctemonadapter.c NEW This generates an environment monitor interface for script-context controllers so that the emon object in SICS will be able to pause counters if the controller goes out of tolerance. sct_usbtmcprot.c, usbtmc.h NEW First attempt at a protocol handler for USB Test and Measurement Class devices sct_julabo_lh45.tcl NEW Implements script-context controller for the Julabo LH45 temperature controller. Makefile Added ansto_sctdriveadapter and sctemonadapter hardsup/makefile Added velocity selector and usbtmc protocol handlers hardsup/sct_velselprot.c Simplified, don't worry about trying to implement a login handler for now, just implement a "Reading" handler. site_ansto.c Add the velocity selector and USBTMC protocol handlers. Added the ANSTO_MakeHM command instrument/config/hipadaba/common_instrument_dictionary.tcl Added support for auxiliary data, ie extra meta-data entries in the "data" group of the nexus file. Add new NXvelocity_selector object under /instrument. instrument/config/hipadaba/hipadaba_configuration_common.tcl Add the new "sct_object" script-context controller objects to the hdb tree. instrument/config/hipadaba/instdict_specification.tcl Define the new sct_object controllers. instrument/config/hmm/hmm_configuration_common_1.tcl Use the new ANSTO_MakeHM command to create histmem drivers which support veto. Define allowed attributes and elements for the BAT_TABLE and FAT_TABLE Stop between counts instead of pausing because setting pause now sends a veto. instrument/config/motors/sct_jogmotor_common.tcl You now need to specify klass when creating a jogmotor. instrument/config/motors/sct_positmotor_common.tcl You can now specify an optional function which calculates the instrument parameter (eg attenuation) from a posit table entry when defining a posit motor. instrument/config/nexus/nxscripts_common_1.tcl Report file status info in the /experiment section of the hdb tree. Add auxiliary data to data file. Handle saving data from script-context controller objects. instrument/util/script_context_util.tcl Added procedure to set required properties for saving script-context object data. instrument/util/utility.tcl Added set_sct_object_attributes proc to automatically set required attributes on SCT_OBJECTs Fixed hlistplainprop to deal with empty property fields on hdb nodes. instrument/server_config.tcl Call the new nexus initialisation command and set attributes on sct objects. hrpd/config/motors/motor_configuration.tcl New absenc home readings for mchi and mphi. New absenc home and range for mf1 hipd/config/commands/commands.tcl Exported and published the new ajscmds so that they can be used in batch files. hipd/config/motors/motor_configuration.tcl New mchi absenc home rsd/config/hmm/hmm_configuration.tcl Provide support for saving corrected hmm data rsd/config/motors/motor_configuration.tcl Use simple names for motors. SICS-329 sans/config/INSTCFCOMMON.TXT Added julabo and lakeshore configuration files to list. sans/config/optics/guide_configuration.tcl Added entrance aperture positions to the configuration table and the cn_maps which map the index to the component ID. sans/commands/commands.tcl The "guide" command now set EApPosYmm after driving the guides in place. The indexed position to component maps (cn_map) have been moved to the guide_configuration.tcl file. sans/motors/motor_configuration.tcl Set samy home to 56.1mm sans/config/velsel/sct_velsel.tc NEW Implements script-context controller object for the NVS40 velocity selector. TODO tilt-angle control, driveable interface. sans/config/hmm/hmm_configuration.tcl Set 5.08mm spacing on detector width. Use pixel-offset for vertical and horizontal detector axes. sans/config/motors/motor_configuration.tcl Swap directions of beamstops 4 and 5 (the two smallest) New config parameters for samx, samthet, apx, det, detoff, bsz, sans/config/motors/positmotor_configuration.tcl Added descriptive headers to positmotor configuration tables and new synstax for the make positmotor command. sans/config/nexus/nxscripts.tcl Implemented initialisation command. sans/config/parameters/parameters.tcl Added SampleThickness and TransmissionFlag. Calculate SamplePosYmm from samy and SamyOffsetmm. Added beamstops to hdb tree. reflectometer/config/nexus/nxscripts.tcl Implement the initialisation procedure. r2767 | ffr | 2009-03-31 10:16:54 +1100 (Tue, 31 Mar 2009) | 123 lines
952 lines
27 KiB
C
952 lines
27 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 <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include "fortify.h"
|
|
#include <string.h>
|
|
#include <sics.h>
|
|
#include <rs232controller.h>
|
|
#include <countdriv.h>
|
|
#include "anstoutil.h"
|
|
|
|
#ifdef SPLINT
|
|
/*@-incondefs@*/
|
|
int gettimeofday(/*@out@*/ struct timeval *restrict tp,
|
|
/*@null@*/ void *restrict tzp);
|
|
char *strdup(const char *s);
|
|
int strcasecmp(const char *s1, const char *s2);
|
|
int strncasecmp(const char *s1, const char *s2, size_t n);
|
|
unsigned long long int
|
|
strtoull(const char *nptr, /*@null@*/ char **endptr, int base);
|
|
/*@+incondefs@*/
|
|
#endif
|
|
|
|
/*@-incondefs@*/
|
|
/*@observer@*//*@dependent@*/ Tcl_Interp *InterpGetTcl(SicsInterp *pSics);
|
|
int readRS232(prs232 self, /*@out@*/void *data, /*@out@*/int *dataLen);
|
|
int readRS232TillTerm(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 unsigned long long int uint64;
|
|
|
|
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;
|
|
uint64 counter_value;
|
|
int debug;
|
|
} BeamMon, *pBeamMon;
|
|
|
|
/** \brief file logging
|
|
*
|
|
* Logs text to a debug file. The file is opened and closed for each call.
|
|
*
|
|
* \param self the beam monitor counter object
|
|
* \param format printf style format string
|
|
* \param ... printf style variable argument list
|
|
*/
|
|
static void flog(pBeamMon self, char* format, ...) {
|
|
if (self->debug) {
|
|
va_list ap;
|
|
FILE* file;
|
|
int iRet;
|
|
char buffer[2048];
|
|
|
|
file = fopen("flog.txt", "a");
|
|
if (file) {
|
|
struct timeval tv;
|
|
(void) gettimeofday(&tv, NULL);
|
|
fprintf(file, "%02ld:%02ld:%02ld:%06ld::",
|
|
tv.tv_sec % (24 * 3600) / 3600,
|
|
tv.tv_sec % (3600) / 60,
|
|
tv.tv_sec % 60,
|
|
tv.tv_usec);
|
|
va_start(ap, format);
|
|
iRet = vsnprintf(buffer, sizeof(buffer), format, ap);
|
|
va_end(ap);
|
|
fputs(buffer, file);
|
|
if (buffer[iRet - 1] != '\n')
|
|
(void) fputc('\n', file);
|
|
(void) fclose(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \brief Check connection for remote disconnection
|
|
*
|
|
* \param self provides access to a monitor's data
|
|
* \return SUCCESS or FAILURE
|
|
*/
|
|
static int checkConnection(pBeamMon self)
|
|
{
|
|
int status;
|
|
char buf[1];
|
|
|
|
/*
|
|
* Make sure that it the structure looks reasonable
|
|
*/
|
|
if (self && self->controller && self->controller->pSock) {
|
|
int sock;
|
|
/*
|
|
* Make sure that the socket looks reasonable
|
|
*/
|
|
sock = self->controller->pSock->sockid;
|
|
if (sock < 0 || sock >= FD_SETSIZE) {
|
|
self->errorCode = NOTCONNECTED;
|
|
return FAILURE;
|
|
}
|
|
/*
|
|
* Q: How do you tell that a connection is broken?
|
|
* A: Read it or Write it and see what happens!
|
|
*/
|
|
if (availableRS232(self->controller) != 0) {
|
|
status = recv(sock, buf, 1, MSG_PEEK);
|
|
if (status < 0) {
|
|
if (errno == EAGAIN) {
|
|
/* no data to read, otherwise OK */
|
|
return SUCCESS;
|
|
}
|
|
flog(self, "#::counter recv = %d, errno = %d:%s\n", status, errno, strerror(errno));
|
|
flog(self, "#::Channel Error!");
|
|
self->errorCode = BADREAD;
|
|
return FAILURE;
|
|
} else if (status == 0) {
|
|
flog(self, "#::Channel Disconnected!");
|
|
self->errorCode = NOTCONNECTED;
|
|
return FAILURE;
|
|
}
|
|
} else {
|
|
status = send(sock, NULL, 0, 0);
|
|
if (status < 0) {
|
|
if (errno == EAGAIN) {
|
|
flog(self, "#::Unexpected EAGAIN\n");
|
|
return SUCCESS;
|
|
} else if (errno == EPIPE) {
|
|
flog(self, "#::Unexpected EPIPE\n");
|
|
self->errorCode = NOTCONNECTED;
|
|
return FAILURE;
|
|
} else {
|
|
flog(self, "#::Unexpected error %d: %s\n", errno, strerror(errno));
|
|
self->errorCode = NOTCONNECTED;
|
|
return FAILURE;
|
|
}
|
|
}
|
|
}
|
|
/* otherwise OK */
|
|
return SUCCESS;
|
|
}
|
|
/* something is not connected */
|
|
self->errorCode = NOTCONNECTED;
|
|
return FAILURE;
|
|
}
|
|
|
|
/** \brief try to fix connection for remote disconnection
|
|
*
|
|
* \param self provides access to a monitor's data
|
|
* \return SUCCESS or FAILURE
|
|
*/
|
|
static int fixConnection(pBeamMon self)
|
|
{
|
|
int status;
|
|
|
|
flog(self, "_::fixConnection");
|
|
if (self && self->controller) {
|
|
if (self->errorCode != NOTCONNECTED) {
|
|
flog(self, "!::unbroken");
|
|
return FAILURE;
|
|
}
|
|
status = initRS232(self->controller);
|
|
if (status == SUCCESS) {
|
|
fd_set rmask, wmask;
|
|
struct timeval tmo = {1,0};
|
|
int iret;
|
|
int sock = self->controller->pSock->sockid;
|
|
if (sock < 0 || sock > FD_SETSIZE) {
|
|
flog(self, "!::bad socket");
|
|
return FAILURE;
|
|
}
|
|
FD_ZERO(&wmask);
|
|
FD_ZERO(&rmask);
|
|
FD_SET(sock, &wmask);
|
|
FD_SET(sock, &rmask);
|
|
iret = select(sock+1, &rmask, &wmask, NULL, &tmo);
|
|
if (iret < 0) {
|
|
flog(self, "!::%s", strerror(errno));
|
|
return FAILURE; /* some sort of error */
|
|
} else if (iret == 0) {
|
|
flog(self, "!::timeout");
|
|
return FAILURE; /* timed out */
|
|
} else {
|
|
if (FD_ISSET(sock, &wmask)) { /* should always be true */
|
|
if (FD_ISSET(sock, &rmask)) { /* there may already be data for read */
|
|
char buff[1];
|
|
iret = recv(sock, buff, 1, MSG_PEEK); /* just peek */
|
|
if (iret == 0 || (iret < 0 && errno != EAGAIN)) {
|
|
flog(self, "!::disconnected");
|
|
return FAILURE; /* disconnected */
|
|
}
|
|
}
|
|
iret = send(sock, NULL, 0, 0); /* zero length, check only return value */
|
|
if (iret < 0) {
|
|
flog(self, "!::%s", strerror(errno));
|
|
return FAILURE; /* some sort of error */
|
|
}
|
|
}
|
|
flog(self, "!::success");
|
|
return SUCCESS;
|
|
}
|
|
} else {
|
|
flog(self, "!::initRS232 failed");
|
|
return FAILURE;
|
|
}
|
|
} else {
|
|
flog(self, "!::no initRS232 controller");
|
|
return FAILURE;
|
|
}
|
|
flog(self, "!::fallthrough");
|
|
return FAILURE;
|
|
}
|
|
|
|
/** \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 or FAILURE
|
|
*/
|
|
static int MonWrite(pBeamMon self, char* text) {
|
|
size_t len;
|
|
int status;
|
|
|
|
len = strlen(text);
|
|
if (len > 0)
|
|
flog(self, ">::%s", text);
|
|
|
|
status = writeRS232(self->controller, text, (int) len);
|
|
if (status != 1) {
|
|
flog(self, "#::MonWrite status = %d, errno = %d\n", status, errno);
|
|
self->errorCode = status;
|
|
if (status == BADSEND && errno == EPIPE)
|
|
self->errorCode = NOTCONNECTED;
|
|
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 or FAILURE
|
|
*/
|
|
static int MonRead(pBeamMon self, /*@out@*/ char* text, int *pLen) {
|
|
int i, status, retries=2;
|
|
|
|
*text = '\0';
|
|
for (i = 0; i <= retries; i++) {
|
|
status = readRS232TillTerm(self->controller, text, pLen);
|
|
switch (status) {
|
|
case 1:
|
|
if (*pLen > 0)
|
|
flog(self, "<::%s", text);
|
|
return SUCCESS;
|
|
case TIMEOUT:
|
|
/*
|
|
* Disconnection of the socket is treated as a timeout.
|
|
*
|
|
* Determine if this is really a socket diconnection.
|
|
*/
|
|
if (checkConnection(self) == FAILURE) {
|
|
return FAILURE;
|
|
}
|
|
|
|
flog(self, "<::*TIMEOUT* %d", self->controller->timeout);
|
|
self->errorCode = status;
|
|
continue;
|
|
default:
|
|
flog(self, "<::*ERROR*");
|
|
self->errorCode = status;
|
|
return FAILURE;
|
|
}
|
|
}
|
|
flog(self, "<::***TIMEOUT*** %d", self->controller->timeout);
|
|
return FAILURE;
|
|
}
|
|
|
|
/** \brief drains any pending input on the RS232 channel
|
|
*
|
|
* \param *cntrData provides access to a monitor's data
|
|
*/
|
|
static void MonDrainInput(CounterDriver *cntrData) {
|
|
int iRet;
|
|
int len;
|
|
char reply[1024];
|
|
BeamMon* self = NULL;
|
|
self = (BeamMon *) cntrData->pData;
|
|
while (availableRS232(self->controller) != 0) {
|
|
len = (int) sizeof(reply);
|
|
iRet = MonRead(self, reply, &len);
|
|
if (iRet == FAILURE) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \brief sends a command to the monitor and gets the reply
|
|
*
|
|
* \param cntrData provides access to a monitor's data
|
|
* \param pText command to send
|
|
* \param pReply space for reply
|
|
* \param iReplyLen length of space for reply
|
|
* \return SUCCESS or FAILURE
|
|
*/
|
|
static int MonSend(CounterDriver *cntrData, char *pText, char *pReply, int iReplyLen) {
|
|
BeamMon *self = NULL;
|
|
int status;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
MonDrainInput(cntrData);
|
|
if (self->errorCode == NOTCONNECTED) {
|
|
status = fixConnection(self);
|
|
}
|
|
status = MonWrite(self, pText);
|
|
/* try to recover the connection if it is broken */
|
|
if (status != SUCCESS && self->errorCode == NOTCONNECTED) {
|
|
status = fixConnection(self);
|
|
if (status == SUCCESS)
|
|
status = MonWrite(self, pText);
|
|
}
|
|
if (status != SUCCESS)
|
|
return status;
|
|
if ((status = MonRead(self, pReply, &iReplyLen)) == SUCCESS)
|
|
{
|
|
pReply[iReplyLen] = '\0';
|
|
return SUCCESS;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/** \brief sends a command to the monitor and gets the reply
|
|
*
|
|
* \param cntrData provides access to a monitor's data
|
|
* \param pText command to send
|
|
* \param buffer space for reply
|
|
* \return SUCCESS or FAILURE
|
|
*/
|
|
static int MonSendBuffer(CounterDriver *cntrData, char *pText, BUFFER* buffer) {
|
|
int status;
|
|
buffer->length = 0;
|
|
status = MonSend(cntrData, pText, buffer->body, (int) sizeof(buffer->body));
|
|
if (status == SUCCESS) {
|
|
buffer->length = (int) strlen(buffer->body);
|
|
}
|
|
return status;
|
|
}
|
|
/** \brief parses and acts upon the READ line
|
|
*
|
|
* \param *cntrData provides access to a monitor's data
|
|
* \param bp pointer to buffer containing the text line
|
|
*
|
|
* This function sets the state of the counter based on state flags reported by
|
|
* the monitor.
|
|
*/
|
|
static void HandleReport(CounterDriver *cntrData, BUFFER* bp)
|
|
{
|
|
//READ xxxx 00:00:00.000000 0.000000 0 0.00
|
|
//0123456789012345678901234567890
|
|
BeamMon* self = NULL;
|
|
char *cp = NULL;
|
|
char *ep = NULL;
|
|
self = (BeamMon *) cntrData->pData;
|
|
/* TODO better than this */
|
|
cp = &bp->body[26];
|
|
cntrData->fTime = (float) 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: /* should not happen */
|
|
self->state = HWFault;
|
|
break;
|
|
}
|
|
if ((self->state == HWBusy || self->state == HWPause)
|
|
&& (toupper(bp->body[8]) == (int) 'G')) /* Gated */
|
|
self->state = HWNoBeam;
|
|
self->counter_value = strtoull(cp, &ep, 10);
|
|
cntrData->fLastCurrent = (float) self->counter_value;
|
|
flog(self, ".::READ %s, %f, %f, %f\n",
|
|
cntrData->eMode == eTimer ? "eTimer" :
|
|
cntrData->eMode == ePreset ? "ePreset" : "Unknown",
|
|
cntrData->fPreset,
|
|
cntrData->fLastCurrent,
|
|
cntrData->fTime);
|
|
}
|
|
|
|
/** \brief handles an input line received on the RS232 channel
|
|
*
|
|
* \param cntrData provides access to a monitor's data
|
|
* \param bp pointer to buffer containing the line of input
|
|
*
|
|
* This function sets the state of the counter based on events reported by
|
|
* the monitor. The event format is "EVENT <timestamp> <event type>"
|
|
*/
|
|
static void MonHandleInput(CounterDriver *cntrData, BUFFER* bp) {
|
|
BeamMon* self = NULL;
|
|
const char et[] = " TERMINAL";
|
|
const char eri[] = "RANGE IN";
|
|
const char ero[] = "RANGE OUT";
|
|
const char rpt[] = "READ";
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
flog(self, "=::%s", bp->body);
|
|
if (strncasecmp(bp->body, "EVENT", 5) == 0) {
|
|
char* cp = bp->body + 5;
|
|
while (*cp && isspace(*cp)) // skip space
|
|
++cp;
|
|
while (*cp && !isspace(*cp)) // skip timestamp
|
|
++cp;
|
|
while (*cp && isspace(*cp)) // skip space
|
|
++cp;
|
|
if (strncasecmp(cp, et, sizeof(et) - 1) == 0) {
|
|
if (self->state == HWBusy)
|
|
self->state = HWIdle;
|
|
}
|
|
else if (strncasecmp(cp, eri, sizeof(eri) - 1) == 0) {
|
|
if (self->state == HWPause)
|
|
self->state = HWBusy;
|
|
}
|
|
else if (strncasecmp(cp, ero, sizeof(ero) - 1) == 0) {
|
|
if (self->state == HWBusy)
|
|
self->state = HWPause;
|
|
}
|
|
}
|
|
else if (strncasecmp(bp->body, rpt, sizeof(rpt) - 1) == 0) {
|
|
HandleReport(cntrData, &self->buffer);
|
|
}
|
|
bp->length = 0;
|
|
}
|
|
|
|
/** \brief look for input on the socket
|
|
*
|
|
* breaks input into lines terminated by <CR><LF> pairs and passes complete lines to MonHandleInput for processing.
|
|
*
|
|
* \param cntrData provides access to a monitor's data
|
|
* \param timeout upper limit on time to wait for data to arrive
|
|
*/
|
|
static void MonLookForInput(CounterDriver *cntrData, /*@unused@*/ int timeout) {
|
|
BeamMon* self = NULL;
|
|
int iRet, len;
|
|
int iLen = 0;
|
|
int jLen = 0;
|
|
char reply[1024];
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
while (availableRS232(self->controller) != 0) {
|
|
len = (int) sizeof(reply);
|
|
iRet = MonRead(self, reply, &len);
|
|
if (iRet == FAILURE) {
|
|
return;
|
|
}
|
|
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],
|
|
(size_t) 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \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 = MonSendBuffer(cntrData, "SICS READ", &self->buffer);
|
|
if (status == SUCCESS) {
|
|
MonHandleInput(cntrData, &self->buffer);
|
|
} else {
|
|
/* TODO */
|
|
}
|
|
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
|
|
* \return SUCCESS or FAILURE
|
|
*
|
|
* This function also sets some of the parameters in the monitor for mode and
|
|
* preset.
|
|
*
|
|
* TODO: check the return from the monitor
|
|
*/
|
|
static int MonStart(CounterDriver *cntrData) {
|
|
BeamMon *self = NULL;
|
|
int status;
|
|
char str[100];
|
|
self = (BeamMon *) cntrData->pData;
|
|
flog(self, ".::START %s, %f, %f, %f\n",
|
|
cntrData->eMode == eTimer ? "eTimer" :
|
|
cntrData->eMode == ePreset ? "ePreset" : "Unknown",
|
|
cntrData->fPreset,
|
|
cntrData->fLastCurrent,
|
|
cntrData->fTime);
|
|
MonDrainInput(cntrData);
|
|
if (cntrData->eMode == eTimer) {
|
|
status = MonSendBuffer(cntrData, "SICS SET TE_CHECK=TIMER", &self->buffer);
|
|
if (status != SUCCESS || strcasecmp(self->buffer.body, "OK") != 0) {
|
|
self->errorCode = /* TODO */ 0;
|
|
return FAILURE;
|
|
}
|
|
} else {
|
|
status = MonSendBuffer(cntrData, "SICS SET TE_CHECK=COUNTER", &self->buffer);
|
|
if (status != SUCCESS || strcasecmp(self->buffer.body, "OK") != 0) {
|
|
self->errorCode = /* TODO */ 0;
|
|
return FAILURE;
|
|
}
|
|
}
|
|
/*@-duplicatequals@ for uint64 */
|
|
/*@-formattype@ for uint64 */
|
|
(void) snprintf(str, sizeof(str), "SICS SET TERMINAL=%llu",
|
|
(uint64) cntrData->fPreset);
|
|
/*@+formattype@*/
|
|
/*@+duplicatequals@*/
|
|
status = MonSendBuffer(cntrData, str, &self->buffer);
|
|
if (status != SUCCESS || strcasecmp(self->buffer.body, "OK") != 0) {
|
|
self->errorCode = /* TODO */ 0;
|
|
return FAILURE;
|
|
}
|
|
status = MonSendBuffer(cntrData, "SICS START", &self->buffer);
|
|
if (status != SUCCESS || strcasecmp(self->buffer.body, "OK") != 0) {
|
|
self->errorCode = /* TODO */ 0;
|
|
return FAILURE;
|
|
}
|
|
self->state = HWBusy;
|
|
return SUCCESS;
|
|
}
|
|
|
|
/** \brief Pauses a counting operation
|
|
*
|
|
* \param *cntrData provides access to a monitor's data
|
|
* \return SUCCESS or FAILURE
|
|
*
|
|
* TODO: check if we should set the state to HWPause or just leave it till we
|
|
* get it back from the monitor.
|
|
*/
|
|
static int MonPause(CounterDriver *cntrData) {
|
|
BeamMon *self = NULL;
|
|
int status;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
status = MonSendBuffer(cntrData, "SICS PAUSE", &self->buffer);
|
|
if (status == SUCCESS) {
|
|
self->state = HWPause;
|
|
return SUCCESS;
|
|
}
|
|
return FAILURE;
|
|
}
|
|
|
|
/* \brief Continues a paused counting operation.
|
|
*
|
|
* \param *cntrData provides access to a monitor's data
|
|
* \return SUCCESS or FAILURE
|
|
*
|
|
* TODO: check if we should set the sate to HWBusy or just leave it till we
|
|
* get it back from the monitor.
|
|
*/
|
|
static int MonContinue(CounterDriver *cntrData) {
|
|
BeamMon *self = NULL;
|
|
int status;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
status = MonSendBuffer(cntrData, "SICS RESUME", &self->buffer);
|
|
if (status == 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
|
|
* \return SUCCESS or FAILURE
|
|
*
|
|
* TODO: check if we should set the sate to HWIdle or just leave it till we
|
|
* get it back from the monitor.
|
|
*/
|
|
static int MonHalt(CounterDriver *cntrData) {
|
|
BeamMon *self = NULL;
|
|
int status;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
status = MonSendBuffer(cntrData, "SICS STOP", &self->buffer);
|
|
if (status == SUCCESS) {
|
|
self->state = HWIdle;
|
|
return SUCCESS;
|
|
}
|
|
return FAILURE;
|
|
}
|
|
|
|
/** \brief Reads the counter and the monitors in the lCounts array.
|
|
*
|
|
* \param *cntrData provides access to a monitor's data
|
|
* \return SUCCESS or FAILURE
|
|
*/
|
|
static int MonReadValues(CounterDriver *cntrData) {
|
|
BeamMon *self = NULL;
|
|
int status;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
flog(self, ".::MonReadValues");
|
|
status = MonSendBuffer(cntrData, "SICS READ", &self->buffer);
|
|
if (status == SUCCESS) {
|
|
HandleReport(cntrData, &self->buffer);
|
|
cntrData->lCounts[0] = (long) self->counter_value; /* NOTE: truncation */
|
|
return SUCCESS;
|
|
}
|
|
return FAILURE;
|
|
}
|
|
|
|
/* \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;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
/* Allocate iErrLen bytes for error messages */
|
|
if (self->errorMsg != NULL) {
|
|
free(self->errorMsg);
|
|
self->errorMsg = NULL;
|
|
}
|
|
*iCode = self->errorCode;
|
|
flog(self, ".::MonGetError: %d", self->errorCode);
|
|
switch (*iCode) {
|
|
case 0:
|
|
if (checkConnection(self) == FAILURE) {
|
|
if (self->errorCode == NOTCONNECTED) {
|
|
strncpy(error,
|
|
"DISCONNECT ERROR: counterdriv channel disconnected.",
|
|
(size_t) iErrLen);
|
|
} else {
|
|
strncpy(error,
|
|
"SOCKET ERROR: counterdriv socket in error.",
|
|
(size_t) iErrLen);
|
|
}
|
|
*iCode = self->errorCode;
|
|
break;
|
|
}
|
|
strncpy(error,
|
|
"UNKNOWN ERROR: counterdriv did not set an errorcode.",
|
|
(size_t) iErrLen);
|
|
break;
|
|
case NOTCONNECTED:
|
|
strncpy(error,
|
|
"DISCONNECT ERROR: counterdriv channel not connected.",
|
|
(size_t) iErrLen);
|
|
break;
|
|
case TIMEOUT:
|
|
case BADREAD:
|
|
case BADSEND:
|
|
case BADMEMORY:
|
|
case INCOMPLETE:
|
|
getRS232Error(*iCode, error, iErrLen);
|
|
break;
|
|
default:
|
|
(void) snprintf(error, (size_t) iErrLen,
|
|
"Unknown error code: %d, returned from counterdriv",
|
|
*iCode);
|
|
break;
|
|
}
|
|
flog(self, "#::MonGetError: %d", *iCode);
|
|
flog(self, "#::%s", error);
|
|
self->errorMsg = strdup(error);
|
|
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;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
flog(self, ".::MonTryAndFixIt");
|
|
assert(self != NULL);
|
|
|
|
switch(iCode){
|
|
case 0: /* Unknown error */
|
|
return COTERM;
|
|
case NOTCONNECTED:
|
|
if (initRS232(self->controller) == 1)
|
|
return COREDO;
|
|
return COTERM;
|
|
case BADREAD:
|
|
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;
|
|
|
|
self = (BeamMon *) cntrData->pData;
|
|
flog(self, ".::MonSet(%s, %d, %f)", name, iCter, fVal);
|
|
if(strcmp(name,"threshold") == 0){
|
|
//TODO set threshold
|
|
self->dummy_threshold = fVal;
|
|
}
|
|
else if(strcmp(name,"debug") == 0){
|
|
//TODO set threshold
|
|
self->debug = 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;
|
|
}
|
|
else if(strcasecmp(name,"debug") == 0){
|
|
//TODO get threshold
|
|
*fVal = self->debug;
|
|
}
|
|
/* TODO*/
|
|
|
|
flog(self, ".::MonGet(%s, %d, %f)", name, iCter, *fVal);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static void KillMon(/*@null@*/ pCounterDriver cntrData) {
|
|
if (cntrData != NULL) {
|
|
if (cntrData->pData != NULL) {
|
|
pBeamMon self = (pBeamMon) cntrData->pData;
|
|
if (self->host) {
|
|
free(self->host);
|
|
self->host = NULL;
|
|
}
|
|
if (self->errorMsg) {
|
|
free(self->errorMsg);
|
|
self->errorMsg = NULL;
|
|
}
|
|
KillRS232(self->controller);
|
|
free(cntrData->pData);
|
|
cntrData->pData = NULL;
|
|
}
|
|
/* TODO free(cntrData); */
|
|
}
|
|
}
|
|
|
|
/*@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 = 1000; /* milliseconds timeout */
|
|
|
|
controller=createRS232(host,port);
|
|
if (controller==NULL) {
|
|
(void) snprintf(pError, ERRLEN,
|
|
"ERROR: failed to create controller for %s at port %d",
|
|
host, port);
|
|
(void) SCWrite(pCon,pError,eError);
|
|
return NULL;
|
|
}
|
|
if ( initRS232(controller) != 1) {
|
|
(void) snprintf(pError, ERRLEN,
|
|
"ERROR: failed to connect to %s at port %d",
|
|
host, port);
|
|
(void) SCWrite(pCon,pError,eError);
|
|
KillRS232(controller);
|
|
return NULL;
|
|
}
|
|
setRS232ReplyTerminator(controller,"\r\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;
|
|
}
|
|
(void) sscanf(pPtr,"%d", &newCtr->iPort);
|
|
|
|
if ((pPtr=getParam(pCon, interp, params,"host",1)) == NULL) {
|
|
KillMon(pCntDriv);
|
|
return NULL;
|
|
}
|
|
/*@-mustfreeonly@ host = NULL after BeamMon is created */
|
|
newCtr->host = strdup(pPtr);
|
|
/*@+mustfreeonly@*/
|
|
|
|
/*@-mustfreeonly@ controller = NULL after BeamMon is created */
|
|
newCtr->controller = MonConnect(pCon, newCtr->host, newCtr->iPort);
|
|
/*@+mustfreeonly@*/
|
|
|
|
newCtr->dummy_threshold = 1.7e6;
|
|
return pCntDriv;
|
|
}
|
|
|