596 lines
14 KiB
C
596 lines
14 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 comes 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 0;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
}
|
|
strlcpy(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);
|
|
strlcpy(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:
|
|
strlcpy(pError, "ERROR: NOT Initialized!", iLen);
|
|
break;
|
|
case POWEROFF:
|
|
strlcpy(pError, "ERROR: Power is OFF", iLen);
|
|
break;
|
|
case LOWRANGE:
|
|
strlcpy(pError, "ERROR: Lower limit violated", iLen);
|
|
break;
|
|
case HIGHRANGE:
|
|
strlcpy(pError, "ERROR: Upper limit violated", iLen);
|
|
break;
|
|
case CONTIMEOUT:
|
|
strlcpy(pError, "ERROR: Internal controller timeout ", iLen);
|
|
break;
|
|
case BADCOMMAND:
|
|
strlcpy(pError, "ERROR: Controller did not understand command", iLen);
|
|
break;
|
|
case BADINPUT:
|
|
strlcpy(pError, "A bad parameter was entered", iLen);
|
|
break;
|
|
case MOTERROR:
|
|
strlcpy(pError, "ERROR: Iternal motor error in controller", iLen);
|
|
break;
|
|
case CONBUSY:
|
|
strlcpy(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;
|
|
}
|