/* -------------------------------------------------------------------------- */
/* -    Planewave A200HR mount with a Sidereal Technologies controller      - */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/* Copyright 2009-2012 John Kielkopf                                          */
/*                                                                            */
/* Distributed under the terms of the General Public License (see LICENSE)    */
/*                                                                            */
/* John Kielkopf (kielkopf@louisville.edu)                                    */
/*                                                                            */
/* Date: August 15, 2012                                                      */
/* Version: 6.0                                                               */
/*                                                                            */
/* History:                                                                   */
/*                                                                            */
/* August 15, 2012                                                            */
/*   Version 6.0                                                              */
/*     This version for the A200 mount requires Renishaw (HR) encoders        */
/*     Use external programs for other motors or sensors                      */
/*       Focus -- setfocus and getfocus                                       */
/*       Rotation -- setrotate and getrotate                                  */
/*       Temperature -- setfan, setheater and gettemperature                  */
/*     Includes CenterGuide function                                          */
/*     Leapsecond updated to 35.0 in protocol.h                               */


#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 "protocol.h"

#define NULL_PTR(x) (x *)0

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

/* There are two classes of routines defined here:                            */

/*   XmTel commands allow generic access to the controller.                   */
/*   Sitech specific commands for hardware and data.                          */


/* System variables and prototypes */

/* Telescope and mounting commands that may be called externally */

/* Interface control */

void ConnectTel(void);
int  SyncTelEncoders(void);
void DisconnectTel(void);
int  CheckConnectTel(void);

/* Slew, track, and guide control */

void SetRate(int newrate);
void StartSlew(int direction);
void StopSlew(int direction);
void StartTrack(void);
void StopTrack(void);
void CenterGuide(double centerra, double centerdec, 
  int raflag, int decflag, int pmodel);
void FullStop(void);
void GetGuideTargets(int *ntarget, int *starget, int *etarget, int *wtarget);

/* Coordinates */

void GetTel(double *telra, double *teldec, int pmodel);
int  GoToCoords(double newRA, double newDec, int pmodel);
int  CheckGoTo(double desRA, double desDec, int pmodel);

/* Slew limits */

int  GetSlewStatus(void);
int  SetLimits(int limits);
int  GetLimits(int *limits);

/* Synchronizing */

int  SyncTelToUTC(double newutc);
int  SyncTelToLocation(double newlong, double newlat, double newalt);
int  SyncTelToLST(double newTime);

/* Instrumentation */

void Heater(int heatercmd);
void Fan(int fancmd);
void Focus(int focuscmd, int focusspd);
void Rotate(int rotatecmd, int rotatespd);
void GetFocus(double *telfocus);
void GetRotate(double *telrotate);
void GetTemperature(double *teltemperature);

/* External variables and shared code */

extern double LSTNow(void);
extern double UTNow(void);
extern double Map24(double hour);
extern double Map12(double hour);
extern double Map360(double degree);
extern double Map180(double degree);
extern double SiteLatitude, SiteLongitude;
extern int    telmount;
extern int    homenow;
extern double homeha;            
extern double homera;            
extern double homedec;           
extern char   telserial[32];     

extern void PointingFromTel (double *telra1, double *teldec1, 
  double telra0, double teldec0, int pmodel);

extern void PointingToTel (double *telra0, double *teldec0, 
  double telra1, double teldec1, int pmodel);
  
extern void EquatorialToHorizontal(double ha, double dec, double *az, double *alt);
extern void HorizontalToEquatorial(double az, double alt, double *ha, double *dec);  

/* Contants */

double mtrazmcal = MOTORAZMCOUNTPERDEG; 
double mtraltcal = MOTORALTCOUNTPERDEG;
double mntazmcal = MOUNTAZMCOUNTPERDEG;
double mntaltcal = MOUNTALTCOUNTPERDEG;

/* Global slew parameters */

int slewrate;          


/* Global encoder counts */

int mtrazm = 0;
int mtralt = 0;
int mntazm = 0;
int mntalt = 0;


/* Global pointing angles derived from the encoder counts */

double mtrazmdeg = 0.;
double mtraltdeg = 0.;
double mntazmdeg = 0.;
double mntaltdeg = 0.;
 

/* Global tracking parameters */

int azmtrackrate0   = 0;
int alttrackrate0   = 0;
int azmtracktarget  = 0;
int azmtrackrate    = 0;
int alttracktarget  = 0;
int alttrackrate    = 0;


/* Files */

FILE *fp_focus;
char *focusfile;

FILE *fp_temperature;
char *temperaturefile;

FILE *fp_rotate;
char *rotatefile;

 
/* Communications variables and routines for internal use */

static int TelPortFD;
static int TelConnectFlag = FALSE;

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);

/* End of prototype and variable definitions */



/* Report on telescope connection status */

int CheckConnectTel(void)
{
  if (TelConnectFlag == TRUE)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}


/* Connect to the telescope serial interface */
/* Returns without action if TelConnectFlag is TRUE */
/* Sets TelConnectFlag TRUE on success */

void ConnectTel(void)
{  
  struct termios tty;    
  char sendstr[256] = "";
  char returnstr[256] = "";
  int numread;
  int limits, flag;
  
  if (TelConnectFlag != FALSE)
  {
    return;
  }
  
  /* Software allows for different mount types but the A200HR requires "GEM" */
  /* GEM:    over the pier pointing at the pole                              */
  /* EQFORK: pointing at the equator on the meridian                         */
  /* ALTAZ:  level and pointing north                                        */

  if (telmount != GEM)
  {
    fprintf(stderr,"Request made to connect A200HR controller to the wrong mount type.");
    return;
  }
  else
  {
    fprintf(stderr, "On a cold start A200HR must be over the pier\n");
    fprintf(stderr, "  pointing at the pole.\n\n");
    fprintf(stderr, "We always read the precision encoder and \n"); 
    fprintf(stderr, "  set the motor encoders to match.\n");
  }
      
  /* Make the connection */
  
  /* On the Sidereal Technologies controller                                */
  /*   there is an FTDI USB to serial converter that appears as             */
  /*   /dev/ttyUSB0 on Linux systems without other USB serial converters.   */
  /*   The serial device is known to the program that calls this procedure. */
  
  /* TelPortFD = open("/dev/ttyUSB0",O_RDWR); */
  
  TelPortFD = open(telserial,O_RDWR);
  if (TelPortFD == -1)
  {
    fprintf(stderr,"A200HR serial port %s not available ... \n",telserial);
    return;
  }
  fprintf(stderr,"A200HR serial port opened ... \n");
  tcgetattr(TelPortFD,&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(TelPortFD, TCSANOW, &tty);

  /* Flush the input (read) buffer */

  tcflush(TelPortFD,TCIOFLUSH);

  /* Test connection by asking for firmware version    */
   
  strcpy(sendstr,"XV\r");
  
  writen(TelPortFD,sendstr,3);
  numread=readn(TelPortFD,returnstr,4,2);
  if (numread !=0 ) 
  {
    returnstr[numread] = '\0';
    fprintf(stderr,"Planewave A200HR\n");
    fprintf(stderr,"Sidereal Technology %s\n",returnstr);
    fprintf(stderr,"Telescope connected \n");  
    TelConnectFlag = TRUE;   
  }
  else
  {
    fprintf(stderr,"A200HR drive control did not respond\n");    
    return;
  }  

  /* Flush the input buffer */

  tcflush(TelPortFD,TCIOFLUSH);
  
  /* Set global tracking parameters */
          
  /* Set rates for defaults */
    
  azmtrackrate0 = AZMSIDEREALRATE;
  alttrackrate0 = ALTSIDEREALRATE;
  azmtrackrate = 0;
  alttrackrate = 0;
       
  /* Perform startup tests for slew limits if any */

  flag = GetLimits(&limits);
  usleep(500000);
  limits = FALSE;
  flag = SetLimits(limits);
  usleep(500000);  
  flag = GetLimits(&limits);
      
  flag = SyncTelEncoders();
  if (flag != TRUE)
  {
    fprintf(stderr,"Initial telescope pointing request was out of range ... \n");
    fprintf(stderr,"Cycle power, set telescope to home position, restart. \n");    
    return;
  }
  
  /* Pause for telescope controller to initialize */
   
  usleep(500000);
  
  /* Read encoders and confirm pointing */
  
  GetTel(&homera, &homedec, RAW);
  
  fprintf(stderr, "Local latitude: %lf\n", SiteLatitude);
  fprintf(stderr, "Local longitude: %lf\n", SiteLongitude);
  fprintf(stderr, "Local sidereal time: %lf\n", LSTNow()); 
  fprintf(stderr, "Mount type: %d\n", telmount);
  fprintf(stderr, "Mount motor encoder RA: %lf\n", homera);
  fprintf(stderr, "Mount motor encoder Dec: %lf\n", homedec);
  
  fprintf(stderr, "The telescope is on line ...\n\n");
    
  /* Flush the input buffer in case there is something left from startup */

  tcflush(TelPortFD,TCIOFLUSH);
  return;

}


/* Assign and save slewrate for use in StartSlew */

void SetRate(int newrate)
{
  if (newrate == SLEW) 
  {
    slewrate = 4000000; 
  }
  else if (newrate == FIND) 
  {
    slewrate = 400000;
  }
  else if (newrate == CENTER) 
  {
    slewrate = 100000;
  }
  else if (newrate == GUIDE) 
  {
    slewrate = 20000;
  }
  else
  {
    slewrate = 20000;
  }
  return;
}
 

/* Start a slew in chosen direction at slewrate */

void StartSlew(int direction)
{
  char slewcmd[32] = "";
  int nsend;
  int rate;
  int ntarget, starget, etarget, wtarget;
  
  rate = slewrate;
  
  /* Define directions                                       */
  /* Set here for GEM A200HR                                 */ 
  /* Northern hemisphere east of pier looking west           */
  /* These should be dynamically changed with pier side flip */
    
  GetGuideTargets(&ntarget, &starget, &etarget, &wtarget);
  
  if (direction == NORTH)
  {
    sprintf(slewcmd,"X%dS%d\r",ntarget,rate);
    nsend = strlen(slewcmd);
  }
  else if (direction == EAST)
  {
    sprintf(slewcmd,"Y%dS%d\r",etarget,rate);
    nsend = strlen(slewcmd);
  }
  else if (direction == SOUTH)
  {
    sprintf(slewcmd,"X%dS%d\r",starget,rate);
    nsend = strlen(slewcmd);
  }
  else if (direction == WEST)
  {
    sprintf(slewcmd,"Y%dS%d\r",wtarget,rate);
    nsend = strlen(slewcmd);
  }
  else
  {
    return;
  }  

  writen(TelPortFD,slewcmd,nsend);
    
  return;
   
}


/* Stop the slew in chosen direction */

void StopSlew(int direction)
{
  char slewcmd[32] = "";
  
  if (direction == NORTH)
  {
    sprintf(slewcmd,"XS0\r");
  }
  else if (direction == EAST)
  {
    sprintf(slewcmd,"YS0\r");
  }
  else if (direction == SOUTH)
  {
    sprintf(slewcmd,"XS0\r");
  }
  else if (direction == WEST)
  {
    sprintf(slewcmd,"YS0\r");
  }

  tcflush(TelPortFD,TCIOFLUSH);
  writen(TelPortFD,slewcmd,4);
  return;
}

void DisconnectTel(void)
{
  /* printf("DisconnectTel\n"); */
  if (TelConnectFlag == TRUE)
  {  
    close(TelPortFD);
  }
  TelConnectFlag = FALSE;
  return;
}


/* Synchronize the two encoder systems */
/* Returns 1 if synchronization is within 15 counts in both axes */

int SyncTelEncoders(void)
{

  char sendstr[32] = "";
  int nsend = 0;
  
  double nowra0, nowdec0;
  int mtrazmnew, mtraltnew;
      
  /* Zero points:                                                */
  /*   Alt-Az -- horizon north                                   */
  /*   Fork equatorial -- equator meridian                       */
  /*   German equatorial -- over the mount at the pole           */
  
  /* Signs:                                                      */
  /*   Dec or Alt -- increase from equator to pole and beyond    */ 
  /*     GEM dec sign is for west of pier looking east           */ 
  /*     GEM dec sign reverses when east of pier looking west    */
  /*   HA or Az   -- increase from east to west                  */

  /* Get the current pointing                                    */
  /* This sets the global telmntazm and telmntalt counters       */
    
  GetTel(&nowra0, &nowdec0, RAW);
  
  /* Assign the motor counts to match the mount counts           */
    
  mtrazmnew = ((double) mntazm) * mtrazmcal / mntazmcal;
  mtraltnew = ((double) mntalt) * mtraltcal / mntaltcal;
     
  /* Set the controller motor counters                           */
  
  tcflush(TelPortFD,TCIOFLUSH);
  sprintf(sendstr,"YF%d\r",mtrazmnew);
  nsend = strlen(sendstr);
  writen(TelPortFD,sendstr,nsend);
  
  usleep(100000);
  
  tcflush(TelPortFD,TCIOFLUSH);
  sprintf(sendstr,"XF%d\r",mtraltnew);
  nsend = strlen(sendstr);
  writen(TelPortFD,sendstr,nsend);
  
  usleep(100000);

  /* Query the controller to update the counters  */
  
  GetTel(&nowra0, &nowdec0, RAW); 
  
  if ( (abs(mtrazm - mtrazmnew) > 15) || (abs(mtralt - mtraltnew) > 15) )
  {
    return(0);
  }  

  return(1);
}


/* Read the motor and mount encoders                                  */
/* Global telmount which should be GEM for A200HR                     */
/* Save encoder counters in global variables                          */
/* Convert the counters to ha and dec                                 */
/* Use an NTP synchronized clock to find lst and ra                   */
/* Correct for the pointing model to find the true direction vector   */
/* Report the ra and dec at which the telescope is pointed            */

void GetTel(double *telra, double *teldec, int pmodel)
{  
   
  char sendstr[32] = "";
  char returnstr[32] = "";
  int numread;
   
  /* Celestial coordinates derived from the pointing angles */ 
 
  double telha0 = 0.;
  double teldec0 = 0.;
  double telra0 = 0.;
  double telra1 = 0.;
  double teldec1 = 0.;
  
  /* Buffers for decoding the binary packet */
  
  int b0, b1, b2, b3;

  /* Send an XXS command to the controller */
  /* Controller should return 20 bytes of information */

  strcpy(sendstr,"XXS\r");
  tcflush(TelPortFD,TCIOFLUSH);  
  writen(TelPortFD,sendstr,4);
  numread=readn(TelPortFD,returnstr,20,2);
  if (numread !=20)
  {
    /* Brief pause then try again with more wait time */
    usleep(100000);
    tcflush(TelPortFD,TCIOFLUSH);  
    writen(TelPortFD,sendstr,4);
    numread=readn(TelPortFD,returnstr,20,4);
    if (numread !=20)
    {
      fprintf(stderr,"Telescope control is not responding ...\n");
      fprintf(stderr,"Read %d / 20 bytes in GetTel data packet \n", numread);

      return;
    }  
  }

  /* Parse motor encoder readings from the response         */
  /* Caution that Sitech 24 bit counters seem to ignore b0  */
  /* Little endian order                                    */
  
  
  /* Translate altitude packet to integers without sign */
  
  b0 = (unsigned char) returnstr[0];
  b1 = (unsigned char) returnstr[1];
  b2 = (unsigned char) returnstr[2];
  b3 = (unsigned char) returnstr[3];
  
  /* Compute an unsigned altitude encoder count */
  
  mtralt = b1 + 256*b2 + 256*256*b3;
       
    
  /* Translate azimuth packet to integers without sign */
   
  b0 = (unsigned char) returnstr[4];
  b1 = (unsigned char) returnstr[5];
  b2 = (unsigned char) returnstr[6];
  b3 = (unsigned char) returnstr[7];
     

  /* Compute an unsigned azimuth encoder count */

  mtrazm =  b1 + 256*b2 +256*256* b3;

  /* Always complement the azimuth when the sign bit set */

  if (mtrazm > 8388608)
  {
    mtrazm = -(16777217 - mtrazm);
  }

  /* There is an  ambiguity on the mtralt encoder */
  /* It does not cover the full range of declination motion */

  if ((mtralt > 8388608))
  {
    mtralt = -(16777217 - mtralt);
  }
            
  /* Translate mount altitude packet to integers without sign */
  
  b0 = (unsigned char) returnstr[8];
  b1 = (unsigned char) returnstr[9];
  b2 = (unsigned char) returnstr[10];
  b3 = (unsigned char) returnstr[11];
  
  /* Compute an unsigned encoder count */
  
  mntalt = b1 + 256*b2 + 256*256*b3;

  /* Complement when sign bit set */

  if (mntalt > 8388608)
  {
    mntalt = -(16777217 - mntalt);
  }   
  
  /* Translate mount azimuth packet to integers without sign */
   
  b0 = (unsigned char) returnstr[12];
  b1 = (unsigned char) returnstr[13];
  b2 = (unsigned char) returnstr[14];
  b3 = (unsigned char) returnstr[15];
     
  /* Compute an unsigned encoder count */

  mntazm =  b1 + 256*b2 +256*256* b3;

  if (mntazm > 8388608)
  {
    mntazm = -(16777217 - mntazm);
  }
        
  /* Calibration diagnostic */

  /* fprintf(stderr,"%d  %d  %d  %d  \n",mtrazm,mntazm,mtralt,mntalt); */
  
  
  /* Convert counts to degrees for all encoders             */
  /* Scaling values of countperdeg are signed               */
  /* Pointing angles now meet the software sign convention  */
  
  mtrazmdeg = ( (double) mtrazm ) / mtrazmcal;
  mtraltdeg = ( (double) mtralt ) / mtraltcal;
  mntazmdeg = ( (double) mntazm ) / mntazmcal;
  mntaltdeg = ( (double) mntalt ) / mntaltcal;
      
  /* Transform mount pointing angles to ha, ra and dec             */
  /* Assume sign convention and zero point for various mount types */
  
  if (telmount == GEM)
  {
    if ( mntazmdeg == 0. ) 
    {
      if ( mntaltdeg < 0.)
      {
        telha0 = -6.;
        teldec0 = 90. + mntaltdeg;
      }
      else
      {
        telha0 = +6.;
        teldec0 = 90. - mntaltdeg;
      }
    }
    else if ( mntazmdeg == -90. )
    {
      if ( mntaltdeg < 0.)
      {
        telha0 = 0.;
        teldec0 = 90. + mntaltdeg;
      }
      else
      {
        telha0 = -12.;
        teldec0 = 90. - mntaltdeg;
      }    
    }
    else if ( mntazmdeg == 90. )
    {
      if ( mntaltdeg > 0.)
      {
        telha0 = 0.;
        teldec0 = 90. - mntaltdeg;
      }
      else
      {
        telha0 = -12.;
        teldec0 = 90. + mntaltdeg;
      }    
    }   
    else if ((mntazmdeg > -180. ) && (mntazmdeg < -90.))
    {
      teldec0 = 90. - mntaltdeg;
      telha0 = Map12(6. + mntazmdeg/15.);  
    }
    else if ((mntazmdeg > -90. ) && (mntazmdeg < 0.))
    {
      teldec0 = 90. - mntaltdeg;
      telha0 = Map12(6. + mntazmdeg/15.);  
    }    
    else if ((mntazmdeg > 0. ) && (mntazmdeg < 90.))
    {
      teldec0 = 90. + mntaltdeg;
      telha0 = Map12(-6. + mntazmdeg/15.);  
    }    
    else if ((mntazmdeg > 90. ) && (mntazmdeg < 180.))
    {
      teldec0 = 90. + mntaltdeg;
      telha0 = Map12(-6. + mntazmdeg/15.);  
    }   
    else
    {
      fprintf(stderr,"HA encoder out of range\n");      
      teldec0 = 0.;
      telha0 = 0.;
    }   
    
    /* Flip signs for the southern sky */
    
    if (SiteLatitude < 0.)
    {
      teldec0 = -1.*teldec0;
      telha0 = -1.*telha0;
    }
        
    telra0 = Map24(LSTNow() - telha0);
  }
    
  else if (telmount == EQFORK)
  {
    teldec0 = mntaltdeg;
    telha0 = Map12(mntazmdeg/15.);
    
    /* Flip signs for the southern sky */
    
    if (SiteLatitude < 0.)
    {
      teldec0 = -1.*teldec0;
      telha0 = -1.*telha0;
    }
        
    telra0 = Map24(LSTNow() - telha0);    
  }
  
  else if (telmount == ALTAZ)
  {
    HorizontalToEquatorial(mntazmdeg, mntaltdeg, &telha0, & teldec0);
    telha0 = Map12(telha0);
    telra0 = Map24(LSTNow() - telha0);   
  }

  else
  {
    fprintf(stderr,"Unknown mounting type\n");  
    *telra=0.;
    *teldec=0.;
    return;
  }
    
  /* Handle special case if not already treated where dec is beyond a pole */
  
  if (teldec0 > 90.)
  {
    teldec0 = 180. - teldec0;
    telra0 = Map24(telra0 + 12.);
  }
  else if (teldec0 < -90.)
  {
    teldec0 = -180. - teldec0;
    telra0 = Map24(telra0 + 12.);
  }
    
  /* Apply pointing model to the coordinates that are reported by the telescope */
  
  PointingFromTel(&telra1, &teldec1, telra0, teldec0, pmodel);
      
  /* Return corrected values */

  *telra=telra1;
  *teldec=teldec1;

  /* Diagnostics */

  /* fprintf(stderr,"mtraltdeg: %lf  mtrazmdeg: %lf\n", mtraltdeg, mtrazmdeg);  */

  /* fprintf(stderr,"mntaltdeg: %lf mntazmdeg: %lf\n", mntaltdeg, mntazmdeg);  */
      
  return;
}

/* Go to new celestial coordinates                                            */
/* Based on CenterGuide algorithm rather than controller goto function        */
/* Evaluate if target coordinates are valid                                   */
/* Test slew limits in altitude, polar, and hour angles                       */
/* Query if target is above the horizon                                       */
/* Return without action for invalid requests                                 */
/* Interrupt any slew sequence in progress                                    */
/* Check current pointing                                                     */
/* Find encoder readings required to point at target                          */
/* Repeated calls are required                                                */
/* Return 1 if slew in progress                                               */
/* Return 0 if slew is not in progress                                        */

int GoToCoords(double newra, double newdec, int pmodel)
{
  char slewcmd[32] = ""; 
  int nsend;
  double telra, teldec;
  double telha;
  double newha, newalt, newaz;
  double raerr, decerr;
  double rarate, decrate;
  double rarate0, decrate0;
  double rasign, decsign;
  double deltime;
  double tolra, toldec;
  int ntarget, starget, etarget, wtarget;

  newha = LSTNow() - newra;
  newha = Map12(newha);
  
  /* Convert HA and Dec to Alt and Az */
  /* Test for visibility              */
  
  EquatorialToHorizontal(newha, newdec, &newaz, &newalt);
  
  /* Check altitude limit */
  
  if (newalt < MINTARGETALT)
  {
    StartTrack();
    fprintf(stderr,"Target is below the telescope horizon\n");
    return(0);
  }
     
  /* Get coordinates now */
  
  GetTel(&telra, &teldec, pmodel);
  
  telha = LSTNow() - telra;
  telha = Map12(telha);
  
  /* Find pointing errors in seconds of arc for both axes   */

  /* Allow for 24 hour ra wrap and trap dec at the celestial poles */
  
  raerr  = 3600.*15.*Map12(telra - newra);
  
  /* Allow for drift during reset to track rate at end of slew */
  /* This may need trimming for the polling or encoder reading times */
  
  raerr = raerr + 15.;
  
  if (fabs(teldec) < 89. )
  {
    decerr = 3600.*(teldec - newdec);
  }
  else
  {
    decerr = 0.;
  }

  /* Set slew tolerances in seconds of arc */
  /* Final trim could be with external fine point routine */
  
  tolra = SLEWTOLRA;
  toldec = SLEWTOLDEC;

  /* Within tolerance? */

  if ( ( fabs(raerr) < tolra ) && ( fabs(decerr) < toldec ) )
  {

    /* Issue full stop request to assure that motors are not running on */
    /* This is for safety and may not be necessary */
    /* We have already set our target ahead of the true sky target */

    FullStop();
    
    /* Wait 1 second for telescope to settle */
    /* Sky drifts to bring the target to the pointing spot */
    
    usleep(1000000);
    
    /* Start tracking when the target is centered */
    
    StartTrack();
    usleep(1000000);
    return(0);
  }  

  /* Check GEM mount basis                         */
  /* Change target if there is a meridian crossing */
  
  if ( telmount == GEM )
  {
    if ( telha*newha < 0. )
    {
      if ( newha > 0. )
      {
        newha = +0.1;
        newdec = SiteLatitude;
      }
      else
      {
        newha = -0.1;
        newdec = SiteLatitude;
      }
      fprintf(stderr,"HA: %f (Tel)  %f (Target)\n", telha, newha);
      fprintf(stderr,"Changing mount basis ...\n");
    }  
  }  
    

  /* Calculate new signed rates based on these errors */
    
  /* Set the default axis slew rates to the azimuth sidereal rate */
  
  rarate0  = azmtrackrate0;
  decrate0 = azmtrackrate0;
  
  /* Select fast slew command if allowed */
  /* Not recommended:  places larger inertial loads on the gear train */

  if ( SLEWFAST )
  {
    rarate0  = 2.*rarate0;
    decrate0 = 2.*decrate0;
  }
  

  /* Set the time allowed for slew                    */
  /* Adjust to get convergence by overshooting        */
  /* INDI tel uses a 0.25 second polling during goto  */
  /*   but it polls goto status every 16 cycles       */
      
  deltime = 0.5;

  /* Calculate a new rate based on 15 arcseconds/second sidereal rate    */
  /* At this rate the telescope will reach the target in 10 deltimes     */
  
  rarate = 0.1*(raerr/15.)*(1./deltime);
  decrate = -0.1*(decerr/15.)*(1./deltime);
  

  /* Separate magnitude and sign to process rate limits */

  if (rarate > 0.)
  {
    rasign = 1.;
  }
  else if (rarate < 0.)
  { 
    rasign = -1.;
  }
  else
  {
    rasign = 0.;
  }
  rarate = fabs(rarate);
  

  if (decrate > 0.)
  {
    decsign = 1.;
  }
  else if (decrate < 0.)
  { 
    decsign = -1.;
  }
  else
  {
    decsign = 0.;
  }
  decrate = fabs(decrate);
  


  /* Upper limit rates to 150 times sidereal */
    
  if (rarate > 150.)
  {
    rarate = 150.;
  }

  if (decrate > 150.)
  {
    decrate = 150.;
  }
     
  
  /* Lower limit rates to 0.1 times sidereal */
    
  if (rarate < 0.1)
  {
    rarate = 0.1;
  }

  if (decrate < 0.1)
  {
    decrate = 0.1;
  }   

  /* Restore signs and scale to sidereal rate */
  /* Add additional sidereal term to keep up with the sky during slew */

  rarate = (1. + rarate*rasign)*rarate0;
  decrate = decrate*decsign*decrate0;


  /* Convert to integer rates for drive controller */ 
  /* Set targets based on sign */
  /* Strip signs from integer rates */ 
  /* Set directions to target */
  
  GetGuideTargets(&ntarget, &starget, &etarget, &wtarget);
  
  azmtrackrate = (int) rarate;
  azmtracktarget = wtarget;
  if ( azmtrackrate < 0 )
  {
    azmtrackrate = -1*azmtrackrate;
    azmtracktarget = etarget;
  }
  
  alttrackrate = (int) decrate;
  alttracktarget = ntarget;
  if ( alttrackrate < 0 )
  {
    alttrackrate = - 1*alttrackrate;
    alttracktarget = starget;  
  }
  
  /* Use the ASCII set velocity command for the azimuth (Y) axis */

  sprintf(slewcmd,"Y%dS%d\r",azmtracktarget,azmtrackrate);  
  nsend = strlen(slewcmd);
  tcflush(TelPortFD,TCIOFLUSH);  
  writen(TelPortFD,slewcmd,nsend);  
  usleep(100000);
  
  /* Use the ASCII set velocity command for the altitude (X) axis */
  
  sprintf(slewcmd,"X%dS%d\r",alttracktarget,alttrackrate); 
  nsend = strlen(slewcmd);
  tcflush(TelPortFD,TCIOFLUSH);  
  writen(TelPortFD,slewcmd,nsend);  
  usleep(100000);
  
  /* A slew is in progress */

  fprintf(stderr,"A slew is in progress\n");
      
  return(1);
}

/* Check slew status and continue a slew to target           */
/* Return 0 if underway                                      */
/* Return 1 if done                                          */
/* This is the opposite logic of GoToCoords                  */

int CheckGoTo(double newra, double newdec, int pmodel)
{

  int status;

  status = GoToCoords(newra, newdec, pmodel);

  if (status == 1)
  {
    /* Slew is in progress */
    return(0);
  }
    
  return(1);
  
}  
  



/* Coordinates and time */

/* Synchronize remote telescope to this UTC */

int SyncTelToUTC(double newutc)
{
  return 0;
}

/* Synchronize remote telescope to this observatory location  */

int SyncTelToLocation(double newlong, double newlat, double newalt)
{
  return 0;
}

/* Synchronize remote telescope to this local sidereal time  */

int SyncTelToLST(double newlst)
{
  return 0;
}

/* Start sidereal tracking on all axes at default rates      */
/* Can be generalized to triaxial tracking for alt-az mounts */
/* Rates are actively adjusted by CenterGuide and reset here */

void StartTrack(void)
{
  
  char slewcmd[32] = ""; 
  int nsend;
  int ntarget, starget, etarget, wtarget;
  
  GetGuideTargets(&ntarget, &starget, &etarget, &wtarget);
  
  /* Default rates and targets should be set elsewhere to reasonable values */
  /* CenterGuide will update rates based on mount encoder readings          */
  /* A call to StartTrack will restore default rates                        */
  /* For an equatorial mount it sets                                        */
  /* RA at the sidereal rate and Dec off                                    */
    
  if (telmount != ALTAZ)
  {
  
    /* Start azimuth (RA) axis tracking at sidereal rate                    */
    /* Use the ASCII set velocity command for the azimuth (Y) axis          */
        
    sprintf(slewcmd,"Y%dS%d\r",wtarget,azmtrackrate0);  
    nsend = strlen(slewcmd);
    tcflush(TelPortFD,TCIOFLUSH);  
    writen(TelPortFD,slewcmd,nsend);
    
    usleep(100000);
    
    /* Turn off any altitude (Dec) axis tracking */
    
    sprintf(slewcmd,"XS0\r");  
    nsend = strlen(slewcmd);
    tcflush(TelPortFD,TCIOFLUSH);  
    writen(TelPortFD,slewcmd,nsend);  
    
  }
  else
  {  
    /* This branch would not be executed for an A200HR mount             */
    /* It may be useful for other applications of the Sitech  controller */
    /* Set the tracking rates and targets based on altitude and azimuth  */
        
    /* Code to start triaxial tracking goes here                         */
  }
  
  return;
} 


/* Maintain target center using mount encoders                           */
/* Adjust motor rates on both axes based on pointing model               */
/* Setting of pmodel should match that used to find reference center     */
/* Allow pointing model updates based on guiding corrections             */

void CenterGuide(double centerra, double centerdec, 
  int raflag, int decflag, int pmodel)
{
  char slewcmd[32] = ""; 
  int nsend;
  double telra, teldec;
  double raerr, decerr;
  double rarate, decrate;
  double rarate0, decrate0;
  double deltime;
  int ntarget, starget, etarget, wtarget;

  /* Set directions for guiding corrections */
  
  GetGuideTargets(&ntarget, &starget, &etarget, &wtarget);
  
  /* Set the time allowed for corrections                              */
  /* This time should be greater than or equal to the polling interval */
  
  deltime = 1.;
 
  /* Do nothing unless flags are set */
  
  if ((raflag == 0) && (decflag == 0))
  {
    return;
  }   
  
  /* Grab latest integer tracking rates and save as floating point */
  
  rarate0 = azmtrackrate0;
  decrate0 = alttrackrate0;

  /* Get the telescope coordinates now */
  
  GetTel(&telra, &teldec, pmodel);
    
  /* Update pointing errors in seconds of arc for both axes   */
  /* Allow for 24 hour ra wrap and dec at the celestial poles */
  
  raerr  = 3600.*15.*Map12(telra - centerra);
  if (fabs(teldec) < 89. )
  {
    decerr = 3600.*(teldec - centerdec);
  }
  else
  {
    decerr = 0.;
  }
  
  /* Calculate new tracking rates based on these errors                 */
  /* Telescope may run for deltime at this rate to correct the error    */
  /* Base dec correction on ra sidereal rate for equatorial mount       */
  
  rarate = rarate0 + rarate0*(raerr/15.)*(1./deltime);
  decrate =   decrate0 - 0.05*rarate0*(decerr/15.)*(1./deltime);
  
  /* Limit rate changes to 20% of sidereal rate to minimize tracking noise */
  
  if (rarate > 1.2*rarate0)
  {
    rarate = 1.2*rarate0;
  }
  else if (rarate < 0.8*rarate0)
  {
    rarate = 0.8*rarate0;
  }
  
  if (decrate > 0.05*rarate0)
  {
    decrate = 0.05*rarate0;
  }
  else if (decrate < -0.05*rarate0)
  {
    decrate = -0.05*rarate0;
  }  
  
  /* Convert to integer rates for drive controller */  
  
  azmtrackrate = (int) rarate;
  azmtracktarget = wtarget;
  if ( azmtrackrate < 0 )
  {
    azmtrackrate = 0;
  }
  
  alttrackrate = (int) decrate;
  alttracktarget = ntarget;
  if ( alttrackrate < 0 )
  {
    alttrackrate = - 1*alttrackrate;
    alttracktarget = starget;  
  }
  
  /* Use the ASCII set velocity command for the azimuth (Y) axis */
  /* Issue ra corrections if raflag is true */

  if (raflag == TRUE)
  {
    sprintf(slewcmd,"Y%dS%d\r",azmtracktarget,azmtrackrate);  
    nsend = strlen(slewcmd);
    tcflush(TelPortFD,TCIOFLUSH);  
    writen(TelPortFD,slewcmd,nsend);  
    usleep(100000);
  }
 
  /* Use the ASCII set velocity command for the altitude (X) axis */
  /* Issue dec corrections if decflag is true */
  
  if (decflag == TRUE)
  {
    sprintf(slewcmd,"X%dS%d\r",alttracktarget,alttrackrate); 
    nsend = strlen(slewcmd);
    tcflush(TelPortFD,TCIOFLUSH);  
    writen(TelPortFD,slewcmd,nsend);  
    usleep(100000);
  } 
       
  return;
}

/* Stop tracking */

void StopTrack(void)
{
  
  char slewcmd[32] = "";
  
  sprintf(slewcmd,"YN\r");
  tcflush(TelPortFD,TCIOFLUSH);
  writen(TelPortFD,slewcmd,3);
  
  if (telmount == ALTAZ)
  {
    usleep(100000);
    sprintf(slewcmd,"XN\r");
    tcflush(TelPortFD,TCIOFLUSH);
    writen(TelPortFD,slewcmd,3);  
  }
  
  return;
}


/* Get guide targets for different pointings                        */
/* Returns targets with correct sense on the sky for N, S, E, and W */
/* With a GEM it follows pier side swap based on HA encoder reading */

void GetGuideTargets(int *ntarget, int *starget, int *etarget, int *wtarget)
{
  double latitude, colatitude;
  int ntarget1, starget1, etarget1, wtarget1;
  
  latitude = fabs(SiteLatitude);
  colatitude = 90. - latitude;
  
  /* Depends on global mtrazmdeg and mtraltdeg set in GetTel            */
  /* First set limits for northern hemisphere                           */
  /* Then change assignments for southern hemisphere if needed          */
  
  /* Sign convention for mnt and  mtr degrees is:                       */
  /*   altdeg = 0 at the pole                                           */
  /*   azmdeg = 0 over the top of the GEM                               */
  /*   altdeg <0 going south from the pole for OTA west looking east    */
  /*   azmdeg <0 for rotation toward the east from above the GEM        */
  
  /* Parameters are set based on signed mtrazmcal and mtraltcal         */
  /* For our A200HR mtrazmcal is >0 and mtraltcal is <0                 */
  
  if (telmount == GEM)
  {
    /* Handle limits for different sides of the pier */
        
    if (mntazmdeg >= 0.)
    {
      /* Telescope west of pier pointed east  */
            
      wtarget1 = 100.*mtrazmcal;
      etarget1 = -10.*mtrazmcal;
      
      if (mntaltdeg <= 0.)
      {
        /* Telescope pointed between pole and south horizon */
                
        ntarget1 = 10.*mtraltcal;
        starget1 = -1.*(90. + colatitude)*mtraltcal;
      }
      else
      {
        /* Telescope pointed beyond the pole */
                
        ntarget1 = -1.*(90. + colatitude)*mtraltcal;
        starget1 = latitude*mtraltcal;
      }  
    }
    else
    {
      /* Telescope east of pier pointed west */
                  
      wtarget1 = 10.*mtrazmcal;
      etarget1 = -100.*mtrazmcal;
      
      if (mntaltdeg > 0.)
      {
        /* Telescope pointed between pole and south horizon */
        
        ntarget1 = -10.*mtraltcal;
        starget1 = (90. + colatitude)*mtraltcal;
      }
      else
      {
        /* Telescope pointed beyond the pole */
        
        ntarget1 = (90. + colatitude)*mtraltcal;
        starget1 = -1.*latitude*mtraltcal;
      } 
    }    
  }
  else if (telmount == EQFORK)
  { 
    /* Should be refined for specific installation */
    /* Here set to allow going  through fork to point under the pole */
         
    ntarget1 = 10.*mtraltcal;
    starget1 = -1.*(90. + colatitude)*mtraltcal;
    etarget1 = -100.*mtrazmcal;  
    wtarget1 = 100.*mtrazmcal;
  }
  else
  {
    /* Should be refined for specific installation */
    /* Here set to allow  going through azimuth 0 */
    
    ntarget1 = 90.*mtraltcal;
    starget1 = -90.*mtraltcal;
    etarget1 = -190.*mtrazmcal;  
    wtarget1 = 190.*mtrazmcal;  
  }

  if (SiteLatitude >= 0.)
  {
    /* Northern hemisphere */
    
    *ntarget = ntarget1;
    *starget = starget1;
    *etarget = etarget1;
    *wtarget = wtarget1;
  }
  else
  {
    /* Southern hemisphere */
    /* Counter directions are reversed */
    
    *ntarget = starget1;
    *starget = ntarget1;
    *etarget = wtarget1;
    *wtarget = etarget1;        
  }


  return;
}


/* Full stop */

void FullStop(void)

{  
  char slewcmd[32] = "";
  
  sprintf(slewcmd,"XN\r");
  tcflush(TelPortFD,TCIOFLUSH);
  writen(TelPortFD,slewcmd,3);
  usleep(100000);
  sprintf(slewcmd,"YN\r");
  tcflush(TelPortFD,TCIOFLUSH);
  writen(TelPortFD,slewcmd,3);
  return;
}

/* Set slew limits control off or on */

int SetLimits(int limits)
{
  if ( limits == TRUE )
  {
    fprintf(stderr,"Limits enabled\n"); 
  
  }
  else
  {
    limits = FALSE;
    fprintf(stderr,"Limits disabled\n");   
  }
       
  return (limits);
}


/* Get status of slew limits control */

int GetLimits(int *limits)
{
  int sitechlimits;
  sitechlimits = 0;
  
  if ( sitechlimits == 1 )
  {
    *limits = 1;
  }
  else
  {
    *limits = 0;
  }

  return (sitechlimits);
}
  


/* Control the dew and drive heaters */

void Heater(int heatercmd)
{
  char cmdstr[256];
  sprintf(cmdstr,"setheater %d 1>/dev/null 2>/dev/null", heatercmd);
  system(cmdstr);   
  return;
}

/* Control the telescope fans */

void Fan(int fancmd)
{
  char cmdstr[256];
  sprintf(cmdstr,"setfan %d 1>/dev/null 2>/dev/null", fancmd);
  system(cmdstr);    
  return;
}


/* Adjust the focus using an external routine */
/* The routine will time out on its own and report through the status file */

void Focus(int focuscmd, int focusspd)
{
  char cmdstr[256];
  sprintf(cmdstr,"setfocus %d %d  1>/dev/null 2>/dev/null", focuscmd, focusspd);
  system(cmdstr);  
  return;
}


/* Report the current focus reading from the status file */

void GetFocus(double *telfocus)
{
  int nread;
  double current_focus;
  char cmdstr[256];
  sprintf(cmdstr,"getfocus 1>/dev/null 2>/dev/null");
  system(cmdstr);  
  
  focusfile = (char*) malloc (MAXPATHLEN);
  strcpy(focusfile,FOCUSFILE);
  fp_focus = fopen(focusfile, "r");
  if (fp_focus == NULL)
  {
    return;
  }
  
  nread = fscanf(fp_focus, "%lg", &current_focus);
  fclose(fp_focus);
  
  if (nread !=0)
  {
    *telfocus = current_focus;
  }
  
  return;  
}


/* Adjust the rotation */

void Rotate(int rotatecmd, int rotatespd)
{
  char cmdstr[256];
  sprintf(cmdstr,"setrotate %d %d  1>/dev/null 2>/dev/null", rotatecmd, rotatespd);
  system(cmdstr);
}

/* Report the rotation setting */

void GetRotate(double *telrotate)
{
  int nread;
  double current_rotate;
  char cmdstr[256];
  sprintf(cmdstr,"getrotate 1>/dev/null 2>/dev/null");
  system(cmdstr);  
  
  rotatefile = (char*) malloc (MAXPATHLEN);
  strcpy(rotatefile,ROTATEFILE);
  fp_rotate = fopen(rotatefile, "r");
  if (fp_rotate == NULL)
  {
    *telrotate = 0.;
    return;
  }
  
  nread = fscanf(fp_rotate, "%lg", &current_rotate);
  fclose(fp_rotate);
  
  if (nread !=0)
  {
    *telrotate = current_rotate;
  }
  
  return;  
}

/* Read and report the temperature */

void GetTemperature(double *teltemperature)
{
  int nread;
  double current_temperature = 20.;
  char cmdstr[256];
  sprintf(cmdstr,"gettemperature 1>/dev/null 2>/dev/null");
  system(cmdstr);  
  
  temperaturefile = (char*) malloc (MAXPATHLEN);
  strcpy(temperaturefile,TEMPERATUREFILE);
  fp_temperature = fopen(temperaturefile, "r");
  if (fp_temperature == NULL)
  {
    *teltemperature= 0.;
    return;
  }
  
  nread = fscanf(fp_temperature, "%lg", &current_temperature);
  fclose(fp_temperature);
  
  if (nread !=0)
  {
    *teltemperature = current_temperature;
  }
  

  *teltemperature = current_temperature;
  
  return;   
}


/* Time synchronization utilities */

/* Reset the telescope sidereal time */

int  SyncLST(double newTime)
{		
  fprintf(stderr,"XmTel uses computer time and site location only \n");
  return -1;
}


/*  Reset the telescope local time */

int  SyncLocalTime()
{
  fprintf(stderr,"XmTel uses computer time only \n");
  return -1;
}


/* 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);
    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);
}

