/* ----------------------------------------------------------------------- */
/* -                 Astronomical CCD Camera Control                     - */
/* -                      Apogee Camera Protocol                         - */
/* ----------------------------------------------------------------------- */
/*                                                                         */
/* Copyright 2009-2011 John Kielkopf                                       */
/* kielkopf@louisville.edu                                                 */
/*                                                                         */
/* This file is part of XmCCD.                                             */
/*                                                                         */
/* XmCCD is free software: you can redistribute it and/or modify           */
/* it under the terms of the GNU General Public License as published by    */
/* the Free Software Foundation, either version 3 of the License, or       */
/* (at your option) any later version.                                     */
/*                                                                         */
/* XmCCD is distributed in the hope that it will be useful,                */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of          */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           */
/* GNU General Public License for more details.                            */   
/*                                                                         */
/* Date: August 6, 2011                                                    */
/*                                                                         */
/* Version 3.1.3                                                           */
/*                                                                         */
/* For use with XmCCD and the INDI driver                                  */
/*                                                                         */
/* ----------------------------------------------------------------------- */


/* System */

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

/* XmCCD */

#include "protocol.h"

/* Include the cfitsio library */

#include <fitsio.h>


/* The follow functions are exported and availble for use in other programs   */
/* As much as possible these prototypes will be stable in subsequent versions */

/* Camera system operations */
/* Return (1) on success, (0) on failure */
/* May send messages to stderr */

int OpenCamera(int device);     

int CloseCamera(int device);    

int GetCameraInfo(int *type, int *wheel, int *filter_max, 
 int *image_width, int *image_height, double *image_gain,
 int *track_width, int *track_height, double *track_gain);
 
int GetCameraFilter(char *filter_name, int *filter);

int GetCameraStatus(int *image_status, int *track_status);
int GetCameraTemperature(double *power, 
  double *temperature, double *setpoint);
int SetCameraTemperature(int state, double temperature);
int SetCameraFilter(int filter);
int SetCameraFilterName(int flag, int new_filter_number, 
  char *new_filter_name);
int SetCameraAOTipTilt(int ao_x, int ao_y);
int ActivateCameraRelay(int relay, double ontime);
int SetCameraGuideParameters(int threshold, 
  double x2trackx, double y2trackx, double x2tracky, double y2tracky,
  double x2aox, double y2aox, double x2aoy, double y2aoy);
             
/* Image capture functions for external use                    */
/* Returns 1 on success and 0 on failure                       */
/* May send messages to stderr                                 */
/* Images are always saved as a file on disk                   */
/* Most recent image is always available in allocated storage  */

/* Reads these parameters as needed:                           */
/*   phase     0 start exposure                                */
/*             1 readout if ready                              */
/*             2 interrupt and reset                           */
/*   frame     0 dark                                          */
/*             1 light                                         */
/*             2 bias                                          */
/*             3 flat                                          */
/*   exposure  time in seconds                                 */
/*   data      malloc'd image storage                          */
/*   subarea   TRUE or FALSE for subarea extraction            */
/*   x         initial x for subarea                           */
/*   y         initial y for subarea                           */
/*   width     width of subarea                                */
/*   height    height of subarea                               */
/*   guide     0 unguided                                      */
/*             1 tracking                                      */
/*             2 optical                                       */
/*   phase     0 exposure not started                          */
/*             1 exposure in progress                          */
/*             2 exposure complete                             */


/* Usage:                                                      */
/*   Make the first call with phase = 0 to start an exposure   */
/*   Make repeated calls with phase = 1 to ask for a readout   */
/*   User may delay asking for phase 1 or poll                 */
/*   Make one call with phase = 2 to interrupt an exposure     */
/*   Return value of phase indicates whether image was read    */
/*   Function will set phase = 2 when the exposure is done     */
/*   A minimum of two calls are required to capture an image   */


int CaptureImage(int *phase,  unsigned short *data,
  int frame, double exposure, int subarea, 
  int x, int y, int width, int height);
  
int CaptureTrack(int *phase, unsigned short *data,
  int frame, double exposure, int subarea, 
  int x, int y, int width, int height);
  
int CaptureGuide(int *phase,
  unsigned short *image_data, 
  unsigned short *track_data,
  double exposure_i, int subarea_i, 
  int x_i, int y_i, int width_i, int height_i,
  double exposure_t, int subarea_t, 
  int x_t, int y_t, int width_t, int height_t,
  int guide, int guide_flag, int guide_log_flag);

int CaptureEnd(int *phase);

int GuideImage(unsigned short *data,
  int width, int height,
  int guide, int guide_flag, int guide_log_flag);  

/* Save an image as in FITS file format with header information */
  
void WriteFits(char *filename, int w, int h, unsigned short *data, 
  double obs_duration, 
  char*  obs_type, 
  char*  obs_date, 
  char*  obs_target,
  char*  obs_instrument, 
  char*  obs_filter, 
  char*  obs_telescope);  

/* Internal fits error handler used in WriteFits */

void show_cfitsio_error(int status);

/* Internal guiding utilities */

/* Operation */
/*   find_guide_star() scans a track subarea image for the brightest star    */
/*     and calculates its position and returns the coordinates               */
/*                                                                           */
/*   feedback_tracking() and feedback_ao() control pointing                  */
/*     by using drive or ao system to hold star in place                     */
                    
void feedback_tracking(double x_err, double y_err);
           
void feedback_ao(double x_err, double y_err);

void find_guide_star(double *data, int w, int h, double *x_s, double *y_s);    
    
/* A fixed ip address for ethernet cameras */

unsigned long camera_ip_address = CCD_ETH; 

/* CCD parameters saved for internal use */
/* Modify with caution */

static int ccd_type;
static int ccd_device;
static int ccd_image_width;
static int ccd_image_height;
static int ccd_track_width;
static int ccd_track_height;
static double ccd_image_gain;   
static double ccd_track_gain;
static int ccd_phase;
static int ccd_image_status;
static int ccd_track_status;
int ccd_filter_wheel = FILTER_WHEEL;
int ccd_filter = FILTER_START;
int ccd_filter_max = FILTER_MAX;
int ccd_tracking = SELF_TRACKING;
int ccd_aux_tracking = AUX_TRACKING;
double ccd_cooler_power = 0.;
int ccd_cooler_state = 0;
double ccd_temperature = 20.;
double ccd_setpoint = 20.;
static unsigned int ccd_id=1;
 

/* Default labels for filters in the wheel                       */
/* The names can be changed by calling SetCameraFilterName       */
/* The indiserver or xmtel preferences will redefine these names */

char ccd_filter_name[][24] = {
  " (1)              ",
  " (2)              ",
  " (3)              ",
  " (4)              ",
  " (5)              ",
  " (6)              ",  
  " (7)              ",  
  " (8)              ",
  " (9)              ", 
  "(10)              "
 }; 
 
 
/* Array of default labels that simply number the filters */ 
 
char default_filter_name[][24] = {
  " (1)              ",
  " (2)              ",
  " (3)              ",
  " (4)              ",
  " (5)              ",
  " (6)              ",  
  " (7)              ",  
  " (8)              ",
  " (9)              ", 
  "(10)              "
 };     
 
 
/* Initial values of the guide parameters are set in the header file      */
/* Change these by calling SetCameraGuideParameters                       */
/* Values of  x and y refer to coordinates in the image                   */
/* Signed values of ao_x, ao_y, track_x and track_y r to guide device     */
/* Units of ao_x are step count for rotation of the refractor plate       */
/* Units of track_x are time in centiseconds for holding the relay closed */


static int guide_threshold = GUIDE_THRESHOLD;
static double guide_x_to_ao_x = GUIDE_X2AOX;
static double guide_y_to_ao_x = GUIDE_Y2AOX;
static double guide_x_to_ao_y = GUIDE_X2AOY;
static double guide_y_to_ao_y = GUIDE_Y2AOY;
static double guide_x_to_track_x = GUIDE_X2TRACKX;
static double guide_y_to_track_x = GUIDE_Y2TRACKX;
static double guide_x_to_track_y = GUIDE_X2TRACKY;
static double guide_y_to_track_y = GUIDE_Y2TRACKY;



/* Open connection and intialize camera */
/* Returns TRUE on success */

int OpenCamera(int device)
{  
  int status;   
  int simulation = FALSE;
  
  if (device == USB)
  { 
    ccd_device = USB;
  }
  else if  (device == ETH)
  {
    ccd_device = ETH;
  }
  else if (device == PIO)
  {
    ccd_device = PIO;
  }
  else
  {
    if ( !simulation )
    {
      fprintf(stderr,"Only USB, ETH and PIO devices are supported");
      return(0);
    }      
  }
       
  /* Apogee support only for USB at this time */
  
  if (ccd_device == USB)
  {
    status = apogee_open(ccd_id);
  }
  else
  {
    status = -1;
  }    
  if ( status != 0 )
  {
    return(0);
  }   
  return(1);
}


/* Close the camera and driver */
/* Returns TRUE on success and FALSE on failure */
/* This should be the last action on shutdown */

int CloseCamera(int device)
{

  
  /* Apogee driver library does not include a close function */


  return(1);
}


/* Provide essential information about the camera system */
/* Returns TRUE on success and FALSE otherwise */

int GetCameraInfo(int *type, int *wheel, int *filter_max, 
 int *image_width, int *image_height, double *image_gain,
 int *track_width, int *track_height, double *track_gain)
{
  char *sensor; 
  char *camera;
  int roiw, roih;
  int binw, binh;
  int osw, osh;
  double mintemp; 
  
  /* Assume that no camera is present until proven otherwise */
  
  ccd_type = NO_CAMERA;

  /* Get camera information */
  
  apogee_get_info(&sensor, &camera);
    
  ccd_image_width  = 0;
  ccd_image_height = 0;
  ccd_image_gain   = 0.;    
  ccd_image_gain   = 1.;  
  ccd_track_width  = 0;
  ccd_track_height = 0;
  ccd_track_gain   = 0.;
      
  apogee_get_values(&roiw, &roih, &osw, &osh, 
    &binw, &binh, &mintemp);     
   
  ccd_image_width = roiw;
  ccd_image_height = roih;
  ccd_type = APOGEE;
  
  *type = ccd_type;
  *image_width = ccd_image_width;
  *image_height = ccd_image_height;
  *image_gain = ccd_image_gain;
  *track_width = ccd_track_width;
  *track_height = ccd_track_height;
  *track_gain = ccd_track_gain;
  *wheel = ccd_filter_wheel;
  *filter_max = ccd_filter_max;
  
  if ( ccd_type == NO_CAMERA )
  {
    fprintf(stderr,"No camera found.\n");
    return(0);
  }
  else if (ccd_type != APOGEE)
  { 
    fprintf(stderr,"No connection to a Apogee CCD camera.\n");
    return(0);
  }
  else
  {
    fprintf(stderr,"Apogee CCD camera found.\n");
  }          
  return(1);
}

/* Inquire about the exposure status of the camera */
/* Sets internal ccd_track_status and ccd_image_status */
/* Returns these values to the calling routine */

int GetCameraStatus(int *image_status, int *track_status)
{
  int status;
      
  /* Assign status to standard values */
  /* Options are                      */
  /*   IDLE                           */
  /*   INTEGRATING                    */
  /*   COMPLETE                       */
  
  /* Assign track status as IDLE  */
  /* Apogee reports either IDLE or COMPLETE with image ready to read */
   
  ccd_track_status = IDLE;
  status = apogee_get_status();
  if (status == 2)
  {
    ccd_image_status = COMPLETE;
  }
  else
  {
    ccd_image_status = IDLE;
  }    

  /* Return status to calling routine  */
  
  *image_status = ccd_image_status;
  *track_status = ccd_track_status;
  return(1);
}


/* Inquire about the CCD temperature, setpoint, and cooler power level */

int GetCameraTemperature(double *power, double *temperature, double *setpoint)
{
  
  int status;
  
  status = apogee_get_cooler_temperature(&ccd_temperature);
  
  if ( status == 0 )
  {
    ccd_cooler_power = 0.;
  }
  else if ( status == 1)
  {
    ccd_cooler_power = 1.;
  }
  else if ( status == 2 )
  {  
    ccd_cooler_power = 0.5;
  }
  else
  {
    ccd_cooler_power = 0.;
  }      

  /* Return data to calling routine */

  *power = ccd_cooler_power;
  *temperature = ccd_temperature;
  *setpoint = ccd_setpoint;

  return(1);
}


/* CCD cooler control */
/* Includes ambient workaround for cooler off bug */
/* Fan will stay high */

int SetCameraTemperature(int state, double temperature)
{
  int fan = 3;
  double ambient = 20.;
    
  if( state == TRUE )
  {
    /* Fan always on high */
    apogee_set_fan(fan);

    /* Cooler on at requested temperature */
    ccd_cooler_power = 1.;
    ccd_cooler_state = 1;
    apogee_set_cooler_state(ccd_cooler_state);
    ccd_setpoint = temperature;
    apogee_set_cooler_temperature(ccd_setpoint);
  }
  else if ( state == FALSE )
  {
    /* Fan left on high for warmup */
    apogee_set_fan(fan);

    /* Cooler flags off at ambient */
    ccd_cooler_power = 0.;
    ccd_cooler_state = 0;
    
    /****************************************************************/
    /*                                                              */
    /* Apogee 16M cooler will not turn off but stops regulating     */
    /*                                                              */
    /* Workaround is to regulate at ambient temperature             */
    /* Set flags to indicate cooler is disabled and power is off    */
    /* Enable setpoint at typical ambient temperature               */
    /* Cooler will continue to regulate until camera is reset       */
    /*                                                              */
    /* apogee_set_cooler_state(ccd_cooler_state);                   */
    /*                                                              */
    /****************************************************************/
    
    ccd_setpoint = ambient;
    apogee_set_cooler_temperature(ambient);
  }
  else
  {
    fprintf(stderr,"Unknown cooler state request\n");
  }      
  
  return(1);
}

/* Provide filter information for a filter number */

int GetCameraFilter(char filter_name[24], int *filter)
{
  if (ccd_filter_wheel == NO_FILTER_WHEEL)
  {
    strcpy(filter_name,"Open");    
    return(1);
  }
  if ( ( *filter >=1 ) && ( *filter <= ccd_filter_max ) )
  { 
    strcpy(filter_name,ccd_filter_name[ccd_filter-1]);
    return(1);
  }
  return(0);
}

/* Replace a filter name with a new one for positive flag    */
/* Restore defaults for zero or negative flag            */
/* Return 1: success                                         */ 
/* Return 0: if filter wheel not present no action taken     */
/* Return 0: out of bounds filter_number no action taken     */
/* Use default filter name if incoming name is too long      */

int SetCameraFilterName(int flag, 
  int new_filter_number, char *new_filter_name)
{
  int i;
  
  if ( ccd_filter_wheel == NO_FILTER_WHEEL )
  {
    return(0);
  }
    
  if ( flag > 0 )
  {
    
    /* Save new filter name */
            
    i = new_filter_number - 1;
    if ( (i >= 0) & (i < ccd_filter_max) )
    { 
      if ( strlen(new_filter_name) < 24 )
      {
        strcpy(ccd_filter_name[i],new_filter_name);
      }
      else
      {
        strcpy(ccd_filter_name[i],default_filter_name[i]);
      }
    }
    
  }
  else
  {
    
    /* Restore defaults */
    
    ccd_filter_max = FILTER_MAX;
  
    for (i=0; i<10; i++)
    {
      strcpy(ccd_filter_name[i],default_filter_name[i]);  
    }
  }
  return(1);
}

/* Request filter change */
/* Filter numbers are from 1 to FILTER_MAX                        */
/* Sends request to system script for generic filterwheel support */

int SetCameraFilter(int filter)
{
  char cmdstr[256];
  /* Respond to request if the filter wheel is available */
  
  if (ccd_filter_wheel == EXT_FILTER_WHEEL)
  {

    /* Correct errors in filter number request */
  
    if (filter < 1 )
    {
      filter = 1;
    }
    if (filter > ccd_filter_max )
    {
      filter = ccd_filter_max;
    }  
  
    /* Execute external set_camera_filter */
  
    sprintf(cmdstr,"set_camera_filter %d",filter);
    system(cmdstr);

    /* Pause to allow time for wheel to set */
  
    usleep(5000000);
  
    /* Keep a record of the last filter selection */

    ccd_filter = filter;
    return(1);
  }
  else if ( ccd_filter_wheel == NO_FILTER_WHEEL )
  { 
    return(1);
  }
  return(0);  
}


/* Set the guiding parameters */

int SetCameraGuideParameters(int threshold, 
  double x2trackx, double y2trackx, double x2tracky, double y2tracky,
  double x2aox, double y2aox, double x2aoy, double y2aoy)
{
  /* Save these values for use by the guiding routines */
  
  guide_threshold = threshold;
  guide_x_to_ao_x =    x2aox;
  guide_y_to_ao_x =    y2aox;
  guide_x_to_ao_y =    x2aoy;
  guide_y_to_ao_y =    y2aoy;
  guide_x_to_track_x = x2trackx;
  guide_y_to_track_x = y2trackx;
  guide_x_to_track_y = x2tracky;
  guide_y_to_track_y = y2tracky;

  return(1);
}


/* Set the AO tip and tilt with limit protection */

int SetCameraAOTipTilt(int ao_x, int ao_y)
{
  int status = 0;
  
  if (ao_x < 0 )   ao_x = 0;
  if (ao_x > 4095) ao_x = 4095; 
  if (ao_y < 0 )   ao_y = 0;
  if (ao_y > 4095) ao_y = 4095;   
  
  /* Send command to set tip and tilt */
  
  status = apogee_tiptilt(ao_x, ao_y);

  if ( status == 0) 
  {
    return(0);
  } 
  
  return(1);

}


/* Activate one tracking relay at a time */
/* May be generalized for both x and y simultaneously */
/* Relay ID is the bit pattern 0001 0010 0100 1000 */
/* Time ontime in seconds up to maximum of 30 */

int ActivateCameraRelay(int relay, double ontime)
{
  int status = 0;

  status = apogee_guide(relay, ontime);

  if ( status == 0 ) 
  {
    fprintf(stderr,"Camera relay error\n");
    return(0);    
  } 
  return(1);
}


/* Acquire an image frame */

int CaptureImage(int *phase,  unsigned short *data,
  int frame, double exposure, int subarea, 
  int x, int y, int width, int height)
{ 
  int status;
    
  ccd_phase = *phase;
  
  if ( ccd_phase == 2 )
  {
    
    /* End an exposure without readout */
    
    status = apogee_stop_exposure();
    ccd_phase = 0;
    *phase = ccd_phase;
    if ( status == FALSE )  
    {
      fprintf(stderr,"Error ending image exposure");
      return(0);
    }
    return(1);
  }
    
  if ( ccd_phase == 0 )
  { 
  
    /* Test for subarea request */
    /* If not subarea, then default to driver's values for ccd dimensions */
    /* If subarea, then check validity and clamp to allowed bounds */

    if (subarea == FALSE)
    {
       x = 0;
       y = 0;
       width = ccd_image_width;
       height = ccd_image_height;
    }  
    else 
    {
      if (x < 0)
      {
        x = 0;
      }
      if (y < 0)
      {
        y = 0;
      }
      if (x > ccd_image_width  )
      {
        x = ccd_image_width;
      }
      if (y > ccd_image_height )
      {
        y = ccd_image_height;
      }
      if (width > ccd_image_width)
      {
        width = ccd_image_width;
      }
      if (height > ccd_image_height)
      {
        height = ccd_image_height;
      }
    } 
    
    /* Send start request to the camera for imaging exposure  */
      
    status = TRUE;
    
    if ( (frame == LIGHT) | (frame == FLAT) )
    {  
      /* Shutter open */
      status = apogee_expose(exposure, x, y, height, width, TRUE);
    }
    else if ( (frame == DARK) | (frame == BIAS) )
    {
      /* Shutter closed */
      status = apogee_expose(exposure, x, y, height, width, FALSE);
    }
    else
    {
      fprintf(stderr,"Unknown frame type requested in CaptureImage");
      return(0);
    }

    /* Did this work? */

    if ( status == FALSE ) 
    {
      fprintf(stderr,"Request to start camera exposure ignored");
      return(0);
    }
   
    /* Return indicating succesful start and exposure in progress */
    
    ccd_phase = 1;
    *phase = ccd_phase;
    return(1);
  }   
    
  if ( ccd_phase != 1 )
  {
    /* We have already handled all allowed values except 1 */
    /* Reset phase to 0.  This permits recursive calls */
    
    ccd_phase = 0;
    *phase = ccd_phase;
    return(0) ;
  }
  
  /* Are we done yet? */
  /* If so, read it, if not reset phase and return */

  GetCameraStatus(&ccd_image_status, &ccd_track_status);
  
  if ( ccd_image_status != COMPLETE )
  {
    /* The exposure is still underway */
    ccd_phase = 1;
    *phase = ccd_phase;
    return(1);
  }  
           
  /* Flush the buffers */
  /* This probably isn't necessary.  It's a carryover from earlier versions. */
        
  fflush(stdout);
  fflush(stderr);
  sync();
      
  /* Read it using subarea information sent with exposure request */
  
  status = apogee_get_image(data);

  if ( status == FALSE ) 
  {
    fprintf(stderr,"Unable to read image data from camera");
    ccd_phase = 0;
    *phase = ccd_phase;    
    return(0);
  } 
      
  /* Successful readout */
  /* Indicate that a new image is available */
  
  ccd_phase = 2;
  *phase = ccd_phase;  
  return(1);
}
  

/* Acquire a track frame */

int CaptureTrack(int *phase,  unsigned short *data,
  int frame, double exposure, int subarea, 
  int x, int y, int width, int height)
{ 
  if ( SELF_TRACKING )
  {
    fprintf(stderr,"Self-tracking is not enabled on this camera\n");
  }
  else
  {
    fprintf(stderr,"Tracking CCD not available\n");
  }  
  ccd_phase = 0;
  *phase = ccd_phase;
  return(0);
}


/* Use image data to find and apply guiding feedback     */
/* Functions:                                            */
/*   Find a single guide star within the image subarea   */
/*   Center star in the subarea                          */
/*   Write residual error to a log file                  */

int GuideImage(unsigned short *data,
  int width, int height,
  int guide, int guide_flag, int guide_log_flag)
{ 
  int i,j,k;
  unsigned short pixdata;
  double dataval;
  double bkgval;
  double anbkg;
  int nbkg;
  int guide_n;
  double x_err, y_err;
  double x_0, y_0;
  double *guide_star;
  FILE *fp_log; 
  
  guide_n = width*height;
  guide_star = (double *) malloc((guide_n)*sizeof(double));
  
  /* Average perimeter for a background estimate */
  /* Corners are weighted by 2 */
  
  k = 0;
  nbkg = 0;
  bkgval = 0.;
  for (j = 0; j < height; j++)
  {
     for (i = 0; i < width; i++)
    {
      pixdata = *(data + k);
      dataval = (double) pixdata;
      if ( (j == 0) || (j == (height - 1)) ) 
      {
        bkgval = bkgval + dataval;
        nbkg++;
      }
      if ( (i == 0) || (i == (width - 1))) 
      {
        bkgval = bkgval + dataval;
        nbkg++;
      }       
      guide_star[k] = dataval;
      k++;
    }
  } 

  anbkg = (double) nbkg;
  bkgval = bkgval / anbkg;
  
  /* Subtract the background */
  
  k = 0;
  for (j = 0; j < height; j++)
  {
    for (i = 0; i < width; i++)
    {
      guide_star[k] = guide_star[k] - bkgval;
      k++;
    }
  }
      
  /* Find the guide star in this new image */
  
  find_guide_star(guide_star, width, height, &x_err, &y_err);
  
  /* Find the error from the center of the image subarea  */
    
  x_0 = (double) width;
  x_0 = x_0/2.;
  y_0 = (double) height;
  y_0 = y_0/2.;
  
  x_err = x_err - x_0;
  y_err = y_err - y_0; 
  
  /* Use the latest error to correct the image */
  
  if ( guide_flag == TRUE )
  {
    if ( guide == TRACKING )
    {
      /* Correct telescope pointing based on guide star location */

      feedback_tracking(x_err, y_err);
    }
    else if  ( guide == OPTICAL )
    {
      /* Correct adaptive optics based on guide star location */
      
      feedback_ao(x_err, y_err);
    }
  }
  
  if ( guide_log_flag == TRUE )
  {
    time_t timebuf;
    char *gmtstr;
    
    time(&timebuf);
    gmtstr = asctime(gmtime(&timebuf));
    if (gmtstr[strlen(gmtstr) - 1] == '\n') gmtstr[strlen(gmtstr) - 1]= '\0';
    fp_log=fopen("guide.log", "a");
    fprintf(fp_log,"%s  %f  %f\n", gmtstr, x_err, y_err);
    fflush(fp_log);
    fclose(fp_log);
  }
  
  free(guide_star);
  return(1);
}
   

/* Acquire a self-guided image frame and save it as a fits file  */
/* Uses tracking CCD to find and apply guiding corrections       */

int CaptureGuide(int *phase,
  unsigned short *image_data, 
  unsigned short *track_data,
  double exposure_i, int subarea_i, 
  int x_i, int y_i, int width_i, int height_i,
  double exposure_t, int subarea_t, 
  int x_t, int y_t, int width_t, int height_t,
  int guide, int guide_flag, int guide_log_flag)
{
  fprintf(stderr,"Apogee cameras do not have a dual imaging capability.");
  return(0);
}

int CaptureEnd(int *phase)
{
  int status;
    
  ccd_phase = *phase;
  
  if ( ccd_phase == 2 )
  {
    
    /* End an exposure without readout */
    
    status = apogee_stop_exposure();
    ccd_phase = 0;
    *phase = ccd_phase;
    if ( status == FALSE )  
    {
      fprintf(stderr,"Error ending image exposure");
      return(0);
    }
    return(1);
  }  
  
  return(0);
}

/* FITS routines                               */
/*                                             */
/* Write a FITS primary array with a 2-D image */
/*   and a header with keywords                */
/*                                             */             
  
void WriteFits(char *filename, int w, int h, unsigned short *data, 
  double obs_duration, 
  char*  obs_type, 
  char*  obs_date, 
  char*  obs_target,
  char*  obs_instrument, 
  char*  obs_filter, 
  char*  obs_telescope)  
{
    fitsfile *fptr;       /* pointer to the FITS file, defined in fitsio.h */
    int status;
    long  fpixel, nelements;

    /* Initialize FITS image parameters */

    int bitpix   =  USHORT_IMG;       /* 16-bit unsigned short pixel values */
    long naxis    =   2;              /* 2-dimensional image                */    
    long naxes[2] = { 256,256 };      /* default image 256 wide by 256 rows */
    
    /* Set the actual width and height of the image */

    naxes[0] = w;
    naxes[1] = h;

    /* Delete old FITS file if it already exists */  

    remove(filename); 
     
    /* Must initialize status before calling fitsio routines */           

    status = 0;  
    
    /* Create a new FITS file and show error message if one occurs */

    if (fits_create_file(&fptr, filename, &status)) 
         show_cfitsio_error( status );           

    /* Write the required keywords for the primary array image.       */
    /* Since bitpix = USHORT_IMG, this will cause cfitsio to create   */
    /* a FITS image with BITPIX = 16 (signed short integers) with     */
    /* BSCALE = 1.0 and BZERO = 32768.  This is the convention that   */
    /* FITS uses to store unsigned integers.  Note that the BSCALE    */
    /* and BZERO keywords will be automatically written by cfitsio    */
    /* in this case.                                                  */

    if ( fits_create_img(fptr,  bitpix, naxis, naxes, &status) )
        show_cfitsio_error( status );          

    fpixel = 1;                               /* first pixel to write      */
    nelements = naxes[0] * naxes[1];          /* number of pixels to write */

    /* Write the array of unsigned integers to the FITS file */
         
    if ( fits_write_img(fptr, TUSHORT, fpixel, nelements, data, &status) )
      show_cfitsio_error( status );
        
    /* Write optional keywords to the header */
    
    if ( fits_update_key_dbl(fptr, "EXPTIME", obs_duration, -2,
      "exposure time (seconds)", &status) )
      show_cfitsio_error( status );
           
    if ( fits_update_key_str(fptr, "DATE-OBS", 
         obs_date, "date of observation (UT)", &status) )
         show_cfitsio_error( status );
         
    if ( fits_update_key_str(fptr, "IMAGETYP", 
         obs_type, "image type", &status) )
         show_cfitsio_error( status );         

    if ( fits_update_key_str(fptr, "TARGET", 
         obs_target, "target", &status) )
         show_cfitsio_error( status );
                   
    if ( fits_update_key_str(fptr, "INSTRUME", 
         obs_instrument, "instrument", &status) )
         show_cfitsio_error( status ); 
                         
    if ( fits_update_key_str(fptr, "FILTER", 
         obs_filter,"filter", &status) )
         show_cfitsio_error( status );

    if ( fits_update_key_str(fptr, "TELESCOP", 
         obs_telescope,"telescope", &status) )
         show_cfitsio_error( status );         
           
    if ( fits_write_date(fptr, &status) )
         show_cfitsio_error( status );                   
          
    /* Close the file */             

    if ( fits_close_file(fptr, &status) )              
         show_cfitsio_error( status );           

    return;
}


/* Print cfitsio error report */

void show_cfitsio_error(int status)
{
  if (status)
  {
     fits_report_error(stderr, status); 
  }
  return;
}  


/* Autoguiding */

/* Feedback to relays based on guide star error signal */
/* Global guide parameters deliver calibrated response */
/* Uses int relay 1 2 4 8 -> +X -X +Y -Y               */
/* Activates the relay servicing largest error         */
/* Make repeated calls to bring both errors down       */

void feedback_tracking(double x_err, double y_err)
{
  int relay, relay_on_time;
  double fx, fy;
   
  fx = guide_x_to_track_x *x_err + guide_y_to_track_x*y_err;
  fy = guide_x_to_track_y *x_err + guide_y_to_track_y*y_err;
    
  if (fx*fx > fy*fy)
  {
    if ( fx > 0. )
    {
      relay = 1;
      relay_on_time = fx;  
      ActivateCameraRelay(relay, relay_on_time);
    }
    else if ( fx < 0. )
    {
      relay = 2;
      relay_on_time = -fx;  
      ActivateCameraRelay(relay, relay_on_time);
    }  
  }
  else
  {
    if ( fy > 0. )
    {
      relay = 4;
      relay_on_time = fy;  
      ActivateCameraRelay(relay, relay_on_time);
    }
    else if ( fy < 0. )
    {
      relay = 8;
      relay_on_time = -fy;  
      ActivateCameraRelay(relay, relay_on_time);
    }
  }  
  return;
}

/* Feedback ao system based on guide star offset       */
/* Global guide parameters deliver calibrated response */
/* AO center is (2048,2048)                            */ 
/* Error coordinate center is (0,0)                    */
/* Uses SetCameraAOTipTilt to check bounds             */

void feedback_ao(double x_err,double y_err)
{
  int ao_x, ao_y;
  double fx, fy;
   
  fx = guide_x_to_ao_x *x_err + guide_y_to_ao_x*y_err;
  fy = guide_x_to_ao_y *x_err + guide_y_to_ao_y*y_err;
  ao_x = 2048 + (int) fx;
  ao_y = 2048 + (int) fy;
  
  SetCameraAOTipTilt(ao_x,ao_y);
  
  return;
}

/* Determine barycenter of guide star in image subarray */

void find_guide_star(double *data, int w, int h, double *x_s, double *y_s)
{
  int i,j,k;
  double xval,yval,pixval,sumval,xav,yav;
    
  k = 0;
  sumval = 0.;
  xav = 0.;
  yav = 0.;
  for (j = 0; j < h; j++)
  {
    yval = (double) j;
    for (i = 0; i < w; i++)
    {
      pixval = data[k];
      if ( pixval >= guide_threshold )
      {
        xval = (double) i;
        sumval = sumval + pixval;
        xav = xav + xval * pixval;
        yav = yav + yval * pixval;
      }
      k++;
    }
  }
    
  /* Handle frame without signal */ 
  
  if (sumval <= 0.)
  {
    *x_s = ((double) w)/2.; 
    *y_s = ((double) h)/2.;
    return;
  }
     
  xav = xav / sumval;
  yav = yav / sumval;
  *x_s = xav;
  *y_s = yav;
  
  return;
}

