/* -------------------------------------------------------------------------- */
/* -                Optec IFW filter wheel set                              - */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/* Copyright 2011 John Kielkopf                                               */
/*                                                                            */
/* Distributed under the terms of the General Public License (see LICENSE)    */
/*                                                                            */
/* John Kielkopf (kielkopf@louisville.edu)                                    */
/*                                                                            */
/* Date: August 1, 2011                                                       */
/* Version: 1.1                                                               */
/*                                                                            */
/* History:                                                                   */
/*                                                                            */
/* May 25, 2009                                                               */
/*   Version 1.0                                                              */
/*     Released                                                               */
/*                                                                            */
/* August 1, 2011                                                             */
/*   Version 1.1                                                              */
/*     Added WEXITS to insure control panel will work on return               */
/*     Added tests for successful serial port connection                      */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <math.h>
#include "optec.h"

#define NULL_PTR(x) (x *)0

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif


/* Communications variables and routines for internal use */

static int FilterPortFD;
static int FilterConnectFlag = FALSE;

/* Optec commands */

int CheckConnectOptecFilter(void);
int ConnectOptecFilter(void);
int SetOptecFilter(int filter);
int GetOptecFilter(int *filter);
int HomeOptecFilter(char *wheel_name);
int GetOptecWheel(char *wheel_name);
int WriteFilter(int filter);
int ExitOptecFilter(void);
int DisconnectOptecFilter(void);

/* Serial port utilities */

typedef fd_set telfds;

static int readn(int fd, char *ptr, int nbytes, int sec);
static int writen(int fd, char *ptr, int nbytes);
static int telstat(int fd,int sec,int usec);


/* Main program */

int main(int argc, char *argv[])
{
  int filter;
  int filtermax = FILTER_MAX;
  char *testfilter;
  int i;
  
  if (argc < 2) { 
    printf("Usage: setifw filter# \n");
    printf("\n");
    printf("Select filter in an Optec IFW filter wheel \n");
    printf("\n");
    printf("Example: \n");
    printf("  setifw 4\n");
    return(0);
  }
  
  filter = strtod(argv[1],&testfilter);
  if ( (filter < 1) || (filter > filtermax) )
  {
    fprintf(stderr,"Filter number outside range ...\n");
    return(0);
  }  
  
  i = 1;
  for ( ; ; )
  {
    if ( ConnectOptecFilter() == TRUE )
    {
      break;
    }
    else if ( i > 10 )
    {     
      fprintf(stderr,"Cannot connect to filter wheel ...\n");
      return(0);
    }   
    else
    {
      i++;
    }
  } 
      
  if ( SetOptecFilter(filter) == TRUE )
  {
    WriteFilter(filter);
  }
  else
  {
    fprintf(stderr,"Failed to set filter ...\n");
  }
 
  DisconnectOptecFilter();
  return(0);
} 

/* Report on filter wheel connection status */

int CheckConnectOptecFilter(void)
{
  if (FilterConnectFlag == TRUE)
  {
    return(TRUE);
  }
  else
  {
    return(FALSE);
  }
}


/* Connect to the filter wheel serial interface */
/* Sets FilterConnectFlag TRUE and returns TRUE on success */

int ConnectOptecFilter(void)
{
  struct termios tty;
  char sendstr[32]; 
  char returnstr[32];
  char filterport[32];
  int numRead;
  
  DisconnectOptecFilter();
  
  /* Make the connection */

  strcpy(filterport,FILTER_PORT);
  FilterPortFD = open(filterport,O_RDWR);
  if(FilterPortFD == -1)
  {
    fprintf(stderr,"The filter wheel serial port not available ... \n");
    return(0) ;
  }
  
  /* Set the port attributes for 1900 baud, 8 bits, no parity */
  
  tcgetattr(FilterPortFD,&tty);
  cfsetospeed(&tty, (speed_t) B19200);
  cfsetispeed(&tty, (speed_t) B19200);
  tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
  tty.c_iflag =  IGNBRK;
  tty.c_lflag = 0;
  tty.c_oflag = 0;
  tty.c_cflag |= CLOCAL | CREAD;
  tty.c_cc[VMIN] = 1;
  tty.c_cc[VTIME] = 5;
  tty.c_iflag &= ~(IXON|IXOFF|IXANY);
  tty.c_cflag &= ~(PARENB | PARODD);
  tcsetattr(FilterPortFD, TCSANOW, &tty);

  /* Flush the input (read) buffer */

  tcflush(FilterPortFD,TCIOFLUSH);
  
  /* Send WSMODE command to establish communications */
  
  strcpy(sendstr,"WSMODE\r");
  writen(FilterPortFD,sendstr,7);
  usleep(1000000);
  numRead=readn(FilterPortFD,returnstr,1,2);

  if ( returnstr[0]!='!' )
  {
    fprintf(stderr,"The filter wheel is not responding ... \n"); 
    tcflush(FilterPortFD,TCIOFLUSH);
    DisconnectOptecFilter();
    return(0);
  }
                  
  FilterConnectFlag = TRUE;
      
  /* Flush the input buffer in case there is something left from startup */

  tcflush(FilterPortFD,TCIOFLUSH);

  return(1);  

}

/* Home */

int HomeOptecFilter(char *wheel_name)
{
  char sendstr[32];
  char returnstr[32];
  char optec_wheel;
  
  sprintf(sendstr,"WHOME\r");
  writen(FilterPortFD,sendstr,6);
  
  /* Homing takes up to 20 seconds.  Wait here. */
  
  usleep(20000000);

  /* Look for '*' acknowledgement of request*/

  if ( readn(FilterPortFD,returnstr,1,4) ) 
  {
    returnstr[1] ='\0';
    sscanf(returnstr,"%s",&optec_wheel);
    *wheel_name = optec_wheel;   
    return(1);       
  }
  else 
  {        
    return(0);
  }
  return(1);
}

/* Exit filter serial gracefully */

int ExitOptecFilter(void)
{
  char sendstr[32];
  char returnstr[32];
  char optec_wheel;
  
  sprintf(sendstr,"WEXITS\r");
  writen(FilterPortFD,sendstr,7);
  
  /* Look for '*' acknowledgement of request*/

  if ( readn(FilterPortFD,returnstr,1,4) ) 
  {
    /* Acknowledged */
    return(1);       
  }
  else 
  {        
    /* Timed out */
    return(0);
  }
  return(1);
}


/* Filter selection */

int SetOptecFilter(int filter)
{
  char sendstr[32];
  char returnstr[32];
  int numread;

  if ((filter < 1) || (filter > 8))
  {
    return(0);
  }
    
  sprintf(sendstr,"WGOTO%1d\r",filter);
  writen(FilterPortFD,sendstr,7);

  /* Look for '*' acknowledgement of request                               */
  /* Must allow long enough for the wheel to respond                       */
  /* Value of 10 seconds here seems to work for IFW3 with 6 filter wheel   */
  /* If the time is too short the wheel will still go to its position      */
  /*   but the software will report an error since it will not see the     */
  /*   acknowledgement                                                     */
  
  numread = readn(FilterPortFD,returnstr,1,10);

  if ( numread ) 
  {
    if (returnstr[0] != '*')
    {
      return(0);
    }
    else
    {
      return(1);
    }
  }

  return(0);
}


/* Return the current filter setting */

int GetOptecFilter(int *filter)
{
  char sendstr[32];
  char returnstr[32];
  int numread;
  int optec_filter;

  sprintf(sendstr,"WFILTR\r");
  writen(FilterPortFD,sendstr,7);
  numread=readn(FilterPortFD,returnstr,1,1);
  returnstr[1] = '\0'; 
  sscanf(returnstr,"%d",&optec_filter);
  *filter = optec_filter;   
  return(1);
}

/* Return the current filter wheel designation */

int GetOptecWheel(char *wheel_name)
{
  char sendstr[32];
  char returnstr[32];
  int numread;
  char optec_wheel;

  sprintf(sendstr,"WIDENT\r");
  writen(FilterPortFD,sendstr,7);
  numread=readn(FilterPortFD,returnstr,1,1);
  returnstr[1] = '\0'; 
  sscanf(returnstr,"%s",&optec_wheel);
  *wheel_name = optec_wheel;   
  return(1);
}

/* Close serial connection to Optec IFW and reset FilterConnectFlag */

int DisconnectOptecFilter(void)
{
  if(FilterConnectFlag == TRUE)
  {
    /* Tell the controller it may use the hand control now */
    /* We will not test if it acknowledges this request */
    
    ExitOptecFilter();
    
    /* Close the serial port */
    
    close(FilterPortFD);
    
    /* Set the program flag */
    
    FilterConnectFlag = FALSE;
    return(TRUE);
  }
  
  /* The filter control was not connected */
  /* Do nothing but make sure the flag is  FALSE */
  /* Return a FALSE in case we need to know there was a mixup */
  
  FilterConnectFlag = FALSE;
  return(FALSE);
}

/* Write the filter to stdout and and to a system status file */

int WriteFilter(int filter)
{
  FILE* outfile;
  outfile = fopen("/usr/local/observatory/status/ccdfilter","w");
  if ( outfile == NULL )
  {
    fprintf(stderr,"Cannot update ccd filter status file\n");
  }
  else
  {
    fprintf(outfile,"%1d\n",filter); 
    fclose(outfile);
  }

  fprintf(stdout,"%1d\n",filter);  
  return TRUE;
}

/* Serial port utilities */

static int writen(fd, ptr, nbytes)
int fd;
char *ptr;
int nbytes;
{
  int nleft, nwritten;
  nleft = nbytes;
  while (nleft > 0) 
  {
    nwritten = write (fd, ptr, nleft);
    if (nwritten <=0 ) break;
    nleft -= nwritten;
    ptr += nwritten;
  }
  return (nbytes - nleft);
}

static int readn(fd, ptr, nbytes, sec)
int fd;
char *ptr;
int nbytes;
int sec;
{
  int stat;
  int nleft, nread;
  nleft = nbytes;
  while (nleft > 0) 
  {
    stat = telstat(fd,sec,0);
    if (stat <=  0 ) break;
    nread  = read (fd, ptr, nleft);

/*  Diagnostic */    
    
/*    printf("readn: %d read\n", nread);  */

    if (nread <= 0)  break;
    nleft -= nread;
    ptr += nread;
  }
  return (nbytes - nleft);
}

/*
 * Examines the read status of a file descriptor.
 * The timeout (sec, usec) specifies a maximum interval to
 * wait for data to be available in the descriptor.
 * To effect a poll, the timeout (sec, usec) should be 0.
 * Returns non-negative value on data available.
 * 0 indicates that the time limit referred by timeout expired.
 * On failure, it returns -1 and errno is set to indicate the
 * error.
 */
static int telstat(fd,sec,usec)
register int fd, sec, usec;
{
  int ret;
  int width;
  struct timeval timeout;
  telfds readfds;

  memset((char *)&readfds,0,sizeof(readfds));
  FD_SET(fd, &readfds);
  width = fd+1;
  timeout.tv_sec = sec;
  timeout.tv_usec = usec;
  ret = select(width,&readfds,NULL_PTR(telfds),NULL_PTR(telfds),&timeout);
  return(ret);
}

