/*--------------------------------------------------------------------------
                            D o C h o


  A SICS driver for a Dornier Chopper Control System accessed through a 
  RS-232 interface connected to a Macintosh PC running the SerialPortServer
  terminal server program. There are two choppers which ususally run at fixed
  speed ratios against each other. There ia also a phase difference between
  the two choppers. And lots of machine surveillance parameters.

  This driver is used by the generic chopper or device controller as described
  in choco.tex. 


  Mark Koennecke, January 1999

  Modified to support a single chopper only,

  Uwe Filges, Mark Koennecke; November 2001
--------------------------------------------------------------------------*/
#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 this Dornier chopper
-------------------------------------------------------------------------*/
  typedef struct {
                   char *pHost;
                   int  iPort;
                   int iChannel;
                   void *pData;
                   int iRefreshIntervall;
                   pStringDict pPar;
                   time_t tRefresh;
                   int iStop;
                   long lTask;
                   int iError;
                   int iBusy;
                   float fRatio;
                   int iSingle;
                   char pError[80]; 
                  } DoCho, *pDoCho;
/*
  pHost, iPort and iChannel combined are the adress of the chopper
  controller at the  Macintosh terminal server. pData is the serial 
  port connection data structure needed and managed by the SerialIO
  functions.

  As the communication with the Dornier Chopper System is very slow the
  parameter list of this driver will only be updated a predefined time
  intervalls. In between buffered values will be returned for requests.
  The buffered parameters are held in the string dictioanry pPar. 
  iRefreshIntervall is the time between refreshs. tRefresh is the time for
  the next refresh. iBusy is  flag which indicates, that it was tried to 
  modify a variable. This will only be reflected with the next status update.
  In between DoChoCheckPar might conclude, that the chopper is already
  done. iBusy is meant to stop that. It is set when a parameter is changed
  and cleared bu the status message code. DoChoCheckPar checks for it.

  Refreshing will be performed by a special SICS task which will be 
  started when the driver is initialized. In order to stop this task when
  need arises the parameter iStop can be set to true.

  iError is the last error reported on this device. If no error: 0

  fRatio is the target value for the chopper ratio. In contrast to the
  other parameters, its target value cannot be extracted from the chopper
  status message.

  iSingle is a flag which is true if only a single chopper is controlled
  through this driver. This supports the POLDI single choper case.

*/
/*----------------------------------------------------------------------
  ERROR CODES:
*/
#define UNDRIVABLE -8002
#define UNKNOWNPAR -8003 
#define PARERROR   -8004
#define BADSYNC    -8005
#define BADSTOP    -8006
#define CHOPERROR  -8007

extern char *trim(char *pTrim); /* trim.c */

/*----------------------------------------------------------------------*/
static void SplitChopperReply(pCodri self, char *prefix, char *pBueffel)
{
     char pToken[30], pValue[20];
     char *pPtr, *pTok, *pVal;
     int iCount, iRet;
     pDoCho pPriv = NULL;

     pPriv = (pDoCho)self->pPrivate;

     /* decompose pBueffel and store into string dictionary */
      pPtr = strtok(pBueffel,";");
      while(pPtr != NULL)
      {
        iCount = sscanf(pPtr,"%s %s",pToken,pValue);
        if(iCount == 2)
	{
            pTok = trim(pToken);
            pVal = trim(pValue);
            sprintf(pToken,"%s.%s",prefix,pTok);
            iRet = StringDictUpdate(pPriv->pPar,pToken,pVal);
            if(!iRet)
            {
                StringDictAddPair(pPriv->pPar,pToken,pVal);
                strcat(self->pParList,pToken);
                strcat(self->pParList,",");
	    }
	}
	else
	{
	   /* this fixes a bug with oversized messages in dphas */
	   if(strstr(pPtr,"dphas") != NULL)
	   {
	       sprintf(pToken,"%s.dphas",prefix);
	       iRet = StringDictUpdate(pPriv->pPar,
	                        pToken,pPtr+5);
               if(!iRet)
               {
                  StringDictAddPair(pPriv->pPar,pToken,
                                    pPtr+5);
                  strcat(self->pParList,pToken);
                  strcat(self->pParList,",");
	       }
	   }
	}
        pPtr = strtok(NULL,";");
      }
}
/*-------------------------------------------------------------------------
  Well, DoChoStatus sends a status request to the Dornier chopper control
  system. There is a gotcha, you need three reads to get the full information.
  Then the answer is parsed and decomposed into parameter content for the
  string dictionary. The single status components are separated by ;.  
-------------------------------------------------------------------------*/

  static int DoChoStatus(pCodri self)
  {
      int iRet, iCount, iCode;
      char pBueffel[1024], pToken[30], pValue[20];
      char *pPtr, *pTok, *pVal;
      pDoCho pPriv = NULL;

      assert(self);
      pPriv = (pDoCho)self->pPrivate;
      assert(pPriv);
      pPriv->iBusy = 0;
      pPriv->iError = 0;


      /* first send, command, returns the echo */
      iRet = SerialWriteRead(&(pPriv->pData),"asyst 1",pBueffel,1023);
      if(iRet < 0)
      {
         pPriv->iError = iRet;
         return 0;
      }

      /* next send: reads first chopper line */
      iRet = SerialWriteRead(&(pPriv->pData),"",pBueffel,1023);
      if(iRet < 0)
      {
         pPriv->iError = iRet;
         return 0;
      }
      SplitChopperReply(self,"chopper1",pBueffel);

      if(!pPriv->iSingle)
      {
	/* second send: get next second chopper line */
	iRet = SerialWriteRead(&(pPriv->pData),"",pBueffel,1023);
	if(iRet < 0)
	{
	   pPriv->iError = iRet;
	   return 0;
	}
        SplitChopperReply(self,"chopper2",pBueffel);
      }
      

      return 1;
  }
/*-------------------------------------------------------------------------*/
  static int DoChoTask(void *pData)
  {
      pCodri self = NULL;
      pDoCho pPriv = NULL;
      int iCode, iRet;
      char pDummy[60];

      self = (pCodri)pData;
      assert(self);
      pPriv = (pDoCho)self->pPrivate;
      assert(pPriv);

      /* check for stop */
      if(pPriv->iStop)
         return 0;


      /* check if it is time to run a status request */
      if(time(NULL) > pPriv->tRefresh)
      {
	/* try, fix error */
	if(pPriv->iError != 0)
	{
	  self->GetError(self,&iCode,pDummy,59);
	  iRet = self->TryFixIt(self,iCode);
	  if(iRet == CHFAIL)
	  {
            pPriv->tRefresh = time(NULL) + pPriv->iRefreshIntervall;
	    return 1;
	  }
	}
        /* do it */
        DoChoStatus(self);
        pPriv->tRefresh = time(NULL) + pPriv->iRefreshIntervall;
      }
      return 1;
  }  
/*------------------------------------------------------------------------*/
  static void DoChoKill(void *pData)
  {
     pCodri self = NULL;
     pDoCho pPriv = NULL;

     self = (pCodri)pData;
     if(!self)
       return;
     pPriv = (pDoCho)self->pPrivate;
     if(!pPriv)
       return;


    if(pPriv->pData)
    {
      SerialClose(&(pPriv->pData));
      pPriv->pData = NULL;
    }

    if(pPriv->pHost)
      free(pPriv->pHost);
    if(pPriv->pPar)
      DeleteStringDict(pPriv->pPar);

    free(pPriv); 
  } 
/*-------------------------------------------------------------------------*/
  static int DoChoInit(pCodri self)
  {
     pDoCho pPriv = NULL;
     int iRet;

     assert(self);
     pPriv = (pDoCho)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 */
     SerialConfig(&(pPriv->pData),10000);
     SerialATerm(&(pPriv->pData),"1\r\n");
     SerialSendTerm(&(pPriv->pData),"\r");

     pPriv->iStop = 0;
     pPriv->tRefresh = 0; /* force a status request when first run */
     
     /* start the update task */
     if(pPriv->lTask == 0)
     {
	 pPriv->lTask = TaskRegister(pServ->pTasker,
				      DoChoTask,
				      NULL,
				      NULL,
				      self,
				      1);                             
     }
     return 1;     
  } 
/*------------------------------------------------------------------------*/
  static int DoChoClose(pCodri self)
  {
     pDoCho pPriv = NULL;
     int iRet;
     long lVal;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     if(pPriv->pData)
     {
       SerialClose(&(pPriv->pData));
       pPriv->pData = NULL;
     }
     return 1;
  }
/*------------------------------------------------------------------------*/
  static int DoChoDelete(pCodri self)
  {
     pDoCho pPriv = NULL;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     if(pPriv->pData)
       {
	 SerialClose(&(pPriv->pData));
	 pPriv->pData = NULL;
       }

     if(pPriv->pHost)
       free(pPriv->pHost);
     if(pPriv->pPar)
       DeleteStringDict(pPriv->pPar);

     free(pPriv); 

     return 1;
  }
/*--------------------------------------------------------------------------*/
  static int DoChoSetPar2(pCodri self, char *parname, char *pValue)
  {
     pDoCho pPriv = NULL;
     char pCommand[80], pReply[132];
     char pState[20];
     int iRet;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     /* deal with our four parameters */
     if(strcmp(parname,"chopper1.nspee") == 0)
     {
         sprintf(pCommand,"nspee 1 %s",pValue);
     }
     else if(strcmp(parname,"chopper2.nspee") == 0)
     {
         iRet = StringDictGet(pPriv->pPar,"chopper2.state",pState,19);
         if(iRet && strstr(pState,"async") != NULL )
	 {
           sprintf(pCommand,"nspee 2 %s",pValue);
         }
         else
	 {
           pPriv->iError = BADSYNC;
           return 0;
         }
     }
     else if(strcmp(parname,"chopper2.nphas") == 0)
     {
         sprintf(pCommand,"nphas 2 %s",pValue);
     }
     else if(strcmp(parname,"chopper2.ratio") == 0)
     {
        sprintf(pCommand,"ratio 2 %s",pValue);
     }
     else
     {
       pPriv->iError = UNDRIVABLE;
       return 0;
     }

     iRet = SerialWriteRead(&(pPriv->pData),pCommand,pReply,131);
     if(iRet != 1)
     {
        pPriv->iError = iRet;
        return 0;
     } 
     if(strstr(pReply,"error") != NULL)
     {
       pPriv->iError = CHOPERROR;
       strncpy(pPriv->pError,pReply,79);
       return 0;
     }
     else
     {
       pPriv->iError = 0;
     }
     pPriv->iBusy = 1;
     return 1;
  }
/*-------------------------------------------------------------------------*/
  static int DoChoHalt(pCodri self)
  {
     pDoCho pPriv = NULL;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     /* 
        there is no documented way to stop the Dornier chopper 
        system. This at least makes SICS happy.
     */
     pPriv->iError = BADSTOP;
     pPriv->iBusy = 0;
     return 1; 
  }
/*---------------------------------------------------------------------------*/
  static int DoChoSetPar(pCodri self, char *parname, float fValue)
  {
      char pValue[50];
      pDoCho pPriv = NULL;

      assert(self);
      pPriv = (pDoCho)self->pPrivate;
      assert(pPriv);

      if(strstr(parname,"nspee") != NULL)
      {
         sprintf(pValue,"%d",(int)fValue);
      }
      else if(strstr(parname,"ratio") != NULL)
      {
         sprintf(pValue,"%d",(int)fValue);
         pPriv->fRatio = (int)fValue;
      }
      else if(strcmp(parname,"updateintervall") == 0)
      {
         sprintf(pValue,"%d",(int)fValue);
         StringDictUpdate(pPriv->pPar,"updateintervall",pValue);
         pPriv->iRefreshIntervall = (int)fValue;
         return 1;
      }
      else
      {
        sprintf(pValue,"%f",fValue);
      }
      return DoChoSetPar2(self,parname, pValue);
  }
/*----------------------------------------------------------------------*/
  static int DoChoGetPar(pCodri self, char *parname, 
                         char *pBuffer, int iBufLen)
  {
     pDoCho pPriv = NULL;
     int iRet;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     if(pPriv->iError != 0)
     {
       self->GetError(self,&iRet,pBuffer,iBufLen);
       return 0;
     }  

     iRet = StringDictGet(pPriv->pPar,parname,pBuffer,iBufLen);
     if(!iRet)
     {
        pPriv->iError = UNKNOWNPAR;
        return 0;
     }
     return 1;
  }
/*-----------------------------------------------------------------------*/
  static int DoChoCheckPar(pCodri self, char *parname)
  {
     pDoCho pPriv = NULL;
     char pVal1[20], pVal2[20];
     float fTarget, fIst, fDelta;
     int iRet;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     /* check the busy flag first */
     if(pPriv->iBusy)
        return HWBusy;

     /* was there an error in the status show? */
     if(pPriv->iError != 0)
     {
       return HWFault;
     }

     /* updateintervall is always HWIdle */
     if(strcmp(parname,"updateintervall") == 0)
     {
       return HWIdle;
     }

     /* OK, got a new status let us check the parameter */
     /* chopper 1 speed */
     if(strcmp(parname,"chopper1.nspee") == 0)
     {
        iRet = StringDictGet(pPriv->pPar,"chopper1.nspee",pVal1,19);
        iRet += StringDictGet(pPriv->pPar,"chopper1.aspee",pVal2,19);
        if(iRet != 2)
	{
          pPriv->iError = PARERROR;
          return HWFault;
        }
        sscanf(pVal1,"%f",&fTarget);
        sscanf(pVal2,"%f",&fIst);
        fDelta = fTarget - fIst;
        if(fDelta < 0.0)
           fDelta = -fDelta;
        if(fDelta > 50)
        {
           return HWBusy;
        }        
        else
	{
           return HWIdle;
        }
     }     
     /* chopper 2 speed */
     if(strcmp(parname,"chopper2.nspee") == 0)
     {
        iRet = StringDictGet(pPriv->pPar,"chopper2.nspee",pVal1,19);
        iRet += StringDictGet(pPriv->pPar,"chopper2.aspee",pVal2,19);
        if(iRet != 2)
	{
          pPriv->iError = PARERROR;
          return HWFault;
        }
        sscanf(pVal1,"%f",&fTarget);
        sscanf(pVal2,"%f",&fIst);
        fDelta = fTarget - fIst;
        if(fDelta < 0.0)
           fDelta = -fDelta;
        if(fDelta > 5.)
        {
           return HWBusy;
        }        
        else
	{
           return HWIdle;
        }
     }     

     /* phase */
     if(strcmp(parname,"chopper2.nphas") == 0)
     {
       iRet = StringDictGet(pPriv->pPar,"chopper2.dphas",pVal1,19);
       sscanf(pVal1,"%f",&fDelta);
       if(fDelta < 0.)
          fDelta = - fDelta;
       if(fDelta > 0.3)
       {
          return HWBusy;
       }
       else
       {
          return HWIdle;
       }
     }

     /* ratio */
     if(strcmp(parname,"chopper2.ratio") == 0)
     {
       iRet = StringDictGet(pPriv->pPar,"chopper2.ratio",pVal1,19);
       sscanf(pVal1,"%f",&fIst);
       fDelta = fIst - pPriv->fRatio;
       if(fDelta < 0.)
          fDelta = - fDelta;
       if(fDelta > 0.3)
       {
          return HWBusy;
       }
       else
       {
          return HWIdle;
       }
     }    
     pPriv->iError = UNKNOWNPAR;
     return HWFault;
  }  
/*-------------------------------------------------------------------------*/
  static int DoChoError(pCodri self, int *iCode, char *pError, int iLen)
  {
     pDoCho pPriv = NULL;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     *iCode = pPriv->iError;
     switch(pPriv->iError)
     {
          case UNDRIVABLE:
               strncpy(pError,"Parameter is not drivable",iLen);
               break;
	  case UNKNOWNPAR:
               strncpy(pError,"Parameter is unknown",iLen);
               break;
	  case PARERROR:
               strncpy(pError,"Internal parameter error",iLen);
               break;
	  case BADSYNC:
               strncpy(pError,"Cannot drive slave chopper",iLen);
               break;
	  case CHOPERROR:
               strncpy(pError,pPriv->pError,iLen);
               break;
          case BADSTOP:
               strncpy(pError,
                "User called STOP. WARNING: chopper is still untamed!",
                iLen);
               break;
	  default:
               SerialError(pPriv->iError,pError,iLen);
               break;
     }
     pPriv->iError = 0;     
     return 1;
  }
/*------------------------------------------------------------------------*/
  static int DoChoFix(pCodri self, int iCode)
  {
     pDoCho pPriv = NULL;
     int iRet;

     assert(self);
     pPriv = (pDoCho)self->pPrivate;
     assert(pPriv);

     switch(iCode)
     {
	 /* 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 = DoChoInit(self);
		   if(iRet)
		   {
		     return CHREDO;
		   }                 
		   else
		   {
		     return CHFAIL;
		   }                 
		   break;
	 default:
		   return CHFAIL;
		   break;                       
     }
     return CHFAIL;
 }
/*-------------------------------------------------------------------------*/
 pCodri MakeDoChoDriver(char *pHost, int iPort, int iChannel, int iSingle)
 {
      pCodri pNew = NULL;
      pDoCho pPriv = NULL;
      char *pText;

      /* allocate memory */
      pText = (char *)malloc(4096*sizeof(char));
      pNew = (pCodri)malloc(sizeof(Codri));
      pPriv = (pDoCho)malloc(sizeof(DoCho));
      if( !pText || !pNew || !pPriv)
      {
        return NULL;
      }
      memset(pText,0,4096);
      memset(pNew,0,sizeof(Codri));
      memset(pPriv,0,sizeof(DoCho));

     /* initialize private data structure */
     pPriv->pHost = strdup(pHost);
     pPriv->iPort = iPort;
     pPriv->iChannel = iChannel;
     pPriv->pData = NULL;
     pPriv->iRefreshIntervall = 60;
     pPriv->pPar = CreateStringDict();
     pPriv->tRefresh = time(NULL);
     pPriv->iSingle = iSingle;
     if(!pPriv->pPar)
     {
        free(pText);
        free(pNew);
        free(pPriv);
        return NULL;
     }

     /* install codri */
     pNew->Init      =  DoChoInit;
     pNew->Close     =  DoChoClose;
     pNew->Delete    =  DoChoDelete;
     pNew->SetPar    =  DoChoSetPar;
     pNew->SetPar2   =  DoChoSetPar2;
     pNew->GetPar    =  DoChoGetPar;
     pNew->CheckPar  =  DoChoCheckPar;
     pNew->GetError  =  DoChoError;
     pNew->TryFixIt  =  DoChoFix;
     pNew->Halt      =  DoChoHalt;
     pNew->pParList  =  pText;
     strcpy(pNew->pParList,"updateintervall,");
     StringDictAddPair(pPriv->pPar,"updateintervall","60");
     pNew->pPrivate = pPriv;

     return pNew;
 }




