required inclusion of a matrix package. - modified counter error handling to send a Stop when the _BAD_BUSY error is received. - added an environment interface to the general controller stuff in choco.* Also added setting a parameter directly at the controller object. - Added a driver for the ETH High Temperature Furnace to be used at SANS.
683 lines
16 KiB
C
683 lines
16 KiB
C
/*--------------------------------------------------------------------------
|
|
|
|
S A N S C O O K
|
|
|
|
This is a controller driver for a heaiting device developed especially
|
|
for use with SANS at SINQ by somebody at the Some God Forsaken University
|
|
(SGFU). As this device somes with two motors in addition to the heater
|
|
the general controller mechanism as described in choco.tex is used.
|
|
|
|
copyright: see copyright.h
|
|
|
|
Mark Koennecke, July 2000
|
|
---------------------------------------------------------------------------*/
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <time.h>
|
|
#include "fortify.h"
|
|
#include "sics.h"
|
|
#include "stringdict.h"
|
|
#include "hardsup/serialsinq.h"
|
|
#include "hardsup/el734_errcodes.h"
|
|
#include "hardsup/el734fix.h"
|
|
#include "codri.h"
|
|
|
|
/*-----------------------------------------------------------------------
|
|
A private data structure for the SANS cooker
|
|
-------------------------------------------------------------------------*/
|
|
typedef struct {
|
|
char *pHost;
|
|
int iPort;
|
|
int iChannel;
|
|
void *pData;
|
|
int iStop;
|
|
int iError;
|
|
} SANSCook, *pSANSCook;
|
|
/*
|
|
pHost, iPort and iChannel combined are the adress of the cooker
|
|
controller at the Macintosh terminal server. pData is the serial
|
|
port connection data structure needed and managed by the SerialIO
|
|
functions.
|
|
|
|
iError is the last error reported on this device. If no error: 0
|
|
|
|
-----------------------------------------------------------------------*/
|
|
|
|
/*
|
|
ERROR CODES:
|
|
*/
|
|
|
|
#define NOTINIT -9001
|
|
#define POWEROFF -9002
|
|
#define LOWRANGE -9003
|
|
#define HIGHRANGE -9004
|
|
#define CONTIMEOUT -9005
|
|
#define BADCOMMAND -9006
|
|
#define NOANSWER -9007
|
|
#define BADINPUT -9008
|
|
#define MOTERROR -9009
|
|
#define CONBUSY -9010
|
|
#define BADREPLY -9011
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
static int CookerInit(pCodri self)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
int iRet;
|
|
char pReply[131];
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
pPriv->iError = 0;
|
|
|
|
/* first open the connection to the serial port server and channel */
|
|
iRet = SerialOpen(&(pPriv->pData),pPriv->pHost,pPriv->iPort,
|
|
pPriv->iChannel);
|
|
if(iRet <= 0)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return 0;
|
|
}
|
|
/* configure the connection */
|
|
SerialATerm(&(pPriv->pData),"1\r\n");
|
|
SerialSendTerm(&(pPriv->pData),"\r");
|
|
|
|
pPriv->iStop = 0;
|
|
|
|
/* switch everything on, but do not deal with errors here */
|
|
/*
|
|
SerialWriteRead(&(pPriv->pData),"mpon",pReply,131);
|
|
SerialWriteRead(&(pPriv->pData),"mzon",pReply,131);
|
|
SerialWriteRead(&(pPriv->pData),"ton",pReply,131);
|
|
*/
|
|
|
|
return 1;
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
static int CookerClose(pCodri self)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
int iRet;
|
|
long lVal;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
|
|
if(pPriv->pData)
|
|
{
|
|
SerialClose(&(pPriv->pData));
|
|
pPriv->pData = NULL;
|
|
}
|
|
return 1;
|
|
}
|
|
/*-----------------------------------------------------------------------*/
|
|
static int CookerDelete(pCodri self)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
|
|
if(pPriv->pData)
|
|
{
|
|
SerialClose(&(pPriv->pData));
|
|
pPriv->pData = NULL;
|
|
}
|
|
|
|
if(pPriv->pHost)
|
|
free(pPriv->pHost);
|
|
|
|
free(pPriv);
|
|
|
|
return 1;
|
|
}
|
|
/*-----------------------------------------------------------------------*/
|
|
static int CookerSetPar(pCodri self, char *parname, float fValue)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
char pCommand[80], pReply[132];
|
|
int iRet, iValue;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
pPriv->iError = 0;
|
|
|
|
/* handle the numeric parameters */
|
|
iValue = (int)fValue;
|
|
|
|
if(strcmp(parname,"mp") == 0)
|
|
{
|
|
sprintf(pCommand,"mps%3.3d",iValue);
|
|
}
|
|
else if(strcmp(parname,"mz") == 0)
|
|
{
|
|
sprintf(pCommand,"mzs%3.3d",iValue);
|
|
}
|
|
else if(strcmp(parname,"ts") == 0)
|
|
{
|
|
sprintf(pCommand,"ts%3.3d",iValue);
|
|
}
|
|
else
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return 0;
|
|
}
|
|
/* send command and check for errors right here */
|
|
iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,131);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if(strstr(pReply,"Error") != NULL)
|
|
{
|
|
pPriv->iError = MOTERROR;
|
|
return 0;
|
|
}
|
|
if(strstr(pReply,"Power OFF") != NULL)
|
|
{
|
|
pPriv->iError = POWEROFF;
|
|
return 0;
|
|
}
|
|
if(strstr(pReply,"not init.") != NULL)
|
|
{
|
|
pPriv->iError = NOTINIT ;
|
|
return 0;
|
|
}
|
|
if(strstr(pReply,"Timeout") != NULL)
|
|
{
|
|
pPriv->iError = CONTIMEOUT ;
|
|
return 0;
|
|
}
|
|
if(strstr(pReply,"Busy") != NULL)
|
|
{
|
|
pPriv->iError = CONBUSY ;
|
|
return 0;
|
|
}
|
|
if( (strstr(pReply,"Max") != NULL) || (strstr(pReply,"high") != NULL) )
|
|
{
|
|
pPriv->iError = HIGHRANGE;
|
|
return 0;
|
|
}
|
|
if( (strstr(pReply,"Min") != NULL) || (strstr(pReply,"down") != NULL) )
|
|
{
|
|
pPriv->iError = LOWRANGE;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
static int CookerSetPar2(pCodri self, char *parname, char *pValue)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
char pCommand[80], pReply[132];
|
|
int iRet, iAct = 0;
|
|
float fValue;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
pPriv->iError = 0;
|
|
|
|
/* handle our parameters. The first set is the power settings */
|
|
if(strcmp(parname,"mp.power") == 0)
|
|
{
|
|
if(strstr(pValue,"on") != NULL)
|
|
{
|
|
strcpy(pCommand,"mpon");
|
|
iAct = 1;
|
|
}
|
|
else if(strstr(pValue,"off") != NULL)
|
|
{
|
|
strcpy(pCommand,"mpoff");
|
|
iAct = 1;
|
|
}
|
|
else
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return 0;
|
|
}
|
|
}
|
|
else if(strcmp(parname,"mz.power") == 0)
|
|
{
|
|
if(strstr(pValue,"on") != NULL)
|
|
{
|
|
strcpy(pCommand,"mzon");
|
|
iAct = 1;
|
|
}
|
|
else if(strstr(pValue,"off") != NULL)
|
|
{
|
|
strcpy(pCommand,"mzoff");
|
|
iAct = 1;
|
|
}
|
|
else
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return 0;
|
|
}
|
|
}
|
|
else if(strcmp(parname,"ts.power") == 0)
|
|
{
|
|
if(strstr(pValue,"on") != NULL)
|
|
{
|
|
iAct = 1;
|
|
strcpy(pCommand,"ton");
|
|
}
|
|
else if(strstr(pValue,"off") != NULL)
|
|
{
|
|
iAct = 1;
|
|
strcpy(pCommand,"toff");
|
|
}
|
|
else
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return 0;
|
|
}
|
|
}
|
|
if(iAct == 1)
|
|
{
|
|
/* send command and check for errors right here */
|
|
iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,131);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if(strstr(pReply,"Error") != NULL)
|
|
{
|
|
pPriv->iError = MOTERROR;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
/* second set: init */
|
|
if(strcmp(parname,"mp.init") == 0)
|
|
{
|
|
strcpy(pCommand,"mpi");
|
|
iAct = 1;
|
|
}
|
|
else if(strcmp(parname,"mz.init") == 0)
|
|
{
|
|
strcpy(pCommand,"mzi");
|
|
iAct = 1;
|
|
}
|
|
if(iAct)
|
|
{
|
|
/* send command and check for errors right here */
|
|
iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,131);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if(strstr(pReply,"Error") != NULL)
|
|
{
|
|
pPriv->iError = MOTERROR;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
what is left here is either the para's themselves which will be
|
|
handled in the second function after convertion to float or an
|
|
error
|
|
*/
|
|
iRet = sscanf(pValue,"%f",&fValue);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return;
|
|
}
|
|
|
|
return CookerSetPar(self,parname,fValue);
|
|
}
|
|
/*-----------------------------------------------------------------------*/
|
|
static int CookerHalt(pCodri self)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
pPriv->iError = 0;
|
|
|
|
/* no commands are defined to halt a thing! */
|
|
return 1;
|
|
}
|
|
/*----------------------------------------------------------------------*/
|
|
extern char *stptok(const char *s, char *tok, size_t toklen, char *brk);
|
|
|
|
static int CookerGetPar(pCodri self, char *parname,
|
|
char *pBuffer, int iLen)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
char pBueffel[256], pCommand[80], pReply[80];
|
|
float fVal;
|
|
char *pPtr;
|
|
int iAct = 0, iRet;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
pPriv->iError = 0;
|
|
|
|
/* first the power stuff */
|
|
if(strcmp(parname,"mp.power") == 0 || strcmp(parname,"mp.init") == 0)
|
|
{
|
|
strcpy(pBueffel,parname);
|
|
strcpy(pCommand,"mps");
|
|
iAct = 1;
|
|
}
|
|
else if(strcmp(parname,"mz.power") == 0 || strcmp(parname,"mz.init") ==0 )
|
|
{
|
|
strcpy(pBueffel,parname);
|
|
strcpy(pCommand,"mzs");
|
|
iAct = 1;
|
|
}
|
|
else if(strcmp(parname,"ts.power") == 0)
|
|
{
|
|
strcpy(pBueffel,parname);
|
|
strcpy(pCommand,"ts");
|
|
iAct = 1;
|
|
}
|
|
if(iAct)
|
|
{
|
|
iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,80);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
if( strstr(pReply,"OFF") != NULL || strstr(pReply,"Off") != NULL)
|
|
{
|
|
strcat(pBueffel," off");
|
|
}
|
|
else if(strstr(pReply,"not init") != NULL)
|
|
{
|
|
strcat(pBueffel," not");
|
|
}
|
|
else
|
|
{
|
|
strcat(pBueffel," on");
|
|
}
|
|
|
|
}
|
|
strncpy(pBuffer,pBueffel,iLen);
|
|
return 1;
|
|
}
|
|
|
|
/* now request values for the parameter */
|
|
if(strcmp(parname,"mp") == 0)
|
|
{
|
|
strcpy(pCommand,"mps");
|
|
}
|
|
else if(strcmp(parname,"mz") == 0)
|
|
{
|
|
strcpy(pCommand,"mzs");
|
|
}
|
|
else if(strcmp(parname,"ts") == 0)
|
|
{
|
|
strcpy(pCommand,"ts");
|
|
}
|
|
else
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return 0;
|
|
}
|
|
iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,80);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return 0;
|
|
}
|
|
/* decode value */
|
|
pPtr = pReply;
|
|
pPtr = stptok(pPtr,pBueffel,255,"/\0");
|
|
pPtr = stptok(pPtr,pBueffel,255,"/\0");
|
|
iRet = sscanf(pBueffel,"%f",&fVal);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = BADREPLY;
|
|
return 0;
|
|
}
|
|
sprintf(pBueffel,"%f",fVal);
|
|
strncpy(pBuffer,pBueffel,iLen);
|
|
return 1;
|
|
}
|
|
/*-----------------------------------------------------------------------*/
|
|
static int CookerCheckPar(pCodri self, char *parname)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
char pCommand[80], pReply[80];
|
|
int iRet;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
|
|
if(strcmp(parname,"mp") == 0)
|
|
{
|
|
strcpy(pCommand,"mps");
|
|
}
|
|
else if(strcmp(parname,"mz"))
|
|
{
|
|
strcpy(pCommand,"mzs");
|
|
}
|
|
else
|
|
{
|
|
pPriv->iError = BADINPUT;
|
|
return 0;
|
|
}
|
|
|
|
/* get a status reply */
|
|
iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,80);
|
|
if(iRet != 1)
|
|
{
|
|
pPriv->iError = iRet;
|
|
return HWFault;
|
|
}
|
|
|
|
if( strstr(pReply,"Start") != NULL || strstr(pReply,"Move") != NULL )
|
|
{
|
|
return HWBusy;
|
|
}
|
|
else
|
|
{
|
|
return HWIdle;
|
|
}
|
|
return HWIdle;
|
|
}
|
|
/*---------------------------------------------------------------------*/
|
|
static int CookerError(pCodri self, int *iCode, char *pError, int iLen)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
char pCommand[80], pReply[80];
|
|
int iRet;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
|
|
*iCode = pPriv->iError;
|
|
switch(pPriv->iError)
|
|
{
|
|
case NOTINIT:
|
|
strncpy(pError,"ERROR: NOT Initialized!",iLen);
|
|
break;
|
|
case POWEROFF:
|
|
strncpy(pError,"ERROR: Power is OFF",iLen);
|
|
break;
|
|
case LOWRANGE:
|
|
strncpy(pError,"ERROR: Lower limit violated",iLen);
|
|
break;
|
|
case HIGHRANGE:
|
|
strncpy(pError,"ERROR: Upper limit violated",iLen);
|
|
break;
|
|
case CONTIMEOUT:
|
|
strncpy(pError,"ERROR: Internal controller timeout ",iLen);
|
|
break;
|
|
case BADCOMMAND:
|
|
strncpy(pError,"ERROR: Controller did not understand command",iLen);
|
|
break;
|
|
case BADINPUT:
|
|
strncpy(pError,"A bad parameter was entered",iLen);
|
|
break;
|
|
case MOTERROR:
|
|
strncpy(pError,"ERROR: Iternal motor error in controller",iLen);
|
|
break;
|
|
case CONBUSY:
|
|
strncpy(pError,"ERROR: Controller is busy",iLen);
|
|
break;
|
|
default:
|
|
SerialError(pPriv->iError,pError,iLen);
|
|
break;
|
|
}
|
|
pPriv->iError = 0;
|
|
return 1;
|
|
}
|
|
/*----------------------------------------------------------------------*/
|
|
static int CookerFix(pCodri self, int iCode)
|
|
{
|
|
pSANSCook pPriv = NULL;
|
|
int iRet;
|
|
|
|
assert(self);
|
|
pPriv = (pSANSCook)self->pPrivate;
|
|
assert(pPriv);
|
|
|
|
switch(iCode)
|
|
{
|
|
case NOTINIT:
|
|
case POWEROFF:
|
|
case LOWRANGE:
|
|
case HIGHRANGE:
|
|
case BADCOMMAND:
|
|
case BADINPUT:
|
|
case MOTERROR:
|
|
return CHFAIL;
|
|
case CONTIMEOUT:
|
|
case CONBUSY:
|
|
return CHREDO;
|
|
/* network errors */
|
|
case EL734__BAD_FLUSH:
|
|
case EL734__BAD_RECV:
|
|
case EL734__BAD_RECV_NET:
|
|
case EL734__BAD_RECV_UNKN:
|
|
case EL734__BAD_RECVLEN:
|
|
case EL734__BAD_RECV1:
|
|
case EL734__BAD_RECV1_PIPE:
|
|
case EL734__BAD_RNG:
|
|
case EL734__BAD_SEND:
|
|
case EL734__BAD_SEND_PIPE:
|
|
case EL734__BAD_SEND_NET:
|
|
case EL734__BAD_SEND_UNKN:
|
|
case EL734__BAD_SENDLEN:
|
|
case NOCONNECTION:
|
|
SerialForceClose(&(pPriv->pData));
|
|
pPriv->pData = NULL;
|
|
iRet = SerialOpen(&(pPriv->pData),pPriv->pHost,
|
|
pPriv->iPort,pPriv->iChannel);
|
|
if(iRet == 1 )
|
|
{
|
|
return CHREDO;
|
|
}
|
|
else
|
|
{
|
|
return CHFAIL;
|
|
}
|
|
break;
|
|
case EL734__FORCED_CLOSED:
|
|
iRet = CookerInit(self);
|
|
if(iRet)
|
|
{
|
|
return CHREDO;
|
|
}
|
|
else
|
|
{
|
|
return CHFAIL;
|
|
}
|
|
break;
|
|
default:
|
|
return CHFAIL;
|
|
break;
|
|
|
|
}
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
pCodri MakeCookerDriver(char *pHost, int iPort, int iChannel)
|
|
{
|
|
pCodri pNew = NULL;
|
|
pSANSCook pPriv = NULL;
|
|
char *pText;
|
|
|
|
/* allocate memory */
|
|
pText = (char *)malloc(1024*sizeof(char));
|
|
pNew = (pCodri)malloc(sizeof(Codri));
|
|
pPriv = (pSANSCook)malloc(sizeof(SANSCook));
|
|
if( !pText || !pNew || !pPriv)
|
|
{
|
|
return NULL;
|
|
}
|
|
memset(pText,0,1024);
|
|
memset(pNew,0,sizeof(Codri));
|
|
memset(pPriv,0,sizeof(SANSCook));
|
|
|
|
/* initialize private data structure */
|
|
pPriv->pHost = strdup(pHost);
|
|
pPriv->iPort = iPort;
|
|
pPriv->iChannel = iChannel;
|
|
pPriv->pData = NULL;
|
|
|
|
/* set known parameter names */
|
|
strcpy(pText,"mp");
|
|
strcat(pText,",mp.power");
|
|
strcat(pText,",mp.init");
|
|
strcat(pText,",mz");
|
|
strcat(pText,",mz.power");
|
|
strcat(pText,",mz.init");
|
|
strcat(pText,",ts");
|
|
strcat(pText,",ts.power");
|
|
|
|
|
|
/* install codri */
|
|
pNew->Init = CookerInit;
|
|
pNew->Close = CookerClose;
|
|
pNew->Delete = CookerDelete;
|
|
pNew->SetPar = CookerSetPar;
|
|
pNew->SetPar2 = CookerSetPar2;
|
|
pNew->GetPar = CookerGetPar;
|
|
pNew->CheckPar = CookerCheckPar;
|
|
pNew->GetError = CookerError;
|
|
pNew->TryFixIt = CookerFix;
|
|
pNew->Halt = CookerHalt;
|
|
pNew->pParList = pText;
|
|
pNew->pPrivate = pPriv;
|
|
|
|
return pNew;
|
|
}
|
|
|
|
|
|
|
|
|
|
|