/* -------------------------------------------------------------------------- */
/* -                   Astronomical Telescope Control                       - */
/* -                             INDI Driver                                - */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/* Copyright (c) 2008-2012 John Kielkopf                                      */
/* kielkopf@louisville.edu                                                    */
/*                                                                            */
/* This file is part of XmTel.                                                */
/*                                                                            */
/* Distributed under the terms of the General Public License (see LICENSE)    */ 
/*                                                                            */
/* Date: August 20, 2012                                                      */
/* Version: 6.0 of the INDI driver                                            */ 
/*                                                                            */
/* Credits: INDI components included here were derived from software          */
/* Copyright (C) 2003 Elwood C. Downey and originally distributed under       */
/* the terms of the GNU Lesser General Public License version 2.1.            */ 
/*                                                                            */
/* Regarding coordinates:                                                     */
/*                                                                            */
/* The tel drivers use "epoch of date" (EOD) coordinates.  They assume        */
/*   a telescope hardware controller that does not precess                    */
/*   what it sends or receives.  The INDI tel driver accepts epoch 2000.0     */
/*   coordinates from XEphem and converts them to EOD. It returns             */
/*   EOD and epoch 2000 separately as read-only coodinates.                   */
/*   This tel driver does not accept edb entries (but see XmTel).             */
/*                                                                            */
/* XmTel also uses EOD internally.  It accepts edb coordinates from XEphem    */
/*   and precessses them to EOD.  The user interface offers a toggle from     */
/*   EOD to epoch 2000.  Telescope pointing commands are sent in EOD.         */
/*   Telescope pointer information is sent to XEphem in epoch 2000.           */
/*   XmTel also will allow for proper motion from an edb or queue entry.      */
/*                                                                            */
/* A command line interface using setINDI should set the epoch 2000           */
/*   target coordinates, and read the epoch 2000 pointing information         */
/*   if it is used in conjunction with a database or other display            */
/*   software providing epoch 2000.0 FK5 coordinates.                         */
/*                                                                            */
/* History:                                                                   */
/*                                                                            */
/* June 25, 2010                                                              */
/*   Version 1.3                                                              */
/*   Production version                                                       */
/*                                                                            */
/* August 20, 2010                                                            */
/*   Version 1.4                                                              */
/*   Modified for park position                                               */
/*                                                                            */
/* October 12, 2010                                                           */
/*   Version 1.4.1                                                            */
/*   Corrected indi park parsing code                                         */
/*                                                                            */
/* October 15, 2010                                                           */
/*   Version 1.4.2                                                            */
/*   Added StartTrack after all StopSlew                                      */
/*                                                                            */
/* October 23, 2010                                                           */
/*   Version 5.2.3                                                            */
/*   Matched version number to current XmTel                                  */
/*   Added speed options in focus buttons                                     */
/*                                                                            */
/* October 10, 2011                                                           */
/*   Version 5.3.0                                                            */
/*   Added CenterGuide for precision encoders                                 */
/*                                                                            */
/* August 15, 2012                                                            */
/*   Version 6.0                                                              */
/*   Precision encoder support                                                */
/*   Encoded focus support                                                    */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <dirent.h>
#include <errno.h>
#include <math.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#include "indidevapi.h"
#include "tel.h"
#include "protocol.h"
         

/* Dependencies from INDI (code provided in the XmTel package) */

/* indidrivermain.c */
/* eventloop.c */
/* libastro */
/* liblilxml */


/* Dependencies from XmTel */

/* protocol.c */
/* pointing.c */
/* algorithms.c */


/* Files */

FILE *fp_config;                      /* Configuration file pointer */
static char *configfile;              /* Configuration file name */
void read_config(void);               /* Configuration file reader */
void write_coords(double ra, double dec);    /* Writes ra and dec to status */

/* Internal telescope control flags some of which are obsolete */

int telflag;
int gotocmd;
int guidecmd;
int slewcmd;
int teldir;
int trackcmd;
int refcmd;
int modelcmd;
int rotatecmd;
int focuscmd;
int fancmd;
int heatercmd;

/* Telescope control parameters available for clients to read or set */

double targetra;      /* Epoch of date!! */
double targetdec;     /* Epoch of date!! */
double targetra2;     /* Epoch 2000.0    */
double targetdec2;    /* Epoch 2000.0    */
int telspd; 
int focusspd;
int rotatespd;
int pmodel;

/* Telescope parameters provided read-only to clients */

double telfocus;
double telrotate;
double teltemperature;
double telra;         /* Epoch of date!! */
double telha;         /* Epoch of date!! */
double teldec;        /* Epoch of date!! */
double telra2;        /* Epoch 2000.0    */
double teldec2;       /* Epoch 2000.0    */ 


/* Center guide control */

int guidedecflag=0;    /* Flag to control dec guide function */
int guideraflag=0;     /* Flag to control ra  guide function */
double guidera;        /* Guiding center ra and dec */ 
double guidedec;       /* Guiding center ra and dec */ 
 


/* Telescope data may not be made available to clients through this driver */

double offsetha = 0.;
double offsetdec = 0.;
double offsetha_default = 0.;
double offsetdec_default = 0.;
double polaraz = 0.;
double polaralt = 0.;
double arcsecperpix = ARCSECPERPIX;
double modelha0 = 0.;
double modelha1 = 0.;
double modeldec0 = 0.;
double modeldec1 = 0.;
double modelha1_default = 0.;
double modeldec1_default = 0.;

double SiteLatitude = LATITUDE;        /* Latitude in degrees + north */  
double SiteLongitude = LONGITUDE;      /* Longitude in degrees + west */  
double SiteAltitude = ALTITUDE;        /* Altitude in meters */
double SitePressure = PRESSURE;        /* Atmospheric pressure in Torr */
double SiteTemperature = TEMPERATURE;  /* Local atmospheric temperature  in C */
double parkha = PARKHA;                /* Park telescope at this HA */
double parkdec = PARKDEC;              /* Park telescope at this Dec */
int    telmount = GEM;                 /* One of GEM, EQFORK, ALTAZ */
int    homenow = FALSE;                /* New homeha and homedec if true */
double homeha = 0.;                    /* Startup HA */
double homera = 0.;                    /* Startup RA reset based on HA */
double homedec = 0.;                   /* Startup Dec */
char   telserial[32];                  /* Serial port if needed */
  
/* Hardware-dependent external routines defined in the protocols */
/* The server calls these routines to control the telescope */
 
/* Interface control */

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

/* Slew and track control */

extern void SetRate(int newRate);
extern void StartSlew(int direction);
extern void StopSlew(int direction);
extern int  GetSlewStatus(void);
extern void StartTrack(void);
extern void CenterGuide(double centerra, double centerdec, 
  int raflag, int decflag, int pmodel);
extern void StopTrack(void);
extern void AutoTrack(void);
extern void FullStop(void);

/* Celestial coordinate read, write, and go to */

extern void GetTel(double *telra, double *teldec, int pmodel);
extern int  GoToCoords(double newra, double newdec, int pmodel);
extern int  CheckGoTo(double desra, double desdec, int pmodel);

/* Telescope instrumentation functions */

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

/* Corrections for proper motion, precession, aberration, and nutation */

extern void Apparent(double *ra, double *dec, int dirflag);  
extern void PrecessToEOD(double epoch, double  *ra, double  *dec);
extern void PrecessToEpoch(double epoch, double  *ra, double  *dec);
extern void ProperMotion(double epoch, double *ra, double *dec, 
  double pm_ra, double pm_dec); 
extern void TestAlgorithms(void);

/* Time from the computer system processed by the algorithms package */

extern double LSTNow(void);
extern double UTNow(void);

/* Utility */

extern double Map12(double ha);
extern double Map24(double ra);
extern double Map180(double dec);

  
/* ----------------------------------- */
/* ---- End of external routines  ---- */
/* ----------------------------------- */


/* Reference management  */

void target_telescope_reference(void); 
void clear_telescope_reference(void);
void recall_telescope_reference(void);
void save_telescope_reference(void);
void default_telescope_reference(void);
void wcs_telescope_reference(void);

/* Model management */

void clear_telescope_model(void);   /* Set model parameters to zero */
void recall_telescope_model(void);  /* Recall saved model from the status directory */
void save_telescope_model(void);    /* Save model in the status directory */
void default_telescope_model(void); /* Use the default model */



/* INDI prototypes and variables */


/* Function for indiserver requests           */

static void inittel(void);
static void telescope (void *p);

/* Process these values as numbers            */
/*                                            */
/* Read-write                                 */
/*                                            */
/* double targetra                            */
/* double targetdec                           */
/* double modelha1                            */
/* double modeldec1                           */
/* int slewrate                               */ 
/* int focusspd                               */
/* int rotatespd                              */
/*                                            */
/* Read only                                  */
/*                                            */
/* double telfocus                            */
/* double telrotate                           */
/* double teltemperature                      */
/* double telra                               */
/* double teldec                              */
/*                                            */
/* INDI number                                */
/*                                            */
/* Index name                                 */
/* Short description                          */
/* GUI display format                         */
/* Range, ignore if min == max                */
/* Step size, ignore if step == 0             */
/* Current value                              */
/* Pointer to parent property          */
/* Opt: handy place to hang helper info       */
/*                                            */
/*                                            */
/* INDI number property                       */
/*                                            */
/* Device name                                */
/* Property name                              */
/* Short description                          */
/* GUI grouping hint                          */
/* Client accessibility hint                  */
/* Current max time to change, secs           */
/* Current property state                     */
/* Numbers comprising this vector             */
/* Dimension of np[]                          */
/* ISO 8601 timestamp of this event           */
/* Opt: handy place to hang helper info       */


/* Telescope telra and teldec epoch of date */

static INumber indi_telescope[] = 
{ 
  {"ra", "RA H:M:S",   "%12.8m",   0., 24., 1., 0.},
  {"dec", "Dec D:M:S", "%10.6m", -90., 90., 1., 0.}   
};

static INumberVectorProperty indi_telescope_property = 
{ 
  MYDEV, 
  "telescope", 
  "Telescope Epoch of Date", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  indi_telescope, 
  NARRAY(indi_telescope) 
};

/* Telescope telra and teldec epoch 2000.0 */

static INumber indi_telescope_2000[] = 
{ 
  {"ra", "RA H:M:S",   "%12.8m",   0., 24., 1., 0.},
  {"dec", "Dec D:M:S", "%10.6m", -90., 90., 1., 0.}   
};

static INumberVectorProperty indi_telescope_2000_property = 
{ 
  MYDEV, 
  "telescope_2000", 
  "Telescope Epoch 2000", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  indi_telescope_2000, 
  NARRAY(indi_telescope_2000) 
};

/* Target targetra and targetdec epoch of date */

static INumber indi_target[] = 
{ 
  {"ra", "RA H:M:S",   "%12.8m",   0., 24., 1., 0.},
  {"dec", "Dec D:M:S", "%10.6m", -90., 90., 1., 0.}   
};

static INumberVectorProperty indi_target_property = 
{ 
  MYDEV, 
  "target", 
  "Target Epoch of Date", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  indi_target, 
  NARRAY(indi_target) 
};

/* Target targetra and targetdec epoch 2000.0 */

static INumber indi_target_2000[] = 
{ 
  {"ra", "RA H:M:S",   "%12.8m",   0., 24., 1., 0.},
  {"dec", "Dec D:M:S", "%10.6m", -90., 90., 1., 0.}   
};

static INumberVectorProperty indi_target_2000_property = 
{ 
  MYDEV, 
  "target_2000", 
  "Target Epoch 2000", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  indi_target_2000, 
  NARRAY(indi_target_2000) 
};


/* Pointing model parameters */

static INumber indi_model_parameters[] = 
{ 
  {"ha", "ha  pixels/hour",   "%5.1f",   -100., 100., 0.1, 0.},
  {"dec", "dec pixels/hour",  "%5.1f", -100., 100., 0.1, 0.}   
};

static INumberVectorProperty indi_model_parameters_property = 
{ 
  MYDEV, 
  "model_parameters", 
  "Model Parameters", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  indi_model_parameters, 
  NARRAY(indi_model_parameters) 
};


/* Focus reading telfocus */

static INumber indi_focus_reading = 
{ 
  "microns", 
  "Microns", 
  "%.0f", 
  -5000, 
  5000, 
  1, 
  0 
};

static INumberVectorProperty indi_focus_reading_property = 
{ 
  MYDEV, 
  "focus_reading", 
  "Focus Reading", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  &indi_focus_reading, 
  1 
};

/* Rotation reading telrotate */

static INumber indi_rotation_reading = 
{ 
  "degrees", 
  "Degrees", 
  "%.1f", 
  -360, 
  360, 
  1, 
  0 
};

static INumberVectorProperty indi_rotation_reading_property = 
{ 
  MYDEV, 
  "rotation_reading", 
  "Rotation Reading", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  &indi_rotation_reading, 
  1 
};

/* Temperature reading teltemperature */

static INumber indi_temperature_reading = 
{ 
  "celsius", 
  "Celsius", 
  "%.1f", 
  -360, 
  360, 
  1, 
  0 
};

static INumberVectorProperty indi_temperature_reading_property = 
{ 
  MYDEV, 
  "temperature_reading", 
  "Temperature Reading", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  &indi_temperature_reading, 
  1 
};

/* INDI switches                                   */
/*                                                 */
/* Index name                                      */
/* Switch label                                    */
/* Switch state                                    */
/* Opt: Pointer to parent                          */
/* Opt: Handy place to hang helper info            */
/*                                                 */
/* Device name                                     */
/* Property name                                   */
/* Short description will appear on a client GUI   */
/* GUI grouping hint                               */
/* Client accessibility hint                       */
/* Switch behavior hint                            */
/* Current max time to change, secs                */
/* Current property state                          */
/* Switches comprising this vector                 */
/* Dimension of sp[] (with help from NARRAY)       */
/* ISO 8601 timestamp of this event                */
/* Opt: handy place to hang helper info            */



/* Pointing switch  */
/* If no switch is on a RAW model is implied */

static ISwitch indi_pointing[] = 
{ 
  {"offset",      "Offset",            ISS_OFF},
  {"refraction",  "Refraction",        ISS_OFF},
  {"polar",       "Polar Alignment",   ISS_OFF},
  {"dynamic",     "Dynamic",           ISS_OFF} 
};

static ISwitchVectorProperty indi_pointing_property = 
{
  MYDEV, 
  "pointing", 
  "Pointing", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_pointing, 
  NARRAY(indi_pointing)
};


/* Reference switch refcmd */

static ISwitch indi_reference[] = 
{ 
  {"target",  "Target",  ISS_OFF},
  {"wcs",     "WCS",     ISS_OFF}, 
  {"clear",   "Clear",   ISS_OFF},  
  {"save",    "Save",    ISS_OFF},
  {"recall",  "Recall",  ISS_OFF},  
  {"default", "Default", ISS_OFF}     
};

/* Reference switch property */

static ISwitchVectorProperty indi_reference_property = 
{
  MYDEV, 
  "reference", 
  "Reference", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_reference, 
  NARRAY(indi_reference)
};


/* Model switch modelcmd */

static ISwitch indi_model[] = 
{ 
  {"clear",   "Clear",   ISS_OFF},  
  {"save",    "Save",    ISS_OFF},
  {"recall",  "Recall",  ISS_OFF},  
  {"default", "Default", ISS_OFF}     
};

/* Model switch property */

static ISwitchVectorProperty indi_model_property = 
{
  MYDEV, 
  "model", 
  "Model", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_model, 
  NARRAY(indi_model)
};



/* Go to switch gotocmd */

static ISwitch indi_goto[] = 
{ 
  {"target", "Target", ISS_OFF},
};

/* Go to switch property */

static ISwitchVectorProperty indi_goto_property = 
{
  MYDEV, 
  "goto", 
  "Go To ", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_goto, 
  NARRAY(indi_goto)
};


/* Set target */

static ISwitch indi_set_target[] = 
{ 
  {"zenith", "Zenith", ISS_OFF},
  {"meridian", "Meridian", ISS_OFF},
  {"park",  "Park",  ISS_OFF}
};

/* Set target property */

static ISwitchVectorProperty indi_set_target_property = 
{
  MYDEV, 
  "set_target", 
  "Set Target", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_set_target, 
  NARRAY(indi_set_target)
};


/* Slew state switch for slewcmd and teldir */

static ISwitch indi_slew_state[] = 
{ 
  {"north", "North", ISS_OFF},
  {"south", "South", ISS_OFF},
  {"east",  "East",  ISS_OFF},
  {"west",  "West",  ISS_OFF}
};

/* Slew state switch property */

static ISwitchVectorProperty indi_slew_state_property = 
{
  MYDEV, 
  "slew", 
  "Slew", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_slew_state, 
  NARRAY(indi_slew_state)
};

/* Slew speed switch telspd */

static ISwitch indi_slew_speed[] = 
{ 
  {"slew",   "Slew",   ISS_OFF},
  {"find",   "Find",   ISS_ON},
  {"center", "Center", ISS_OFF},
  {"guide",  "Guide",  ISS_OFF}
};

/* Slew speed switch property */

static ISwitchVectorProperty indi_slew_speed_property = 
{
  MYDEV, 
  "slew_speed", 
  "Slew Speed", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_slew_speed, 
  NARRAY(indi_slew_speed)
};

/* Track state switch trackcmd */

static ISwitch indi_track_state[] = 
{ 
  {"on", "On", ISS_ON}
};

/* Track state switch property */

static ISwitchVectorProperty indi_track_state_property = 
{
  MYDEV, 
  "track", 
  "Track", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_track_state, 
  NARRAY(indi_track_state)
};

/* Guide state switch guidecmd */

static ISwitch indi_guide_state[] = 
{ 
  {"on", "On", ISS_OFF}
};

/* Guide state switch property */

static ISwitchVectorProperty indi_guide_state_property = 
{
  MYDEV, 
  "guide", 
  "Guide", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_guide_state, 
  NARRAY(indi_guide_state)
};

/* Focus state switch focuscmd */

static ISwitch indi_focus_state[] = 
{ 
  {"off", "Off", ISS_ON},
  {"in",  "In",  ISS_OFF},
  {"out", "Out", ISS_OFF}
};

/* Focus state switch property */

static ISwitchVectorProperty indi_focus_state_property = 
{
  MYDEV, 
  "focus", 
  "Focus", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_focus_state, 
  NARRAY(indi_focus_state)
};

/* Focus speed switch focusspd */

static ISwitch indi_focus_speed[] = 
{ 
  {"8x",   "8X",  ISS_OFF},
  {"4x",   "4X",  ISS_OFF},
  {"2x",   "2X",  ISS_OFF},
  {"1x",   "1X",  ISS_ON}
};

/* Focus speed switch property */

static ISwitchVectorProperty indi_focus_speed_property = 
{
  MYDEV, 
  "focus_speed", 
  "Focus Speed", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_focus_speed, 
  NARRAY(indi_focus_speed)
};

/* Rotate state switch rotatecmd */

static ISwitch indi_rotate_state[] = 
{ 
  {"off", "Off", ISS_ON},
  {"ccw", "CCW", ISS_OFF},
  {"cw",  "CW",  ISS_OFF}
};

/* Rotate state switch property */

static ISwitchVectorProperty indi_rotate_state_property = 
{
  MYDEV, 
  "rotate", 
  "Rotate", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_rotate_state, 
  NARRAY(indi_rotate_state)
};

/* Rotate speed switch rotatespd */

static ISwitch indi_rotate_speed[] = 
{ 
  {"sidereal", "Sidereal", ISS_OFF},
  {"slow",     "Slow",     ISS_OFF},
  {"fast",     "Fast",     ISS_ON}
};

/* Rotate speed switch property */

static ISwitchVectorProperty indi_rotate_speed_property = 
{
  MYDEV, 
  "rotate_speed", 
  "Rotate Speed", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_rotate_speed, 
  NARRAY(indi_rotate_speed)
};

/* Fan switch fancmd */

static ISwitch indi_fan[] = 
{ 
  {"off",  "Off",  ISS_ON},
  {"low",  "Low",  ISS_OFF},
  {"high", "High", ISS_OFF}
};

/* Fan switch property */

static ISwitchVectorProperty indi_fan_property = 
{
  MYDEV, 
  "fan", 
  "Fan", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_fan, 
  NARRAY(indi_fan)
};

/* Heater switch heatercmd */

static ISwitch indi_heater[] = 
{ 
  {"off",  "Off",  ISS_ON},
  {"low",  "Low",  ISS_OFF},
  {"high", "High", ISS_OFF}
};

/* Heater switch property */

static ISwitchVectorProperty indi_heater_property = 
{
  MYDEV, 
  "heater", 
  "Heater", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_heater, 
  NARRAY(indi_heater)
};


/* INDI functions */

/* Call once before any other IS function */
/* This turns on a timer */
/* When the timer expires it calls a camera routine */
/* The telescope routine will restart a new timer */

static void inittel()
{

  /* Initialization control */
  
  static int inited;
  
  if (inited)
  { 
    return;
  }
  
  /* Set initial values for control parameters */
    
  offsetha = 0.;
  offsetdec = 0.;
  polaraz = 0.;
  polaralt = 0.;
  pmodel = RAW;
  slewcmd = FALSE;
  guidecmd = FALSE;
  trackcmd = TRUE;
  refcmd = FALSE;
  modelcmd = FALSE;
  telspd = FIND;
  SetRate(telspd);
  focuscmd = FOCUSCMDOFF;
  focusspd = FOCUSSPD1;
  rotatecmd = ROTATECMDOFF;
  rotatespd = ROTATESPDSLOW; 
  fancmd = FANCMDOFF;
  heatercmd = HEATERCMDOFF; 
  SiteLatitude = LATITUDE;       
  SiteLongitude = LONGITUDE;     
  SiteAltitude = ALTITUDE;       
  SitePressure = PRESSURE;       
  SiteTemperature = TEMPERATURE;
  strcpy (telserial,TELSERIAL);
     
  /* Handle the configuration file */
  /* This may alter some of the defaults */
    
  read_config();
    
  /* Connect with the telescope */

  telflag=FALSE;
  
  fprintf(stderr,"Connecting to the local telescope ... \n");
  ConnectTel();
  telflag=CheckConnectTel();  

  if (telflag != TRUE)
  {
    fprintf(stderr,"The telescope controller is not available.");
    exit(1);
  }
                      
  /* Set flag so we do the initialization only once */
  
  inited = 1;

  /* Start the timer to poll the telescope */
  
  IEAddTimer (POLLMS, telescope, NULL);
}

/* Send client the definitions of all properties we want it to know about */

void ISGetProperties (const char *dev)
{
   if (dev && strcmp (MYDEV, dev))
   {
     return;
   }
    
   inittel();
   IDDefNumber (&indi_telescope_property, NULL);
   IDDefNumber (&indi_telescope_2000_property, NULL);
   IDDefNumber (&indi_target_property, NULL);   
   IDDefNumber (&indi_target_2000_property, NULL); 
   IDDefSwitch (&indi_set_target_property, NULL);
   IDDefSwitch (&indi_goto_property, NULL);
   IDDefSwitch (&indi_slew_state_property, NULL);
   IDDefSwitch (&indi_slew_speed_property, NULL); 
   IDDefSwitch (&indi_track_state_property, NULL);
   IDDefSwitch (&indi_guide_state_property, NULL);
   IDDefNumber (&indi_focus_reading_property, NULL);
   IDDefSwitch (&indi_focus_state_property, NULL);
   IDDefSwitch (&indi_focus_speed_property, NULL);
   IDDefNumber (&indi_rotation_reading_property, NULL);
   IDDefSwitch (&indi_rotate_state_property, NULL);
   IDDefSwitch (&indi_rotate_speed_property, NULL);
   IDDefNumber (&indi_temperature_reading_property, NULL);
   IDDefSwitch (&indi_fan_property, NULL);
   IDDefSwitch (&indi_heater_property, NULL);
   IDDefSwitch (&indi_pointing_property, NULL);
   IDDefSwitch (&indi_reference_property, NULL); 
   IDDefSwitch (&indi_model_property, NULL);
   IDDefNumber (&indi_model_parameters_property, NULL);   
}


/* Client is sending us a new blob */

void ISNewBLOB (const char *dev, const char *name, int sizes[],
  int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
{
  /* This driver does not accept blobs */
}

/* Client is sending us new text */

void ISNewText (const char *dev, const char *name, 
  char *texts[], char *names[], int n)
{
  /* This driver does not accept text */
}


/* Client is sending us new switch settings */

/* In Elwood Downey's notation, the search arguments here are Sv */
/* In my notation the arguments are _property */

void  ISNewSwitch (const char *dev, const char *name, 
  ISState *states, char *names[], int n)
{
  int i;
  static double dha, ddec;

  if (strcmp (dev, MYDEV))
  { 
      /* Ignore if this is not for us */
      return;
  }

  /* Handle a request to change the pointing model */
  /* Bits set in pmodel indicate which corrections are implemented */
  /* This routine sets and unsets selected bits */

  if (!strcmp (name, indi_pointing_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_pointing_property, names[i]);
      if (states[i] == ISS_ON) 
      {

        /* A request to add a pointing component was issued */
        
        sp->s = ISS_ON;

        /* Which models have we selected? */ 
                  
        if (!strcmp (sp->name, "offset"))
        {
          pmodel = pmodel | OFFSET;
        }
        if (!strcmp (sp->name, "refraction"))
        {
          pmodel = pmodel | REFRACT;
        }
        if (!strcmp (sp->name, "polar"))
        {
          pmodel = pmodel | POLAR;
        }
        if (!strcmp (sp->name, "dynamic"))
        {
          pmodel = pmodel | DYNAMIC;
        }         
        if ( pmodel == RAW )
        {
          indi_pointing_property.s = IPS_IDLE; 
        }
        else
        {
          indi_pointing_property.s = IPS_BUSY;
        }
        IDSetSwitch (&indi_pointing_property, NULL);
        break;       
      }
      if (states[i] == ISS_OFF) 
      {
        
        /* A request to remove a model component was issued */
        
        sp->s = ISS_OFF;
 
        /* Which models have we deselected? */ 
                  
        if (!strcmp (sp->name, "offset"))
        {
          pmodel = (pmodel & ~(OFFSET));
        }
        if (!strcmp (sp->name, "refraction"))
        {
          pmodel = (pmodel & ~(REFRACT));
        }
        if (!strcmp (sp->name, "polar"))
        {
          pmodel = (pmodel & ~(POLAR));
        }
        if (!strcmp (sp->name, "dynamic"))
        {
          pmodel = (pmodel & ~(DYNAMIC));
        }         
        if ( pmodel == RAW )
        {
          indi_pointing_property.s = IPS_IDLE; 
        }
        else
        {
          indi_pointing_property.s = IPS_BUSY;
        }
        IDSetSwitch (&indi_pointing_property, NULL);
        break;         
      }       
    }
  } 
  
  /* Handle a request to reset the reference system */
  /* An appropriate pointing model must also be enabled */

  if (!strcmp (name, indi_reference_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_reference_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request for a reference action was issued */
        
        sp->s = ISS_ON;
         
        /* Which action  have we selected? */ 
                  
        if (!strcmp (sp->name, "target"))
        {
          /* Reference telescope to target */

          target_telescope_reference();

          IDSetSwitch (&indi_reference_property, 
          "Pointing reference set to current target coordinates");


          
        }
        if (!strcmp (sp->name, "wcs"))
        {
          /* Reference telescope to wcs */
          
          wcs_telescope_reference();
          
          IDSetSwitch (&indi_reference_property, 
          "Pointing reference set to current WCS");         
          
        }
        if (!strcmp (sp->name, "clear"))
        {
          /* Clear pointing reference */
          
          clear_telescope_reference();
                    
          IDSetSwitch (&indi_reference_property, 
          "Pointing reference cleared");          
          
        }
        if (!strcmp (sp->name, "save"))
        {
          /* Save reference */
          
          save_telescope_reference();
          
          IDSetSwitch (&indi_reference_property, 
          "Pointing reference saved");                   
          
        }         
        if (!strcmp (sp->name, "recall"))
        {
          /* Recall reference */
          
          recall_telescope_reference();
          
          IDSetSwitch (&indi_reference_property, 
          "Pointing reference recalled");                   
          
        }         
        if (!strcmp (sp->name, "default"))
        {
          /* Use default reference */ 
          
          default_telescope_reference();
          
          IDSetSwitch (&indi_reference_property, 
          "Pointing reference set to default");                   
                            
        } 

        indi_reference_property.s = IPS_IDLE; 
        IDSetSwitch (&indi_reference_property, NULL);
        break;       
      }
      if (states[i] == ISS_OFF) 
      {
        
        /* A request to remove a reference action was issued */
        
        sp->s = ISS_OFF;
 
        /* Which actions have we deselected? */ 
                  
                 
        if (!strcmp (sp->name, "target"))
        {
          
        }
        if (!strcmp (sp->name, "wcs"))
        {
          
        }
        if (!strcmp (sp->name, "clear"))
        {
          
        }
        if (!strcmp (sp->name, "save"))
        {
          
        }         
        if (!strcmp (sp->name, "recall"))
        {
          
        }         
        if (!strcmp (sp->name, "default"))
        {
          
        }

        indi_reference_property.s = IPS_IDLE;
        IDSetSwitch (&indi_reference_property, NULL);
        break;        
      }          
    }
  
    /* Get the updated coordinates and make them available */
    
    usleep(1000000);
    GetTel(&telra, &teldec, pmodel);
    
    telha = Map12(LSTNow() - telra); 
                 
    telra2 = telra;
    teldec2 = teldec;
    Apparent(&telra2,&teldec2,-1);
    
    indi_telescope[0].value = telra;
    IDSetNumber (&indi_telescope_property, NULL);
    indi_telescope[1].value = teldec;
    IDSetNumber (&indi_telescope_property, NULL);
    
    indi_telescope_2000[0].value = telra2;
    IDSetNumber (&indi_telescope_2000_property, NULL);
    indi_telescope_2000[1].value = teldec2;
    IDSetNumber (&indi_telescope_2000_property, NULL);    
  
  }    

  /* Handle a request to reset the model parameters */
  /* An appropriate pointing model must also be enabled */

  if (!strcmp (name, indi_model_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_model_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request for a model action was issued */
        
        sp->s = ISS_ON;
         
        /* Which action  have we selected? */ 
                  
        if (!strcmp (sp->name, "clear"))
        {
          /* Clear model values */
          
          clear_telescope_model();
                    
          IDSetSwitch (&indi_model_property, 
          "Model parameters cleared");          
          
        }
        if (!strcmp (sp->name, "save"))
        {
          /* Save model values */
          
          save_telescope_model();
          
          IDSetSwitch (&indi_model_property, 
          "Model parameters saved");                   
          
        }         
        if (!strcmp (sp->name, "recall"))
        {
          /* Recall model values */
          
          recall_telescope_model();
          
          IDSetSwitch (&indi_model_property, 
          "Model parameters recalled");                   
          
        }         
        if (!strcmp (sp->name, "default"))
        {
          /* Use default model values */ 
          
          default_telescope_model();
          
          IDSetSwitch (&indi_model_property, 
          "Model parameters set to default");                   
                            
        } 

        indi_model_property.s = IPS_IDLE; 
        IDSetSwitch (&indi_model_property, NULL);
        break;       
      }
      if (states[i] == ISS_OFF) 
      {
        
        /* A request to remove a model parameter action was issued */
        
        sp->s = ISS_OFF;
 
        /* Which actions have we deselected? */ 
                  
                 
        if (!strcmp (sp->name, "clear"))
        {
          
        }
        if (!strcmp (sp->name, "save"))
        {
          
        }         
        if (!strcmp (sp->name, "recall"))
        {
          
        }         
        if (!strcmp (sp->name, "default"))
        {
          
        }

        indi_model_property.s = IPS_IDLE;
        IDSetSwitch (&indi_model_property, NULL);
        break;        
      }          
    }
  
    /* Get the updated coordinates and make them available */
    
    usleep(1000000);
    
    GetTel(&telra, &teldec, pmodel);
    telha = Map12(LSTNow() - telra); 
                 
    telra2 = telra;
    teldec2 = teldec;
    Apparent(&telra2,&teldec2,-1);
    
    indi_telescope[0].value = telra;
    IDSetNumber (&indi_telescope_property, NULL);
    indi_telescope[1].value = teldec;
    IDSetNumber (&indi_telescope_property, NULL);
    
    indi_telescope_2000[0].value = telra2;
    IDSetNumber (&indi_telescope_2000_property, NULL);
    indi_telescope_2000[1].value = teldec2;
    IDSetNumber (&indi_telescope_2000_property, NULL);    

  
  }    

 
  /* Handle a goto target request */
  /* Target coordinates must have already been loaded */
  /* When go to operation is complete the poll routine will change the flags */
  
  if (!strcmp (name, indi_goto_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_goto_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to go to the target was issued */
        
        for (i = 0; i < NARRAY(indi_goto); i++)
          indi_goto[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_goto_property.s = IPS_BUSY;
        gotocmd = GoToCoords(targetra,targetdec,pmodel);
        IDSetSwitch (&indi_goto_property, 
          "Acquiring target ");
        break;
      }
      if (states[i] == ISS_OFF) 
      {
        /* A go to target request was cancelled */
        
        for (i = 0; i < NARRAY(indi_goto); i++)
          indi_goto[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_goto_property.s = IPS_OK;
        
        /* Stop all slews */

        StopSlew(NORTH);
        StopSlew(SOUTH);
        StopSlew(EAST);
        StopSlew(WEST);
        StartTrack();
        gotocmd = 0;
        IDSetSwitch (&indi_goto_property, 
          "Target acquisition stopped");
        break;
      }      
    }  
  }
       
  /* Handle a slew request by noting a change in the state property */
  /* Should also turn off a slew when the property is deselected */
  /* In guide mode this increments or decrements offsets by one pixel */
  
  if (!strcmp (name, indi_slew_state_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_slew_state_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        
        /* A request to slew or step guiding  was issued */
                
        sp->s = ISS_ON;
        indi_slew_state_property.s = IPS_OK;

        /* Get the latest telescope coordinates */
        
        GetTel(&telra, &teldec, pmodel);
        telha = Map12(LSTNow() - telra);
                      
        telra2 = telra;
        teldec2 = teldec;
        Apparent(&telra2,&teldec2,-1);
        
        indi_telescope[0].value = telra;
        IDSetNumber (&indi_telescope_property, NULL);
        indi_telescope[1].value = teldec;
        IDSetNumber (&indi_telescope_property, NULL);
        
        indi_telescope_2000[0].value = telra2;
        IDSetNumber (&indi_telescope_2000_property, NULL);
        indi_telescope_2000[1].value = teldec2;
        IDSetNumber (&indi_telescope_2000_property, NULL);    
       
        /* Which direction have we selected? */
        
        if (!strcmp (sp->name, "north"))
        {
          if (guidecmd == TRUE)
          {
            dha = 0.;
            ddec = -1.0;

          }
          else
          {
            StartSlew(NORTH);
            slewcmd = TRUE;
            teldir = NORTH; 
          }
        }
        if (!strcmp (sp->name, "south"))
        {         
          if (guidecmd == TRUE)
          {
            dha = 0.;
            ddec = 1.0;
          }        
          else
          {  
            StartSlew(SOUTH);
            slewcmd = TRUE;
            teldir = SOUTH;
          }
        }
        if (!strcmp (sp->name, "east"))
        {
          
          if (guidecmd == TRUE)
          {
            dha = 1.0;
            ddec = 0.;
          }           
          else
          {
            StartSlew(EAST);
            slewcmd = TRUE;
            teldir = EAST;
          }
        }
        if (!strcmp (sp->name, "west"))
        {
          if (guidecmd == TRUE)
          {
            dha = -1.0;
            ddec = 0.;
          }                     
          else
          {
            StartSlew(WEST);
            slewcmd = TRUE;
            teldir = WEST;
          }
        }              
           
        indi_slew_state_property.s = IPS_BUSY;
        if (guidecmd == TRUE)
        {
          IDSetSwitch (&indi_slew_state_property, 
            "Pointing step %s", sp->label);
                               
          /* Update dynamic zero point for current guide center */
          
          modelha0 = Map12(LSTNow() - guidera);
          modeldec0 = guidedec;

          /* Change declination units from pixels to degrees */
          
          ddec = arcsecperpix*ddec/3600.;
          
          /* Add the declination correction to the permanent offsetdec in degrees */
          
          offsetdec = Map180(offsetdec + ddec);
         
          /* Change hour angle units from pixels to hours */
          
          dha = arcsecperpix*dha/54000.;

          /* Add the ha correction to the permanent offsetha in hours */

          offsetha = Map12(offsetha + dha);
                                     
          break;        
        }
        else
        {
          IDSetSwitch (&indi_slew_state_property, 
            "Starting slew %s", sp->label);
          break;
        }  
      }
      if (states[i] == ISS_OFF) 
      {
        
        /* A slew or guide step request was cancelled */
        
        sp->s = ISS_OFF;
        indi_slew_state_property.s = IPS_OK;

        if (guidecmd == FALSE )
        {
          /* Which direction have we selected? */
          
          if (!strcmp (sp->name, "north"))
          {
            StopSlew(NORTH);
            StartTrack(); 
            teldir = FALSE;
            slewcmd = FALSE;          
          }
          if (!strcmp (sp->name, "south"))
          { 
            StopSlew(SOUTH);
            StartTrack();
            teldir = FALSE;
            slewcmd = FALSE;
          }
          if (!strcmp (sp->name, "east"))
          { 
            StopSlew(EAST);
            StartTrack();
            teldir = FALSE;
            slewcmd = FALSE;
          } 
          if (!strcmp (sp->name, "west"))
          { 
            StopSlew(WEST);
            StartTrack();
            teldir = FALSE;
            slewcmd = FALSE;
          }              
                    
          IDSetSwitch (&indi_slew_state_property, 
            "Stopping slew %s", sp->label);

          /* Get the latest telescope coordinates */
          
          GetTel(&telra, &teldec, pmodel);
                        
          telra2 = telra;
          teldec2 = teldec;
          Apparent(&telra2,&teldec2,-1);
          
          indi_telescope[0].value = telra;
          IDSetNumber (&indi_telescope_property, NULL);
          indi_telescope[1].value = teldec;
          IDSetNumber (&indi_telescope_property, NULL);
          
          indi_telescope_2000[0].value = telra2;
          IDSetNumber (&indi_telescope_2000_property, NULL);
          indi_telescope_2000[1].value = teldec2;
          IDSetNumber (&indi_telescope_2000_property, NULL);    

        }
        
        break;
      }            
    }
  }

  /* Handle a request to set the target to zenith, meridian, or park */

  if (!strcmp (name, indi_set_target_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_set_target_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to use a preset target was issued */
        
        for (i = 0; i < NARRAY(indi_set_target); i++)
          indi_set_target[i].s = ISS_OFF;
        sp->s = ISS_ON;

        /* Which one have we selected? */
        
        if (!strcmp (sp->name, "zenith"))
        {
          targetra = LSTNow();
          targetdec = 90.0 - SiteLatitude;
         }
        else if (!strcmp (sp->name, "meridian"))
        {
          targetra = LSTNow();
          targetdec = teldec;
        }
        else if (!strcmp (sp->name, "park"))
        {      
          targetra = Map24(LSTNow() - parkha);
          targetdec = parkdec;
        }
        else
        {      
          IDSetSwitch (&indi_set_target_property, 
            "Unknown set target request");
          break;
        }

        /* A new target has been selected in EOD coordinates */
        
        indi_target[0].value = targetra;
        indi_target[1].value = targetdec;
        
        /* Revert from apparent to epoch 2000 */
        
        targetra2 = targetra;
        targetdec2 = targetdec;
        Apparent(&targetra2,&targetdec2,-1);
        
        /* Update both EOD and epoch 2000 local data */
        
        indi_target_2000[0].value = targetra2;
        indi_target_2000[1].value = targetdec2;   
        IDSetNumber (&indi_target_property, 
          "Target RA %10.6m  Dec %10.6m", targetra, targetdec);    
        IDSetNumber (&indi_target_2000_property, 
          "Target Epoch 2000 RA %10.6m  Dec %10.6m", targetra2, targetdec2);                 
        indi_set_target_property.s = IPS_OK;
        IDSetSwitch (&indi_set_target_property, "Target set to %s", sp->label);                         
        break;
      }
    }
  }


  /* Handle a request to set the slew speed */

  if (!strcmp (name, indi_slew_speed_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_slew_speed_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to set the slew speed was issued */
        
        for (i = 0; i < NARRAY(indi_slew_speed); i++)
          indi_slew_speed[i].s = ISS_OFF;
        sp->s = ISS_ON;

        /* Which speed have we selected? */
        
        if (!strcmp (sp->name, "slew"))
        {
          telspd = SLEW;
        }
        else if (!strcmp (sp->name, "find"))
        {
          telspd = FIND;
        }
        else if (!strcmp (sp->name, "center"))
        {
          telspd = CENTER;
        }
        else if (!strcmp (sp->name, "guide"))
        {
          telspd = GUIDE;
        }      
        else
        {      
          IDSetSwitch (&indi_slew_speed_property, 
            "Unknown slew speed selected");
          break;
        }

        SetRate(telspd); 
        
                  
        /* Alert the operator for high speed slew */
        
        if (telspd > CENTER)
        {
          indi_slew_speed_property.s = IPS_ALERT;
        }
        else if (telspd == CENTER)
        {
          indi_slew_speed_property.s = IPS_BUSY;
        }
        else
        {
          indi_slew_speed_property.s = IPS_OK;
        }
        IDSetSwitch (&indi_slew_speed_property, "Slew speed %s", sp->label);                         
        break;
      }
    }
  }

  /* Handle requests to toggle tracking on or off */

  if (!strcmp (name, indi_track_state_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_track_state_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to begin sidereal tracking was issued */
        
        for (i = 0; i < NARRAY(indi_track_state); i++)
          indi_track_state[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_track_state_property.s = IPS_BUSY;
        StartTrack();
        trackcmd = TRUE;
        gotocmd = FALSE;
        guidecmd = FALSE;
        guideraflag = FALSE;
        guidedecflag = FALSE;        
        IDSetSwitch (&indi_guide_state_property, 
          "Guiding stopped");              
        IDSetSwitch (&indi_track_state_property, 
          "Tracking started");
        break;
      }
      if (states[i] == ISS_OFF) 
      {
        /* A request to stop sidereal tracking was issued */
        
        for (i = 0; i < NARRAY(indi_track_state); i++)
          indi_track_state[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_track_state_property.s = IPS_OK;
        StopTrack();
        trackcmd = FALSE;
        gotocmd = FALSE;
        guidecmd = FALSE;
        guideraflag = FALSE;
        guidedecflag = FALSE;  
        IDSetSwitch (&indi_guide_state_property, 
          "Guiding stopped");              
        IDSetSwitch (&indi_track_state_property, 
          "Tracking stopped");
        break;
      }      
    }  
  }

  /* Handle requests to toggle guiding on or off */

  if (!strcmp (name, indi_guide_state_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_guide_state_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to begin guiding was issued */
        
        for (i = 0; i < NARRAY(indi_guide_state); i++)
          indi_guide_state[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_guide_state_property.s = IPS_BUSY;
        guidera = telra;
        guidedec = teldec;
        modelha0 = (LSTNow() - guidera);
        modeldec0 = guidedec;
        guidecmd = TRUE;
        guideraflag = TRUE;
        guidedecflag = TRUE;
        GetTel(&telra, &teldec, pmodel);
                
        IDSetSwitch (&indi_guide_state_property, 
          "RA and Dec Guiding");
        break;
      }
      if (states[i] == ISS_OFF) 
      {
        /* A request to stop guiding was issued */
        
        for (i = 0; i < NARRAY(indi_guide_state); i++)
          indi_guide_state[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_guide_state_property.s = IPS_OK;
        guidecmd = FALSE;
        IDSetSwitch (&indi_guide_state_property, 
          "Guiding stopped");
        break;
      }      
    }  
  }

  /* Handle a focus request by noting a change in the state property */
  /* Should also toggle focus motion when another property is selected */

  if (!strcmp (name, indi_focus_state_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_focus_state_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to change the focus was issued */
        
        for (i = 0; i < NARRAY(indi_focus_state); i++)
          indi_focus_state[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_focus_state_property.s = IPS_OK;

        /* Which state have we selected? */
        
        if (!strcmp (sp->name, "off"))
        {
          focuscmd = FOCUSCMDOFF;
          Focus(focuscmd,focusspd);
          GetFocus(&telfocus);
          indi_focus_reading.value = telfocus;
          IDSetNumber (&indi_focus_reading_property, NULL);      
        }
        else if (!strcmp (sp->name, "in"))
        {
          focuscmd = FOCUSCMDIN;
          Focus(focuscmd,focusspd);
        }
        else if (!strcmp (sp->name, "out"))
        {
          focuscmd = FOCUSCMDOUT;
          Focus(focuscmd,focusspd);
        }              
        else
        {      
          IDSetSwitch (&indi_focus_state_property, 
            "Unknown focus request");
          break;
        } 
          
        indi_focus_state_property.s = IPS_BUSY;
        
        IDSetSwitch (&indi_focus_state_property, 
          "Focus %s", sp->label);
        break;
      }
    }
  }

  /* Handle a request to set the focus speed */

  if (!strcmp (name, indi_focus_speed_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_focus_speed_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        
        /* A request to change the focus speed setting was issued */
        
        for (i = 0; i < NARRAY(indi_focus_speed); i++)
          indi_focus_speed[i].s = ISS_OFF;
        sp->s = ISS_ON;
        
        /* Which speed have we selected? */
        
        if (!strcmp (sp->name, "1x"))
        {
          focusspd = FOCUSSPD1;
        }
        else if (!strcmp (sp->name, "2x"))
        {
          focusspd = FOCUSSPD2;
        }  
        else if (!strcmp (sp->name, "4x"))
        {
          focusspd = FOCUSSPD3;
        }
        else if (!strcmp (sp->name, "8x"))
        {
          focusspd = FOCUSSPD4;
        }
        else
        {      
          IDSetSwitch (&indi_focus_speed_property, 
            "Unknown focus speed selected");
          break;
        } 
                  
                 
        /* Alert the operator for high speed focus motion */
        
        if (focusspd >FOCUSSPD2)
        {
          indi_focus_speed_property.s = IPS_ALERT;
        }
        else if (focusspd == FOCUSSPD2)
        {
          indi_focus_speed_property.s = IPS_BUSY;
        }
        else
        {
          indi_focus_speed_property.s = IPS_OK;
        }
        IDSetSwitch (&indi_focus_speed_property, "Focus speed %s", sp->label);                         
        break;

      }
    }
  }

  /* Handle a rotator request by noting a change in the state property */
  /* Should also toggle rotator motion when another property is selected */

  if (!strcmp (name, indi_rotate_state_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_rotate_state_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to change the rotator state was issued */
        
        for (i = 0; i < NARRAY(indi_rotate_state); i++)
          indi_rotate_state[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_rotate_state_property.s = IPS_OK;

        /* Which state have we selected? */
        
        if (!strcmp (sp->name, "off"))
        {
          rotatecmd = ROTATECMDOFF;
          Rotate(rotatecmd,rotatespd);
          GetRotate(&telrotate);
          indi_rotation_reading.value = telrotate;
          IDSetNumber (&indi_rotation_reading_property, NULL); 
        }
        else if (!strcmp (sp->name, "cw"))
        {
          rotatecmd = ROTATECMDCW;
          Rotate(rotatecmd,rotatespd);
        }
        else if (!strcmp (sp->name, "ccw"))
        {
          rotatecmd = ROTATECMDCCW;
          Rotate(rotatecmd,rotatespd);
        }              
        else
        {      
          IDSetSwitch (&indi_rotate_state_property, 
            "Unknown rotator request");
          break;
        } 
          
        indi_rotate_state_property.s = IPS_BUSY;
        
        IDSetSwitch (&indi_rotate_state_property, 
          "Rotate %s", sp->label);
        break;
      }
    }
  }

  /* Handle a request to set the rotator speed */

  if (!strcmp (name, indi_rotate_speed_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_rotate_speed_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to change the rotator speed setting was issued */
       
        for (i = 0; i < NARRAY(indi_rotate_speed); i++)
          indi_rotate_speed[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_rotate_speed_property.s = IPS_OK;

        /* Which speed have we selected? */
        
        if (!strcmp (sp->name, "sidereal"))
        {
          rotatespd = ROTATESPDSIDEREAL;
        }
        else if (!strcmp (sp->name, "slow"))
        {
          rotatespd = ROTATESPDSLOW;
        }
        else if (!strcmp (sp->name, "fast"))
        {
          rotatespd = ROTATESPDFAST;
        }
        else
        {      
          IDSetSwitch (&indi_rotate_speed_property, 
            "Unknown rotator speed selected");
          break;
        } 
                  
        /* Alert the operator that the rotate selection is sensitive */
        
        if (rotatespd > ROTATESPDSLOW)
        {
          indi_rotate_speed_property.s = IPS_BUSY;
        }          
        IDSetSwitch (&indi_rotate_speed_property, 
          "Rotator speed %s", sp->label);
        break;
      }
    }
  }

  /* Handle a fan request by noting a change in the state property */
  /* Should also toggle fan state when another property is selected */

  if (!strcmp (name, indi_fan_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_fan_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to change the fan state was issued */
        
        for (i = 0; i < NARRAY(indi_fan); i++)
          indi_fan[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_fan_property.s = IPS_OK;

        /* Which state have we selected? */
        
        if (!strcmp (sp->name, "off"))
        {
          fancmd = FANCMDOFF;
          Fan(fancmd);
        }
        else if (!strcmp (sp->name, "low"))
        {
          fancmd = FANCMDLOW;
          Fan(fancmd);
        }
        else if (!strcmp (sp->name, "high"))
        {
          fancmd = FANCMDHIGH;
          Fan(fancmd);
        }              
        else
        {      
          IDSetSwitch (&indi_fan_property, 
            "Unknown fan request");
          break;
        } 
          
        indi_fan_property.s = IPS_BUSY;
        
        IDSetSwitch (&indi_fan_property, 
          "Fan %s", sp->label);
        break;
      }
    }
  }

  /* Handle a dew heater request by noting a change in the state property */
  /* Should also toggle fan state when another property is selected */

  if (!strcmp (name, indi_heater_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_heater_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        /* A request to change the dew heater state was issued */
        
        for (i = 0; i < NARRAY(indi_heater); i++)
          indi_heater[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_heater_property.s = IPS_OK;

        /* Which state have we selected? */
        
        if (!strcmp (sp->name, "off"))
        {
          heatercmd = HEATERCMDOFF;
          Heater(heatercmd);
        }
        else if (!strcmp (sp->name, "low"))
        {
          heatercmd = HEATERCMDLOW;
          Heater(heatercmd);
        }
        else if (!strcmp (sp->name, "high"))
        {
          heatercmd = HEATERCMDHIGH;
          Heater(heatercmd);
        }              
        else
        {      
          IDSetSwitch (&indi_heater_property, 
            "Unknown heater request");
          break;
        } 
          
        indi_heater_property.s = IPS_BUSY;
        
        IDSetSwitch (&indi_heater_property, 
          "Heater %s", sp->label);
        break;
      }
    }
  }


}

/* Client is sending new numbers */
/* Search number properties for a match and act accordingly */

/* In Elwood Downey's notation, the search arguments here are Nv not N */
/* In my notation the arguments are _property */

void ISNewNumber (const char *dev, const char *name,
  double *doubles, char *names[], int n)
{
  int i, nset;
  
  if (strcmp (dev, MYDEV))
  { 
      /* Ignore if this is not for us */
      return;
  }

  if (!strcmp (name, indi_target_property.name) && n > 0) 
  {    
    
    
    /* A new target has been sent in EOD coordinates */
    
    for (nset = i = 0; i < n; i++)
    {
      INumber *target_pointer = IUFindNumber(&indi_target_property, names[i]);
      if (target_pointer == &indi_target[0])
      {
        targetra = doubles[i];
        nset = nset + 1;
      }
      else if (target_pointer == &indi_target[1])
      {
        targetdec = doubles[i];
        nset = nset + 1;
      }
    }
    
    indi_target[0].value = targetra;
    indi_target[1].value = targetdec;
    
    /* Revert from apparent to epoch 2000 */
    
    targetra2 = targetra;
    targetdec2 = targetdec;
    Apparent(&targetra2,&targetdec2,-1);
    
    /* Update both EOD and epoch 2000 local data */
    
    indi_target_2000[0].value = targetra2;
    indi_target_2000[1].value = targetdec2;   
    IDSetNumber (&indi_target_property, 
      "Target RA %10.6m  Dec %10.6m", targetra, targetdec);    
    IDSetNumber (&indi_target_2000_property, 
      "Target Epoch 2000 RA %10.6m  Dec %10.6m", targetra2, targetdec2); 
  }

  if (!strcmp (name, indi_target_2000_property.name) && n > 0) 
  {    
    
    /* A new target has been sent in epoch 2000 coordinates */
    
    for (nset = i = 0; i < n; i++)
    {
      INumber *target_pointer = IUFindNumber(&indi_target_2000_property, names[i]);
      if (target_pointer == &indi_target_2000[0])
      {
        targetra2 = doubles[i];
        nset = nset + 1;
      }
      else if (target_pointer == &indi_target_2000[1])
      {
        targetdec2 = doubles[i];
        nset = nset + 1;
      }
    }

    indi_target_2000[0].value = targetra2;
    indi_target_2000[1].value = targetdec2;

    /* Find apparent coordinates EOD from  epoch 2000 */
    
    targetra = targetra2;
    targetdec = targetdec2;
    Apparent(&targetra,&targetdec,1);

    /* Update both EOD and epoch 2000 local data */
    
    indi_target[0].value = targetra;
    indi_target[1].value = targetdec;
    IDSetNumber (&indi_target_property, 
      "Target EOD RA %10.6m  Dec %10.6m", targetra, targetdec);
    IDSetNumber (&indi_target_2000_property, 
      "Target Epoch 2000 RA %10.6m  Dec %10.6m", targetra2, targetdec2);     
  }
  
  if (!strcmp (name, indi_model_parameters_property.name) && n > 0) 
  {    
        
    /* New model parameters have been sent */
    /* Assumed units are pixels/hr         */
    
    for (nset = i = 0; i < n; i++)
    {
      INumber *model_parameters_pointer = IUFindNumber(&indi_model_parameters_property, names[i]);
      if (model_parameters_pointer == &indi_model_parameters[0])
      {
        modelha1 = doubles[i];    
        modelha0 = (LSTNow() - telra);
        modeldec0 = teldec;

        nset = nset + 1;
      }
      else if (model_parameters_pointer == &indi_model_parameters[1])
      {
        modeldec1 = doubles[i];
        modeldec0 = teldec;
        modelha0 = (LSTNow() - telra);
        
        nset = nset + 1;
      }
    }
    
    indi_model_parameters[0].value = modelha1;
    indi_model_parameters[1].value = modeldec1;
        
    /* Update local data */
    
    IDSetNumber (&indi_model_parameters_property, 
      "Rates HA %8.3f  Dec %8.3f", modelha1, modeldec1);    

  }

}

/* Telescope function called during the polling cycle                */
/* Restarts the timer to call itself again                           */

static void telescope (void *p)
{
  static int tcount, tcount2;
  
  /* Local very high priority operations would go here */
  
  if ( guidecmd == 1 )
  {
    CenterGuide(guidera, guidedec, guideraflag, guidedecflag, pmodel);
  }   
  
  /* Otherwise poll telescope every 4 seconds for a 250 ms INDI polling cycle */
  
  if ((tcount < 0) || (tcount > 16))
  {
    tcount = 1;  

    if ( gotocmd == 1 )
    {
      
      /* A slew was started and is not yet reported complete */
      
      if (CheckGoTo(targetra,targetdec,pmodel) >= 1)
      {
        /* Slew has completed */
        
        gotocmd = 0;
        indi_goto[0].s = ISS_OFF; 
        indi_goto_property.s = IPS_OK;      
        IDSetSwitch (&indi_goto_property, "Goto complete");
        StartTrack();
        usleep(1000000);
        GetTel(&telra, &teldec, pmodel);
        write_coords(telra,teldec);
      }
      else
      {
        /* Get the latest telescope coordinates */
      
        GetTel(&telra, &teldec, pmodel);
      }
                    
      telra2 = telra;
      teldec2 = teldec;
      Apparent(&telra2,&teldec2,-1);
      
      indi_telescope[0].value = telra;
      indi_telescope[1].value = teldec;
      IDSetNumber (&indi_telescope_property, NULL);
      
      indi_telescope_2000[0].value = telra2;
      indi_telescope_2000[1].value = teldec2;
      IDSetNumber (&indi_telescope_2000_property, NULL);    
    
    }
   
  }
  tcount++;

  /* Local lower-priority operations would go here */
     
  if ((tcount2 < 0) || (tcount2 > 80))
  {
    tcount2 = 1;
      
    GetFocus(&telfocus);
    indi_focus_reading.value = telfocus;
    IDSetNumber (&indi_focus_reading_property, NULL);      
    
    GetTemperature(&teltemperature);
    indi_temperature_reading.value = teltemperature;
    IDSetNumber (&indi_temperature_reading_property, NULL); 

    GetRotate(&telrotate);
    indi_rotation_reading.value = telrotate;
    IDSetNumber (&indi_rotation_reading_property, NULL); 
                
  }
  tcount2++;
  
  
  /* Start the timer again */
  IEAddTimer (POLLMS, telescope, NULL);
}

/* Read and parse the initial configuration file */
/* Will override defaults for                    */
/*                                               */
/*   polaraz                                     */
/*   polaralt                                    */
/*   offsetha                                    */
/*   offsetdec                                   */
/*   latitude                                    */
/*   longitude                                   */
/*   altitude                                    */
/*   pressure                                    */
/*   temperature                                 */
/*   parkha                                      */
/*   parkdec                                     */
/*   telserial                                   */

void read_config(void)
{
  char configstr[121];
  char *configptr = configstr;
  int n;
      
  configfile = (char *) malloc (MAXPATHLEN);
  strcpy(configfile,CONFIGFILE);  
  fp_config = fopen(configfile, "r");
  
  if ( fp_config == NULL )
  {
    fprintf(stderr,"New telescope configuration not found.\n");
    fprintf(stderr,"Using default telescope parameters.\n");
    return;
  }
  else
  {
    fprintf(stderr,"Telescope and site parameters redefined.\n");
  }
  
  while ( configstr == fgets(configstr,80,fp_config) )
  {
    configptr = strstr(configstr,"tel.mount");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%d",&telmount);
        fprintf(stderr,"Telescope mounting type: %d\n",telmount);
      }
    }

    configptr = strstr(configstr,"tel.serial");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(telserial,configptr,30);
        n = strlen(telserial);
        if (n>0)
        {
          telserial[n-1]='\0';
        }  
        fprintf(stderr,"Telescope serial port set to: %s\n",telserial);
      }
    }
    
    configptr = strstr(configstr,"tel.homeha");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&homeha);
        fprintf(stderr,"Home HA: %lf\n",homeha);
        homenow = TRUE;
      }  
    }

    configptr = strstr(configstr,"tel.homedec");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&homedec);
        fprintf(stderr,"Home Dec: %lf\n",homedec);
        homenow = TRUE;
      }  
    } 

    configptr = strstr(configstr,"tel.parkha");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&parkha);
        fprintf(stderr,"Park HA: %lf\n",parkha);
      }  
    }

    configptr = strstr(configstr,"tel.parkdec");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&parkdec);
        fprintf(stderr,"Park Dec: %lf\n",parkdec);
      }  
    }
 
    configptr = strstr(configstr,"tel.polaraz");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&polaraz);
        fprintf(stderr,"Polar azimuth: %lf\n",polaraz);
      }  
    }
    
    configptr = strstr(configstr,"tel.polaralt");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&polaralt);      
        fprintf(stderr,"Polar altitude: %lf\n",polaralt);
      }  
    } 

    configptr = strstr(configstr,"tel.offsetha");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&offsetha);
        offsetha_default = offsetha;
        fprintf(stderr,"Offset in hour angle: %lf\n",offsetha);
      }
    }
    
    configptr = strstr(configstr,"tel.offsetdec");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&offsetdec);
        offsetdec_default = offsetdec;       
        fprintf(stderr,"Offset in declination: %lf\n",offsetdec);
      }
    } 
    
    configptr = strstr(configstr,"tel.modelha1");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&modelha1_default);
        fprintf(stderr,"Default model parameter ha1: %lf\n",modelha1_default);
        modelha0 = (LSTNow() - telra);
        modelha1 = modelha1_default;
      }
    }
        
    configptr = strstr(configstr,"tel.modeldec1");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&modeldec1_default);
        fprintf(stderr,"Default model parameter dec1: %lf\n",modeldec1_default);
        modeldec0 = teldec;
        modelha0 = (LSTNow() - telra);
        modeldec1 = modeldec1_default;      
      }
    } 

    configptr = strstr(configstr,"site.longitude");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&SiteLongitude);
        fprintf(stderr,"Site longitude: %lf\n",SiteLongitude);
      }
    } 
    
    configptr = strstr(configstr,"site.latitude");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&SiteLatitude);
        fprintf(stderr,"Site latitude: %lf\n",SiteLatitude);
      }
    }

    configptr = strstr(configstr,"site.altitude");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&SiteAltitude);
        fprintf(stderr,"Site altitude: %lf\n",SiteAltitude);
      }
    } 
    
    configptr = strstr(configstr,"site.pressure");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&SitePressure);
        fprintf(stderr,"Site pressure: %lf\n",SitePressure);
      }
    } 
    
    configptr = strstr(configstr,"site.temperature");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&SiteTemperature);
        fprintf(stderr,"Site temperature: %lf\n",SiteTemperature);
      }
    } 

    configptr = strstr(configstr,"ccd.arcsecperpix");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%lf",&arcsecperpix);
        fprintf(stderr,"CCD image scale arcsec/pixel: %lf\n",arcsecperpix);
      }
    }
  
  
  }  
  fclose(fp_config);

}

/* Write the ra and dec to a status file */

void write_coords (double ra, double dec)
{
  FILE* outfile;
  outfile = fopen("/usr/local/observatory/status/telcoords","w");
  fprintf(outfile, "%lf %lf\n", ra, dec);      
  fclose(outfile);
}

/* Update reference to current target                                      */

/* This is actually more complex than a simple offset correction if        */
/* a pointing model is in place.  In that case, the offset should be       */
/* to the base ra and dec reported by the telescope, taken such that       */
/* the actual ra and dec with offset and pointing correction will          */
/* agree with the reference target.  It will have to iterate to achieve    */
/* this effect.  The simple difference here from the reported coordinates  */
/* with other pointing model corrections still in place will impact        */
/* the effect of the pointing model.                                       */

void target_telescope_reference(void)          
{    

  double tmpoffsetha, tmpoffsetdec;

  /* Turn off reference correction for a moment by setting offsets to zero */
  
  tmpoffsetha = offsetha;
  tmpoffsetdec = offsetdec;
  offsetha = 0.;
  offsetdec = 0.;
  
  /* Get the coordinates with other pointing corrections in place  */

  GetTel(&telra, &teldec, pmodel);
  
  telha = Map12(LSTNow() - telra);
  
  /* Trap the very confusing errors that would happen in an offset */
  /* assigned across the poles.                                    */

  if ( fabs(teldec)> 85. )
  {
    offsetha = tmpoffsetha;
    offsetdec = tmpoffsetdec; 
    fprintf(stderr, "Within 5 degrees of the pole.\n");
    fprintf(stderr, "Original offsets remain in use.\n");
    return;
  }

  if (fabs(targetdec)> 85. )
  {
    offsetha = tmpoffsetha;
    offsetdec = tmpoffsetdec; 
    fprintf(stderr, "Within 5 degrees of the pole original offsets remain in use.\n");
    return;
  } 
  
  /* Calculate new additive offsets */
    
  offsetha = Map12(telra - targetra);
  offsetdec = targetdec - teldec;
  
  
  /* Check that the offsets are not too large */
  
  if (fabs(offsetdec) >= 15.0)
  {
    offsetdec = tmpoffsetdec;
    offsetha = tmpoffsetha;
    fprintf(stderr, "Declination offset greater than 15 degrees.\n");
    fprintf(stderr, "Original offsets remain in use.\n");
    return;
  }
  
  if (fabs(offsetha) >= 1.0)
  {
    offsetdec = tmpoffsetdec;
    offsetha = tmpoffsetha;
    fprintf(stderr, "Hour angle offset greater than 1 hour.\n");
    fprintf(stderr, "Original offsets remain in use.\n");
    return;
  } 

  fprintf(stderr,"New offsets:  %lf  %lf\n",offsetha,offsetdec); 
}


/* Clear telescope offsets */

void clear_telescope_reference(void)
{
  offsetha = 0.;
  offsetdec = 0.;

  fprintf(stderr,"New offsets:  %lf  %lf\n",offsetha,offsetdec); 
  return;
}

/* Write telescope offsets to a system file */

void save_telescope_reference(void)
{  
  FILE* outfile;
  outfile = fopen("/usr/local/observatory/status/teloffsets","w");
  if ( outfile == NULL )
  {
    fprintf(stderr,"Cannot update teloffsets file\n");
    return;
  }

  fprintf(stderr, "Saved offsets: %lf %lf\n", offsetha, offsetdec);      
  fprintf(outfile, "%lf %lf\n", offsetha, offsetdec);      

  fclose(outfile);
}
 
/* Recall telescope offsets from a system file */

void recall_telescope_reference()
{
  FILE* fp_offsets;
  double tmpoffsetha, tmpoffsetdec;
  int recall_flag;
  fp_offsets = fopen("/usr/local/observatory/status/teloffsets","r");
  if ( fp_offsets == NULL )
  {
    fprintf(stderr,"Cannot open a teloffsets file\n");
    return;
  }
  recall_flag = fscanf(fp_offsets, "%lf %lf", &tmpoffsetha, &tmpoffsetdec);
  if (recall_flag == 2)
  {
    offsetha = tmpoffsetha;
    offsetdec = tmpoffsetdec;
    fprintf(stderr, "%lf %lf\n", offsetha, offsetdec);      
  }
  fclose(fp_offsets);
}
/* Read wcs coordinates and compute new offsets */
  
void wcs_telescope_reference(void)
{
  fprintf(stderr, "Reference from a WCS header is not yet implemented.\n");
}
  
/* Use default offsets */
  
void default_telescope_reference(void)
{
  offsetha = offsetha_default;
  offsetdec = offsetdec_default;
  
  fprintf(stderr, "Recalled default offsets: %lf %lf\n", offsetha, offsetdec);      
}

/* Write telescope model  to a system file */

void save_telescope_model()
{  
  FILE* outfile;
  outfile = fopen("/usr/local/observatory/status/telmodel","w");
  if ( outfile == NULL )
  {
    fprintf(stderr,"Cannot update telmodel file\n");
    return;
  }

  fprintf(outfile, "%lf %lf\n", modelha1, modeldec1);      
  fclose(outfile);
}
 
/* Recall telescope model from a system file */
/* Input units are arcseconds per pixel */

void recall_telescope_model()
{
  FILE* infile;
  int recall_flag;
  infile = fopen("/usr/local/observatory/status/telmodel","r");
  if ( infile == NULL )
  {
    fprintf(stderr,"Cannot open a telmodel file\n");
    return;
  }
  recall_flag = fscanf(infile, "%lf %lf", &modelha1, &modeldec1);
  if (recall_flag == 2)
  {
    fprintf(stderr, "Recalled mount model parameters %lf %lf\n", modelha1, modeldec1);    
    modelha0 = (LSTNow() - telra);    
    modeldec0 = teldec;
  }
  fclose(infile);
}

  
/* Use default model */
  
void default_telescope_model(void)
{
  modelha0  = Map12(LSTNow() - telra);
  modelha1  = modelha1_default;
  modeldec0 = teldec;
  modeldec1 = modeldec1_default;
  
  fprintf(stderr, "Recalled default model\n");      
}

/* Clear telescope model */

void clear_telescope_model(void)
{
  modelha0  = 0.;
  modelha1  = 0.;
  modeldec0 = 0.;
  modeldec1 = 0.;
  
  fprintf(stderr,"Model parameters cleared\n");
  return;
}

