/* -------------------------------------------------------------------------- */
/* -                   Astronomical CCD Camera Control                      - */
/* -                             INDI Driver                                - */
/* -------------------------------------------------------------------------- */
/*                                                                            */
/* Copyright (c) 2004-2012 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.                               */
/*                                                                            */
/* 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.            */
/*                                                                            */
/* Date: September 3, 2013                                                    */
/* Version: 4.2                                                               */

#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 <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#include <fitsio.h>

#include "indidevapi.h"
#include "ccd.h"
#include "protocol.h"
         
/* Files */

FILE *fp_config;                       /* Configuration file pointer */
static char *configfile;               /* Configuration file name */
void read_config(void);
  
/* Internal flags and counters */

int  acquire = SINGLE;
int  frame = LIGHT;
int  phase = 0;
int  sequence = 0;
int  frame_count = 1;
int  frame_count_max = FRAMECOUNTMULTI;
int  frame_count_multi = FRAMECOUNTMULTI; 
int  archive = FALSE;
int  image_number = 1;
int  transfer = NOCOPY;
int  scripting = FALSE;  

/* Internal character arrays used to hold text */

char base_name[64]; 
char file_name[64];
char filter_name[24];

/* Fits header elements */

double fits_exposure;
char fits_date[64];
char fits_type[64];
char fits_target[64];
char fits_instrument[64];
double fits_temperature;
char fits_filter[64];
char fits_telescope[64];

/* Fits header exposure start time */

time_t date_obs;

/* Executable external script */

char ccd_script[64];

/* Functions from the camera protocol */

extern int GetCameraInfo(int *image_w, int *image_h);

extern int CaptureImage(int *phase,  unsigned short *data,
  int frame, double exposure, int region, 
  int x, int y, int w, int h);
  
extern int CaptureEnd(int *phase);  
       
extern void WriteFits(char *filename, int w, int h, unsigned short *data, 
  double fits_duration, 
  char*  fits_type, 
  char*  fits_date, 
  char*  fits_target,
  char*  fits_instrument,
  double fits_temperature,
  char*  fits_filter, 
  char*  fits_telescope);    
 
extern int OpenCamera(int device);     

extern int CloseCamera(int device);    

extern int GetCameraStatus(int *status);

extern int GetCameraTemperature(double *power, 
  double *temperature, double *setpoint);

extern int SetCameraTemperature(int state, double temperature);

extern int ActivateCameraRelay(int relay, double ontime);
 
extern int GetCameraFilterName(int new_filter_number, char *new_filter_name);

extern int SetCameraFilterName(int flag,  
  int new_filter_number, char *new_filter_name);

extern int SetCameraFilter(int new_filter_number);

/* Camera variables */

unsigned short *image_data;

double current_exposure;
int    image_w = 1024;
int    image_h = 1024;
double image_exposure = 1.;
double image_exposure_remaining = 1.;
int    region = 0;
int    region_x = 1;
int    region_y = 1; 
int    region_w = 512;
int    region_h = 512;

/* Temperature control */

double setpoint = -10.;
double temperature = 25.;  
double cooler_power;  
int    cool = 0;

/* Temperature reporting */

void save_ccd_temperature();       


/* Filter functions */

int get_filter_name (int new_filter_number, char *new_filter_name);
int set_filter(int filter);
int set_filter_name(int flag,  
  int new_filter_number, char *new_filter_name);

/* Filter variables */

int    filter_max = FILTER_MAX;
int    filter_max_gui = FILTER_MAX;   
int    filter = FILTER_START;
int    filter_wheel = FILTER_WHEEL;

/* Array of dynamic labels for the filters */ 

static char filter_label[10][32] = {
  " (1)              ",
  " (2)              ",
  " (3)              ",
  " (4)              ",
  " (5)              ",
  " (6)              ",  
  " (7)              ",  
  " (8)              ",
  " (9)              ", 
  "(10)              "
 };


/* Array of default labels for the filters */ 

static char default_filter_label[10][32] = {
  " (1)              ",
  " (2)              ",
  " (3)              ",
  " (4)              ",
  " (5)              ",
  " (6)              ",  
  " (7)              ",  
  " (8)              ",
  " (9)              ", 
  "(10)              "
 };

/* Camera relay control */

int    relay = EAST;
double relay_ontime = RELAYONTIME;

/* INDI prototypes and variables */

static void camera (void *p);
static int sendblob (char *filename);
static int sendimage (char *filename);
static int runscript (char *filename);

static struct timeval xtv;

/* 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 vector 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       */


/* Image exposure time */

static INumber indi_image_exposure = 
{ 
  "time", 
  "Time", 
  "%.1f", 
  MINEXP, 
  MAXEXP, 
  INCEXP, 
  DEFEXP 
};

static INumberVectorProperty indi_image_exposure_property = 
{ 
  MYDEV, 
  "image_exposure", 
  "Image Exposure", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  &indi_image_exposure, 
  1 
};


/* Read-only parameters of camera, filter and acquistion system */

static INumber indi_ccd_system[] = 
{
  {"image_width",  "Image CCD Width",  "%.0f", 1, 1, 2, 4096},
  {"image_height", "Image CCD Height", "%.0f", 1, 1, 2, 4096},
  {"filter_max",   "Filters",          "%.0f", 1, 1, 1, FILTER_MAX},
};

static INumberVectorProperty indi_ccd_system_property = 
{ 
  MYDEV, 
  "ccd_system", 
  "CCD System", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  indi_ccd_system, 
  NARRAY(indi_ccd_system)
};

/* Image region */

static INumber indi_image_region[] = 
{
  {"x", "X", "%.0f", 1, 4096, 2, 1},
  {"y", "Y", "%.0f", 1, 4096, 2, 1},
  {"w", "W", "%.0f", 1, 4096, 2, 4096},
  {"h", "H", "%.0f", 1, 4096, 2, 4096},
};

static INumberVectorProperty indi_image_region_property = 
{ 
  MYDEV, 
  "image_region", 
  "Image Region", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  indi_image_region, 
  NARRAY(indi_image_region)
};

/* Temperature control */

static INumber indi_temperature_control = 
{ 
  "setting", 
  "Setting", 
  "%.1f", 
  -50.,  
  30.,  
  1.,  
  -10. 
};

static INumberVectorProperty indi_temperature_control_property = 
{ 
  MYDEV, 
  "temperature_control", 
  "Temperature Control", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  &indi_temperature_control, 
  1 
};


/* Camera temperature reading */

static INumber indi_camera_temperature= 
{ 
  "reading", 
  "Reading", 
  "%.1f", 
  -80.,  
  30.,  
  1.,  
  -10. 
};

static INumberVectorProperty indi_camera_temperature_property = 
{ 
  MYDEV, 
  "camera_temperature", 
  "Camera Temperature", 
  "", 
  IP_RO, 
  0, 
  IPS_IDLE, 
  &indi_camera_temperature, 
  1 
};

/* Filter selection */

static INumber indi_filter_selection = 
{ 
  "selection", 
  "Selection", 
  "%.0f", 
  1,  
  FILTER_MAX,  
  1,  
  FILTER_START 
};


static INumberVectorProperty indi_filter_selection_property = 
{ 
  MYDEV, 
  "filter", 
  "Filter", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  &indi_filter_selection, 
  1 
};


/* Selection of image number */

static INumber indi_image_number = 
{ 
  "number", "Base Number", "%.0f", 1, 1, 1, 1000
};

static INumberVectorProperty indi_image_number_property = 
{ 
  MYDEV, 
  "image", 
  "Image Number", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  &indi_image_number,
  1
};

/* Selection of frame multi  */

static INumber indi_frame_multi = 
{ 
  "multi", 
  "Multi", 
  "%.0f", 
  1, 
  FRAMECOUNTMULTI, 
  1, 
  1000
};

static INumberVectorProperty indi_frame_multi_property = 
{ 
  MYDEV, 
  "frame", 
  "Frame", 
  "", 
  IP_RW, 
  0, 
  IPS_IDLE, 
  &indi_frame_multi,
  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            */


/* Cooler switch  */

static ISwitch indi_cooler_power[]= 
{ 
  {"power", "Power", ISS_ON}
};

static ISwitchVectorProperty indi_cooler_power_property = 
{
  MYDEV, 
  "cooler", 
  "Cooler", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_cooler_power, 
  1
};

/* Image region control  */

static ISwitch indi_image_region_control[] = 
{ 
  {"option", "Option", ISS_OFF},
};


static ISwitchVectorProperty indi_image_region_control_property = 
{
  MYDEV, 
  "image_region_control", 
  "Image Region Control", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_image_region_control, 
  NARRAY(indi_image_region_control)
};


/* Frame type switch  */

static ISwitch indi_frame_type[] = 
{   
  {"dark",  "Dark", ISS_OFF},
  {"light", "Light",ISS_ON},
  {"bias",  "Bias", ISS_OFF},
  {"flat",  "Flat", ISS_OFF}
};

static ISwitchVectorProperty indi_frame_type_property = 
{
  MYDEV, 
  "frame", 
  "Frame", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_frame_type, 
  NARRAY(indi_frame_type)
};

/* Sequence switch  */

static ISwitch indi_sequence[] = 
{ 
  {"run", "Run", ISS_OFF},
};

/* Sequence type switch vector property */

static ISwitchVectorProperty indi_sequence_property = 
{
  MYDEV, 
  "sequence", 
  "Sequence", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_sequence, 
  NARRAY(indi_sequence)
};


/* Acquire mode switch  */

static ISwitch indi_acquire_mode[] = 
{ 
  {"single", "Single", ISS_ON},
  {"focus", "Focus",ISS_OFF},
  {"multi", "Multi",ISS_OFF},
};


static ISwitchVectorProperty indi_acquire_mode_property =
{
  MYDEV, 
  "acquire", 
  "Acquire", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_acquire_mode, 
  NARRAY(indi_acquire_mode)
};

/* Image file transfer mode switch  */

static ISwitch indi_image_transfer_mode[] = 
{ 
  {"none",    "None",     ISS_ON},
  {"network", "Network", ISS_OFF},
  {"indi",    "Indi",    ISS_OFF},
};

static ISwitchVectorProperty indi_image_transfer_mode_property = 
{
  MYDEV, 
  "image_transfer", 
  "Image Transfer", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_image_transfer_mode, 
  NARRAY(indi_image_transfer_mode)
};

/* Image scripting mode switch  */

static ISwitch indi_script_mode[] = 
{ 
  {"run",    "Run",     ISS_OFF},
};

static ISwitchVectorProperty indi_script_mode_property = 
{
  MYDEV, 
  "script", 
  "Image Script", 
  "", 
  IP_RW,
  ISR_1OFMANY, 
  0, 
  IPS_IDLE, 
  indi_script_mode, 
  NARRAY(indi_script_mode)
};

/* Relay activation through camera  */
/* Assumes correct hardware mapping  */
/* Switch any or all on or off       */

static ISwitch indi_relay[] = 
{ 
  {"north", "N", ISS_OFF},
  {"south", "S", ISS_OFF},
  {"east",  "E", ISS_OFF},
  {"west",  "W", ISS_OFF}, 
};

static ISwitchVectorProperty indi_relay_property = 
{
  MYDEV, 
  "relay", 
  "Relay", 
  "", 
  IP_RW,
  ISR_NOFMANY, 
  0, 
  IPS_IDLE, 
  indi_relay, 
  NARRAY(indi_relay)
};


/* INDI blob                             */

/* Index name                             */
/* BLOB's label                           */
/* Format attr                            */
/* Malloced binary large object bytes     */
/* Bytes in blob                          */
/* Number of uncompressed bytes           */
/* Pointer to parent                      */
/* Opt: handy place to hang helper info   */

/* Device name                            */
/* Property name                          */
/* Short description                      */
/* GUI grouping hint                      */
/* Client accessibility hint              */
/* Current max time to change, secs       */
/* Current property state                 */
/* BLOBs comprising this vector           */
/* Dimension of bp[]                      */
/* ISO 8601 timestamp of this event       */
/* Opt: handy place to hang helper info   */


/* Indi image transfer */

static IBLOB indi_image_blob = 
{
  "copy", 
  "Copy"
};

static IBLOBVectorProperty indi_image_blob_property = 
{
  MYDEV, 
  "image", 
  "Image", 
  "",
  IP_RO, 
  0, 
  IPS_IDLE, 
  &indi_image_blob, 
  1
};


/* INDI text */

/* Text descriptor */
/* Index name */
/* Short description */
/* Malloced text string */
/* Pointer to parent */
/* Handy place to hang helper info */


/* Text vector property descriptor */
/* Device name */
/* Property name */
/* Short description */
/* GUI grouping hint */
/* Client accessibility hint */
/* Current max time to change, secs */
/* Current property state */
/* Texts comprising this vector */
/* Dimension of tp[] */
/* ISO 8601 timestamp of this event */
/* Handy place to hang helper info */


/* Archive base_name */

static IText indi_archive_base_name[] = 
{
  {"base_name", "Base Name", base_name}
};

static ITextVectorProperty indi_archive_base_name_property = 
{
    MYDEV, 
    "archive", 
    "Archive", 
    "", 
    IP_RW, 
    30,
    IPS_IDLE, 
    indi_archive_base_name, 
    NARRAY(indi_archive_base_name),
};


/* Target fits_target */

static IText indi_archive_target[] = 
{
  {"fits_target", "Fits Target", fits_target}
};

static ITextVectorProperty indi_archive_target_property = 
{
    MYDEV, 
    "target", 
    "Target", 
    "", 
    IP_RW, 
    30,
    IPS_IDLE, 
    indi_archive_target, 
    NARRAY(indi_archive_target),
};


/* Telescope fits_telescope */

static IText indi_archive_telescope[] = 
{
  {"fits_telescope", "Fits Telescope", fits_telescope}
};

static ITextVectorProperty indi_archive_telescope_property = 
{
    MYDEV, 
    "telescope", 
    "Telescope", 
    "", 
    IP_RW, 
    30,
    IPS_IDLE, 
    indi_archive_telescope, 
    NARRAY(indi_archive_telescope),
};


/* Instrument fits_instrument */

static IText indi_archive_instrument[] = 
{
  {"fits_instrument", "Fits Instrument", fits_instrument}
};

static ITextVectorProperty indi_archive_instrument_property = 
{
    MYDEV, 
    "instrument", 
    "Instrument", 
    "", 
    IP_RW, 
    30,
    IPS_IDLE, 
    indi_archive_instrument, 
    NARRAY(indi_archive_instrument),
};


/* File name */

static IText indi_file_name[] = 
{
  {"name", "Name", file_name}
};

static ITextVectorProperty indi_file_name_property = 
{
    MYDEV, 
    "file", 
    "File", 
    "", 
    IP_RO, 
    30,
    IPS_IDLE, 
    indi_file_name, 
    NARRAY(indi_file_name),
};


/* Scripting file name ccd_script */

static IText indi_script_file[] = 
{
  {"ccd_script", "CCD Script", ccd_script}
};

static ITextVectorProperty indi_script_file_property = 
{
    MYDEV, 
    "script", 
    "Script", 
    "", 
    IP_RW, 
    30,
    IPS_IDLE, 
    indi_script_file, 
    NARRAY(indi_script_file),
};


/* Filter names */

static IText indi_filter_name[] = 
{
  {"name", "Name", filter_name}
};


static ITextVectorProperty indi_filter_name_property = 
{
    MYDEV, 
    "filter", 
    "Filter", 
    "", 
    IP_RO, 
    30,
    IPS_IDLE, 
    indi_filter_name, 
    NARRAY(indi_filter_name),
};


/* INDI functions */


/* Camera startup */

static void initccd(void)
{
  int camera_status = FALSE;
  int filter_status = FALSE;
  int cooler_status = FALSE;

  /* Initialization control */
  
  static int inited;
  
  if (inited)
  { 
    return;
  }

  /* Set base_name to default */
  
  strcpy(base_name,"image_");
  
  /* Handle the configuration file to change other defaults */
  
  read_config();
  

  /* Initialize the driver for a generic USB camera */

  camera_status = OpenCamera(USB);

  /* Alert stderr to a problem and exit */
  
  if (camera_status != TRUE)
  {
    fprintf(stderr,"Camera did not respond to startup request \n");
    exit(1);
  }
  
  /* Contact the camera and allow its driver to update parameters */
  
  camera_status = GetCameraInfo(&image_w, &image_h);
        
  /* Set base_name to default */
  
  strcpy(base_name,"image_");
  
  /* Set empty string for file_name until the first image is stored */
  
  strcpy(file_name,"");
  
  /* Initialize the fits headers */
  
  sprintf(fits_type, "                ");
  sprintf(fits_date, "                ");
  sprintf(fits_target, "                ");
  sprintf(fits_filter, "                ");
  sprintf(fits_instrument, "                ");
  sprintf(fits_telescope, "                ");
  
  /* Handle the configuration file to change defaults */
  
  configfile = (char *) malloc (MAXPATHLEN);
  strcpy(configfile,CONFIGFILE);  

  read_config();
  
  /*  Note that configfile may be changed later through the File menu */
   
  /* Set the image sizes */

  region_x = 1;
  region_y = 1; 
  region_w = image_w;
  region_h = image_h;
          
  /* Select filter and save header information */
  
  filter_status = set_filter(filter);
  filter_status = get_filter_name(filter, filter_name);

  sprintf(fits_filter,filter_name);
         
  /* Alert operator to absence of filter wheel or set filter wheel error */
  
  if (filter_status != TRUE)
  {
    fprintf(stderr,"Filter wheel did not respond to the startup request\n");
    filter_wheel = NO_FILTER_WHEEL;
  }

  cooler_status = GetCameraTemperature(&cooler_power, 
    &temperature, &setpoint);
    
  /* Alert stderr to a temperature controller problem but do not exit*/
  
  if (cooler_status != TRUE)
  {
    fprintf(stderr,"Camera temperature control did not respond to the startup request \n");
  }

  /* Set initial cooler temperature and turn on controller */

  setpoint = -10.;
  cool = TRUE;
  SetCameraTemperature(cool, setpoint); 
      
  /* Allocate memory for images */

  image_data = malloc((image_w*image_h)*sizeof(unsigned short));    
    
  /* Set empty string for file_name until the first image is stored */
  
  strcpy(file_name,"");

  /* Set temperature setpoint to default */
  
  setpoint = -10.;
  
  /* Alert stderr to a problem and exit */
  
  if (camera_status != TRUE)
  {
    fprintf(stderr,"The camera did not respond to the startup request \n");
    exit(1);
  }
  
  /* Update INDI with camera parameters */
  
  region_x = 1;
  indi_image_region[0].value = region_x;
  IDSetNumber (&indi_image_region_property, 
    "Image region x %d", region_x);
    
  region_y = 1;
  indi_image_region[1].value = region_y;
  IDSetNumber (&indi_image_region_property, 
    "Image region y %d", region_y);
    
  indi_image_region[2].value = image_w;
  IDSetNumber (&indi_image_region_property, 
    "Image region w %d", image_w);
    
  indi_image_region[3].value = image_h;
  IDSetNumber (&indi_image_region_property, 
    "Image region h %d", image_h);
  
  indi_filter_selection.value = filter;
  IDSetNumber (&indi_filter_selection_property, 
    "Filter number %d", filter); 
    
    
  IDSetNumber (&indi_temperature_control_property, 
    "Setpoint %g (C)", 
    setpoint);  
  
  indi_cooler_power_property.s = IPS_OK;
  IDSetSwitch (&indi_cooler_power_property, 
    "Cooling power switch on at setpoint %g", setpoint);
       
  /* Allocate memory for images */
  
  image_data = malloc((image_w*image_h)*sizeof(unsigned short));    
      
    
  /* Set flag so we do the initialization only once */
  
  inited = TRUE;

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

/* Dummy definition for libip */

void pm_set (int p)
{
}

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

void ISGetProperties (const char *dev)
{
   if (dev && strcmp (MYDEV, dev))
   {
     return;
   }
    
   initccd();
   IDDefSwitch (&indi_sequence_property, NULL);      
   IDDefNumber (&indi_image_exposure_property, NULL);
   IDDefNumber (&indi_frame_multi_property, NULL);   
   IDDefNumber (&indi_temperature_control_property, NULL);
   IDDefNumber (&indi_camera_temperature_property, NULL);
   IDDefSwitch (&indi_cooler_power_property, NULL);
   IDDefNumber (&indi_ccd_system_property, NULL);
   IDDefSwitch (&indi_image_region_control_property, NULL);
   IDDefNumber (&indi_image_region_property, NULL);
   IDDefSwitch (&indi_frame_type_property, NULL);   
   IDDefSwitch (&indi_acquire_mode_property, NULL);
   IDDefNumber (&indi_filter_selection_property, NULL);
   IDDefText   (&indi_filter_name_property, NULL);
   IDDefText   (&indi_archive_base_name_property, NULL);
   IDDefNumber (&indi_image_number_property, NULL);
   IDDefText   (&indi_file_name_property, NULL);
   IDDefText   (&indi_script_file_property, NULL);
   IDDefText   (&indi_archive_target_property, NULL);
   IDDefText   (&indi_archive_telescope_property, NULL);
   IDDefText   (&indi_archive_instrument_property, NULL);
   IDDefSwitch (&indi_image_transfer_mode_property, NULL);
   IDDefSwitch (&indi_script_mode_property, NULL);
   IDDefSwitch (&indi_relay_property, NULL);
   IDDefBLOB   (&indi_image_blob_property, NULL);
}


/* 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;
  
  if (strcmp (dev, MYDEV))
  { 
      /* Ignore if this is not for us */
      return;
  }
  
  if (!strcmp (name, indi_image_exposure_property.name) && n > 0) 
  {    
    
    /* New imaging exposure */
    
    if (indi_image_exposure_property.s == IPS_BUSY) 
    {
      indi_image_exposure_property.s = IPS_OK;
      indi_image_exposure.value = 0;      
      IDSetNumber (&indi_image_exposure_property, "Image exposure aborted");
      sequence = 0;
      indi_sequence[0].s = ISS_OFF; 
      indi_sequence_property.s = IPS_OK;      
      IDSetSwitch (&indi_sequence_property, "Waiting to start a sequence");      
    } 
    else 
    {
      indi_image_exposure.value = doubles[0];
      indi_image_exposure_property.s = IPS_OK;
      image_exposure = indi_image_exposure.value;
      IDSetNumber (&indi_image_exposure_property, 
        "Set %g second duration image exposure", 
        image_exposure);
    }
  }
  

  if (!strcmp (name, indi_image_number_property.name) && n > 0) 
  {
    
    /* A new image number has been sent */
    
    indi_image_number.value = doubles[0];
    image_number = indi_image_number.value;
    if (image_number < 2)
    {
      image_number = 1;
    }
    IDSetNumber (&indi_image_number_property, 
      "Image number %d", 
      image_number);
  }

  if (!strcmp (name, indi_frame_multi_property.name) && n > 0) 
  {
    
    /* A new maximum multi-mode frame count has been sent */
    
    indi_frame_multi.value = doubles[0];
    frame_count_multi = indi_frame_multi.value;
    if (frame_count_multi < 2)
    {
      frame_count_multi = 1;
    }
    IDSetNumber (&indi_frame_multi_property, 
      "Maximum multiframe count %d", 
      frame_count_multi);
  }
  
  if (!strcmp (name, indi_temperature_control_property.name) && n > 0) 
  {
    
    /* A new setpoint has been sent */
    
    indi_temperature_control.value = doubles[0];
    setpoint = indi_temperature_control.value;
    SetCameraTemperature(cool, setpoint);
    IDSetNumber (&indi_temperature_control_property, 
      "Setpoint %g (C)", 
      setpoint);
  }

  if (!strcmp (name, indi_image_region_property.name) && n > 0) 
  {
    /* New image region parameters have been sent */
    
    for (i = 0; i < n; i++)
    {
      if (strcmp (indi_image_region[0].name, names[i]) == 0)
      {
        region_x = doubles[i];
        if (region_x > image_w)
        {
          region_x = image_w;
        }
        if (region_x < 1)
        {
          region_x = 1;
        }
        indi_image_region[0].value = region_x;
        IDSetNumber (&indi_image_region_property, 
          "Image region x %d", region_x);
      }
      else if (strcmp(indi_image_region[1].name, names[i]) == 0)
      {
        region_y = doubles[i];
        if (region_y > image_h)
        {
          region_y = image_h;
        }
        if (region_y < 1)
        {
          region_y = 1;
        } 
        indi_image_region[1].value = region_y;
        IDSetNumber (&indi_image_region_property, 
          "Image region y %d", region_y);      
      }
      else if (strcmp(indi_image_region[2].name, names[i]) == 0)
      {
        region_w = doubles[i];
        if (region_w > image_w)
        {
          region_w = image_w;
        }
        if (region_w < 1)
        {
          region_w = 1;
        }        
        indi_image_region[2].value = region_w;
        IDSetNumber (&indi_image_region_property, 
          "Image region w %d", region_w);      
      }
      else if (strcmp(indi_image_region[3].name, names[i]) == 0)
      {
        region_h = doubles[i];
        if (region_h > image_h)
        {
          region_h = image_h;
        }
        if (region_h < 1)
        {
          region_h = 1;
        }         
        indi_image_region[3].value = region_h;
        IDSetNumber (&indi_image_region_property, 
          "Image region h %d", region_h);      
      }
      else 
      {
        IDSetNumber (&indi_image_region_property, 
          "Image region parameter setting error");      
      }                  
    }
  }
 
  if (!strcmp (name, indi_filter_selection_property.name) && n > 0) 
  {    

    /* Filter selection request */

    if (indi_filter_selection_property.s != IPS_BUSY) 
    {
      indi_filter_selection.value = doubles[0];
      filter = indi_filter_selection.value;
      set_filter(filter);
      indi_filter_selection_property.s = IPS_OK;
      IDSetNumber (&indi_filter_selection_property, 
        "Selected filter %d", filter);
      get_filter_name (filter, filter_name);
      IDSetText (&indi_filter_name_property, 
        "Filter name %s", filter_name);
      sprintf(fits_filter,"%s",filter_name);
    }
  }

  /* New numbers have been handled */
  
}

/* 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 */

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


void ISNewText (const char *dev, const char *name, 
  char *texts[], char *names[], int n)
{
  if (strcmp (dev, MYDEV))
  { 
      /* Ignore if this is not for us */
      return;
  }
  
  if (!strcmp (name, indi_archive_base_name_property.name) && n > 0) 
  {  
    IText *tp = IUFindText (&indi_archive_base_name_property, names[0]);
    char *newtext = texts[0];

    if (!tp)
    {
      indi_archive_base_name_property.s = IPS_IDLE;
      IDSetText (&indi_archive_base_name_property, "New name not found");       
      return;
    }

    strncpy(base_name,newtext,17);
    base_name[16]='\0';
    indi_archive_base_name_property.s = IPS_OK;
    IDSetText (&indi_archive_base_name_property, 
      "Set base_name %s", base_name); 
    return;
  }

  if (!strcmp (name, indi_archive_target_property.name) && n > 0) 
  {  
    IText *tp = IUFindText (&indi_archive_target_property, names[0]);
    char *newtext = texts[0];

    if (!tp)
    {
      indi_archive_target_property.s = IPS_IDLE;
      IDSetText (&indi_archive_target_property, "New fits_target not found");       
      return;
    }

    strncpy(fits_target,newtext,64);
    fits_target[63]='\0';
    indi_archive_target_property.s = IPS_OK;
    IDSetText (&indi_archive_target_property, 
      "Set fits_target %s", fits_target); 
    return;
  }

  if (!strcmp (name, indi_archive_telescope_property.name) && n > 0) 
  {  
    IText *tp = IUFindText (&indi_archive_telescope_property, names[0]);
    char *newtext = texts[0];

    if (!tp)
    {
      indi_archive_telescope_property.s = IPS_IDLE;
      IDSetText (&indi_archive_telescope_property, "New fits_telescope not found");       
      return;
    }

    strncpy(fits_telescope,newtext,64);
    fits_telescope[63]='\0';
    indi_archive_telescope_property.s = IPS_OK;
    IDSetText (&indi_archive_telescope_property, 
      "Set fits_telescope %s", fits_telescope); 
    return;
  }


  if (!strcmp (name, indi_archive_instrument_property.name) && n > 0) 
  {  
    IText *tp = IUFindText (&indi_archive_instrument_property, names[0]);
    char *newtext = texts[0];

    if (!tp)
    {
      indi_archive_instrument_property.s = IPS_IDLE;
      IDSetText (&indi_archive_instrument_property, "New fits_instrument not found");       
      return;
    }

    strncpy(fits_instrument,newtext,64);
    fits_instrument[63]='\0';
    indi_archive_instrument_property.s = IPS_OK;
    IDSetText (&indi_archive_instrument_property, 
      "Set fits_instrument %s", fits_instrument); 
    return;
  }

  if (!strcmp (name, indi_script_file_property.name) && n > 0) 
  {  
    IText *tp = IUFindText (&indi_script_file_property, names[0]);
    char *newtext = texts[0];

    if (!tp)
    {
      indi_script_file_property.s = IPS_IDLE;
      IDSetText (&indi_script_file_property, "New script file name not found");       
      return;
    }

    strncpy(ccd_script,newtext,64);
    ccd_script[63]='\0';
    indi_script_file_property.s = IPS_OK;
    IDSetText (&indi_script_file_property, 
      "Set ccd_script %s", ccd_script); 
    return;
  }


}

/* Client is sending us new switch settings */

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


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

  /* ignore if not ours */
  if (strcmp (dev, MYDEV))
      return;

  /* Test for new switch settings */

  if (!strcmp (name, indi_cooler_power_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_cooler_power_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_cooler_power); i++)
          indi_cooler_power[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_cooler_power_property.s = IPS_OK;
        cool = TRUE;
        SetCameraTemperature(cool,setpoint);
        IDSetSwitch (&indi_cooler_power_property, 
          "Cooling power switch on at setpoint %g", setpoint);
        break;
      }
      if (states[i] == ISS_OFF) 
      {
        for (i = 0; i < NARRAY(indi_cooler_power); i++)
          indi_cooler_power[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_cooler_power_property.s = IPS_ALERT;
        cool = FALSE;
        SetCameraTemperature(cool,setpoint);
        IDSetSwitch (&indi_cooler_power_property, "Cooling power switch off"); 
        break;
      }    
    }    
  }
  
  if (!strcmp (name, indi_image_region_control_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_image_region_control_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_image_region_control); i++)
          indi_image_region_control[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_image_region_control_property.s = IPS_ALERT;
        region = TRUE;
          IDSetSwitch (&indi_image_region_control_property, 
            "Image region on");
        break;
      }
      if (states[i] == ISS_OFF) 
      {
        for (i = 0; i < NARRAY(indi_image_region_control); i++)
          indi_image_region_control[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_image_region_control_property.s = IPS_OK;
        region = FALSE;
          IDSetSwitch (&indi_image_region_control_property, 
            "Image region set off");
        break;
      }      
    }
  }
     
  if (!strcmp (name, indi_frame_type_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_frame_type_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_frame_type); i++)
          indi_frame_type[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_frame_type_property.s = IPS_OK;
        
        /* Which frame type have we turned on? */
        
        if (!strcmp (sp->name, "dark"))
        {
          frame = DARK;
        }
        else if (!strcmp (sp->name, "light"))
        {
          frame = LIGHT;
        }
        else if (!strcmp (sp->name, "bias"))
        {
          frame = BIAS;
        }
        else if (!strcmp (sp->name, "flat"))
        {
          frame = FLAT;
        }
        else
        {
          frame = LIGHT;
        }                
                  
        /* Provide a visual alert that this is not a light frame */
        
        if (frame != LIGHT)
        {
          indi_frame_type_property.s = IPS_ALERT;
        }        
        
        IDSetSwitch (&indi_frame_type_property, 
          "Selected %s frame", sp->label);
        break;
      }
    }
  }
    
  
  if (!strcmp (name, indi_sequence_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_sequence_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_sequence); i++)
          indi_sequence[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_sequence_property.s = IPS_BUSY;
        sequence = 1;
        IDSetSwitch (&indi_sequence_property, 
          "Sequence on");
        break;
      }
      if (states[i] == ISS_OFF) 
      {
        for (i = 0; i < NARRAY(indi_sequence); i++)
          indi_sequence[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_sequence_property.s = IPS_OK;
        sequence = 0;
        IDSetSwitch (&indi_sequence_property, 
          "Sequence off");
        break;
      }      
    }
  }  
  
  if (!strcmp (name, indi_acquire_mode_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_acquire_mode_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_acquire_mode); i++)
          indi_acquire_mode[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_acquire_mode_property.s = IPS_OK;

        /* Which mode have we selected? */
        
        if (!strcmp (sp->name, "single"))
        {
          acquire = SINGLE;
        }
        else if (!strcmp (sp->name, "focus"))
        {
          acquire = FOCUS;
        }
        else if (!strcmp (sp->name, "multi"))
        {
          acquire = MULTI;
        }       
        else
        {      
          IDSetSwitch (&indi_acquire_mode_property, 
            "Unknown acquisition mode selected");
          break;
        } 
          
        if (acquire != SINGLE)
        {
          indi_acquire_mode_property.s = IPS_BUSY;
        }          
        IDSetSwitch (&indi_acquire_mode_property, 
          "Acquisition mode %s", sp->label);
        break;
      }
    }
  }
        
  if (!strcmp (name, indi_relay_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_relay_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_relay); i++)
          indi_relay[i].s = ISS_OFF;
        sp->s = ISS_OFF;
        indi_relay_property.s = IPS_OK;

        /* Which relay have we selected? */
        
        if (!strcmp (sp->name, "north"))
        {
          relay = NORTH;
        }
        else if (!strcmp (sp->name, "south"))
        {
          relay = SOUTH;
        }
        else if (!strcmp (sp->name, "east"))
        {
          relay = EAST;
        }  
        else if (!strcmp (sp->name, "west"))
        {
          relay = WEST;
        }    
        else
        {      
          IDSetSwitch (&indi_relay_property, 
            "Unknown relay direction selected");
          break;
        }         
        
        IDSetSwitch (&indi_relay_property, 
          "Relay direction activated %s", sp->label);

        /* Direction and mode have been set */
        /* Activate the request through the camera hardware interface */
          
        relay_ontime = RELAYONTIME;
        ActivateCameraRelay(relay, relay_ontime);
        break;
      }
    }
  }  


  if (!strcmp (name, indi_image_transfer_mode_property.name)) 
  {
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_image_transfer_mode_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_image_transfer_mode); i++)
          indi_image_transfer_mode[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_image_transfer_mode_property.s = IPS_OK;
 
        /* Which mode have we selected? */
        
        if (!strcmp (sp->name, "none"))
        {
          transfer = NOCOPY;
        }
        else if (!strcmp (sp->name, "network"))
        {
          transfer = NETCOPY;
        }        
        else if (!strcmp (sp->name, "indi"))
        {
          transfer = INDICOPY;
        }
        else
        {      
          IDSetSwitch (&indi_image_transfer_mode_property, 
            "Unknown transfer mode selected");
          break;
        }          
        IDSetSwitch (&indi_image_transfer_mode_property, 
          "Transfer mode %s", sp->label);
        break;
      }                
    }
  }
  
  if (!strcmp (name, indi_script_mode_property.name)) 
  {   
    for (i = 0; i < n; i++) 
    {
      ISwitch *sp = IUFindSwitch (&indi_script_mode_property, names[i]);
      if (states[i] == ISS_ON) 
      {
        for (i = 0; i < NARRAY(indi_script_mode); i++)
          indi_script_mode[i].s = ISS_OFF;
        sp->s = ISS_ON;
        indi_script_mode_property.s = IPS_OK;
        scripting = TRUE;
        IDSetSwitch (&indi_script_mode_property, 
          "Scripting on", sp->label);
        break;
      }                    
      if (states[i] == ISS_OFF) 
      {
        for (i = 0; i < NARRAY(indi_script_mode); i++)
          indi_sequence[i].s = ISS_ON;
        sp->s = ISS_OFF;
        indi_script_mode_property.s = IPS_OK;
        scripting = FALSE;
        IDSetSwitch (&indi_script_mode_property, 
          "Scripting off");
        break;
      }
    }  
  }  
  
  /* New switch settings have been handled */

}

/* Camera function called during the polling cycle                   */
/* Count cycles and find the temperature every minute                */
/* Start a sequence when the sequence flag is reset 1                */
/* Stop a sequence when the sequence flag is reset 0                 */
/* Start a new exposure when the phase is 0 while the sequence is 1  */
/* Update the time remaining in an ongoing exposure                  */
/* Restart the timer to call this function again                     */

static void camera (void *p)
{
  static int tcount;
  int year, month, day;
  int hours, minutes, seconds, milliseconds;
  long int microseconds;
  
  time_t now;
  struct tm *g;
   

  /* Take the device temperature 15 seconds on a 250 ms polling cycle */      
  if ((tcount < 0) || (tcount > 60))
  {
    tcount = 1;
    GetCameraTemperature(&cooler_power,&temperature,&setpoint);
    fits_temperature = temperature;
    indi_camera_temperature.value = temperature;
    IDSetNumber (&indi_camera_temperature_property, NULL);
  }
  tcount++;
           
  /*  Handle a request to interrupt a sequence */
  if ( sequence == 0 )
  {
    if (phase != 0)
    {
      /* End any exposure in progress */
      
      phase = 2;
      CaptureEnd(&phase); 
    }

    if (frame_count > 1)
    {
      frame_count = 1;
          
      /* Set the exposure property to show that there is no exposure */
      
      indi_image_exposure_property.s = IPS_OK;
      IDSetNumber (&indi_image_exposure_property, "Exposure interrupted");
    
      /* Set the sequence switch to show that it is not busy */
      
      indi_sequence[0].s = ISS_OFF;
      indi_sequence_property.s = IPS_OK;
      IDSetSwitch (&indi_sequence_property, "Waiting to start a sequence");           

      /* Set exposure to show starting value */
    
      indi_image_exposure.value = image_exposure;
      IDSetNumber (&indi_image_exposure_property, NULL);          
    }        
  }
  
  /* Handle a request for a new exposure */  
  if ((sequence == 1) && ( phase == 0 )) 
  {               
    /* Set parameters for new sequence if needed */
    if (frame_count == 1)
    {
      /* Start a new sequence */
      if (acquire == SINGLE)
      {
        frame_count_max = 1;
      }
      else
      {
        frame_count_max = frame_count_multi;
      } 

      if (acquire == FOCUS)
      {
        archive = FALSE;
      }
      else
      {
        archive = TRUE;
      } 

      /* Set the sequence switch to busy */
      indi_sequence_property.s = IPS_BUSY;
      IDSetSwitch (&indi_sequence_property, "Starting a sequence"); 
    }
    
    /* Note the time it started and save as xtv */
    gettimeofday (&xtv, NULL); 
    
    /* Copy seconds part to convert to UTC */

    now = xtv.tv_sec;
    
    /* Copy microseconds part to handle milliseconds of UTC */

    microseconds = xtv.tv_usec;
    
    /* Convert the microseconds part to milliseconds */
    
    milliseconds = microseconds/1000;
    
    /* Convert unix time now to UTC */
    
    g = gmtime(&now);

    /* Copy values pointed to by g to variables we will use later */

    year = g->tm_year;
    year = year + 1900;
    month = g->tm_mon;
    month = month + 1;
    day = g->tm_mday;
    hours = g->tm_hour;
    minutes = g->tm_min;
    seconds = g->tm_sec;
    
    /* Create ascii string for fits header */
       
    sprintf(fits_date, "%4d-%02d-%02dT%02d:%02d:%02d.%03d",
      year,month,day,hours,minutes,seconds,milliseconds);

    if (frame != BIAS)
    {
      current_exposure = image_exposure;
    }
    else
    {
      current_exposure = 0.001;
    } 
    
    CaptureImage(&phase, image_data,
    frame, current_exposure, region, 
    region_x, region_y, region_w, region_h);    
  }

  /* Update the remaining time if an exposure is in progress */
  /* Read the image into memory when done */
  if (phase == 1) 
  {
    struct timeval tv;
    double dt;

    /* Find total elapsed time since starting exposure */
    gettimeofday (&tv, NULL);
    dt = tv.tv_sec - xtv.tv_sec + (tv.tv_usec - xtv.tv_usec)/1e6;
    
    if (dt >= current_exposure) 
    {
      /* Exposure is ready to read */
      /* Read to memory */
      /* Reset the exposure time counter */
      
      indi_image_exposure.value = image_exposure;
      indi_image_exposure_property.s = IPS_OK;
      IDSetNumber (&indi_image_exposure_property, "Image readout");


      CaptureImage(&phase, image_data,
        frame, current_exposure, region, 
        region_x, region_y, region_w, region_h);
    } 
    else 
    {
      /* Exposure is not complete */

      indi_image_exposure.value = current_exposure - dt;
      IDSetNumber (&indi_image_exposure_property, NULL);
              
    }
  }
  
  if (phase == 2) 
  {
    /* Write a file appropriate for the acquisition mode */
    
    /* Set the image type description */

    if ( frame == DARK )
    {
      sprintf(fits_type, "%s", "Dark Frame");
    }
    else if ( frame == LIGHT )
    {
      sprintf(fits_type, "%s", "Light Frame");
    }
    else if ( frame == BIAS )
    {
      sprintf(fits_type, "%s", "Bias Frame");
    }
    else if ( frame == FLAT )
    {
      sprintf(fits_type, "%s", "Flat Frame");
    }    
    else
    {
      sprintf(fits_type, "%s", "");
    }      

    if (archive)
    {
      /* Archive an ennumerated file */
      
      if ( frame == DARK )
      {
        sprintf(file_name, "%s%05d.fits", "dark_", image_number);
      }
      else if ( frame == LIGHT )
      {
        sprintf(file_name, "%s%05d.fits", base_name, image_number);
      }
      else if ( frame == BIAS )
      {
        sprintf(file_name, "%s%05d.fits", "bias_", image_number);
      }
      else if ( frame == FLAT )
      {
        sprintf(file_name, "%s%05d.fits", "flat_", image_number);
      }    
      else
      {
        sprintf(file_name, "%s%05d.fits", base_name, image_number);
      } 
      image_number++;    
  
    }
    else
    {
      /* Save a generic file */
      sprintf(file_name, "image.fits");    
    }

    
    if (frame != BIAS)
    {
      fits_exposure = image_exposure;
    }
    else
    {
      fits_exposure = 0.;  
    }

    if (region == FALSE)
    {
      WriteFits(file_name, image_w, image_h, image_data, 
        fits_exposure, 
        fits_type, 
        fits_date, 
        fits_target,                                               
        fits_instrument,
        fits_temperature, 
        fits_filter, 
        fits_telescope);
      save_ccd_temperature();  
    }
    else
    {
      WriteFits(file_name, region_w, region_h, image_data, 
        fits_exposure, 
        fits_type, 
        fits_date, 
        fits_target,                                               
        fits_instrument,
        fits_temperature, 
        fits_filter, 
        fits_telescope);
      save_ccd_temperature();  
    }   
    
    /* Update the indi file name */

    IDSetText (&indi_file_name_property, "File name %s", file_name); 
    
    /* Send the frame by the method selected with transfer switch */
    
    if (transfer != NOCOPY)
    {
      if (transfer == INDICOPY)
      {    
        IDSetSwitch (&indi_image_transfer_mode_property, 
          "Sending frame by INDI");
        if (sendblob(file_name) == 0) 
        {
          indi_image_transfer_mode_property.s = IPS_ALERT;
          IDSetSwitch (&indi_image_transfer_mode_property, 
            "Indi frame transfer error");
        } 
        else 
        {
          indi_image_transfer_mode_property.s = IPS_OK;
          IDSetSwitch (&indi_image_transfer_mode_property, 
            "Frame sent by INDI");
        }
      }
      else if (transfer == NETCOPY)
      {    
        IDSetSwitch (&indi_image_transfer_mode_property, 
          "Frame transfer requested");
        if (sendimage(file_name) == -1) 
        {
          indi_image_transfer_mode_property.s = IPS_ALERT;
          IDSetSwitch (&indi_image_transfer_mode_property, 
            "Frame transfer request returned error flag");
        } 
        else 
        {
          indi_image_transfer_mode_property.s = IPS_OK;
          IDSetSwitch (&indi_image_transfer_mode_property, 
            "Frame dispatched");
        }
      }     
    } 
    
    
    /* Run an external script */
    
    if (scripting == TRUE)
    {
      IDSetSwitch (&indi_script_mode_property, 
        "Script execution requested");
      if (runscript(ccd_script) == -1) 
      {
        indi_script_mode_property.s = IPS_ALERT;
        IDSetSwitch (&indi_script_mode_property, 
          "Script request returned error flag");
      } 
      else 
      {
        indi_script_mode_property.s = IPS_OK;
        IDSetSwitch (&indi_script_mode_property, 
          "Script started");
      }        
    }   
 

    /* Reset for the next exposure */

    indi_image_exposure.value = image_exposure;
    IDSetNumber (&indi_image_exposure_property, NULL);

    phase = 0;
    frame_count++;
    
    /* End the sequence when requested frames have been taken */
    if ( frame_count > frame_count_max )
    {
      sequence = 0;
      frame_count = 1;
      indi_sequence[0].s = ISS_OFF; 
      indi_sequence_property.s = IPS_OK;      
      IDSetSwitch (&indi_sequence_property, "Waiting to start a sequence");    
    }  
  }   
  
  /* Start the timer again */
  IEAddTimer (POLLMS, camera, NULL);
}



/* Send the latest fits file through a system network routine */
/* Return system return value */

static int sendimage(char *name)
{
  int flag;
  char cmdstr[256];
  FILE *fp = fopen (name, "r");
  if ( fp == NULL )
  {
    return (0);
  }   

  /* Send file "name" by using a system command */

  sprintf(cmdstr,"transfer_image %s &",name);   
  flag = system(cmdstr);
      
  return (flag);

}


/* Run an external script */
/* Return system return value */

int runscript(char *script)
{
  int flag;
  char cmdstr[256];

  /* Run the external script by using a system command */

  sprintf(cmdstr,"%s",script);   
  flag = system(cmdstr);
  if (flag == -1)
  {
    return(0);
  }
  else
  {
    flag = 1;
  }          
  return (flag);
}

/* Send the latest fits file as a BLOB to the client through INDI */
/* No compression -- this version does not use zlib */
/* Return 1 if ok or else return 0 */

int sendblob(char *name)
{
  
  FILE *fp = fopen (name, "r");
  if ( fp == NULL )
  {
    return (0);
  }   
  
  char *blob = malloc (4096);
  int n, nblob = 0;
  int size;

  /* Read into memory */
  
  while ((n = fread (blob+nblob, 1, 4096, fp)) > 0)
  {
    blob = realloc (blob, (nblob+=n)+4096);
  }
  fclose (fp);
  size = nblob;

  /* Alert the operator */
  
  strcpy (indi_image_blob.format, ".fits");
  fprintf (stderr, "Sending %s through INDI", name);

  /* Send */
  
  indi_image_blob.blob = blob;
  indi_image_blob.bloblen = nblob;
  indi_image_blob.size = size;
  indi_image_blob_property.s = IPS_OK;
  IDSetBLOB (&indi_image_blob_property, NULL);
  free (blob);
  return (1);
}


/* End of  INDI functions */

/* Provide filter name for a filter number */

int get_filter_name(int new_filter_number, char new_filter_name[24])
{
  int rflag;
  
  if ( filter_wheel == NO_FILTER_WHEEL )
  {
    strcpy(filter_name,"Open"); 
    return(1);
  }
  
  
  if ( filter_wheel == INT_FILTER_WHEEL )
  {
    rflag = GetCameraFilterName(new_filter_number, new_filter_name);
    return(rflag);
  }
  
  if ( filter_wheel != EXT_FILTER_WHEEL )
  {
    return(0);
  }  
  
  if ( ( new_filter_number >=1 ) && ( new_filter_number <= filter_max ) )
  { 
    strcpy(new_filter_name,filter_label[new_filter_number - 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      */
/* Filter number is the internal number starting from 0      */

int set_filter_name(int flag, 
  int new_filter_number, char *new_filter_name)
{
  int i;
  int rflag;
  
  if ( filter_wheel == NO_FILTER_WHEEL )
  {
    return(0);
  }
  
  
  if ( filter_wheel == INT_FILTER_WHEEL )
  {
    rflag = SetCameraFilterName(flag, new_filter_number, new_filter_name);
    return(rflag);
  }
  
  if ( filter_wheel != EXT_FILTER_WHEEL )
  {
    return(0);
  }  
  
    
  if ( flag > 0 )
  {
    
    /* Save new filter name */
            
    i = new_filter_number;
    if ( (i >= 0) & (i < filter_max) )
    { 
      if ( strlen(new_filter_name) < 21 )
      {
        strcpy(filter_label[i],new_filter_name);
      }
      else
      {
        strcpy(filter_label[i],default_filter_label[i]);
      }
    }
    
  }
  else
  {
    
    /* Restore defaults */
    
    filter_max = FILTER_MAX;
    
    if ( filter_max > 10 )
    {
      filter_max = 10;
    }  
  
    for (i=0; i< filter_max; i++)
    {
      strcpy(filter_label[i],default_filter_label[i]);  
    }
  }
  return(1);
}


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

int set_filter(int filter)
{
  char cmdstr[256];
  /* Respond to request if the filter wheel is available */
  
  int rflag;
  
  if ( filter_wheel == NO_FILTER_WHEEL )
  {
    return(0);
  }
  
  
  if ( filter_wheel == INT_FILTER_WHEEL )
  {
    rflag = SetCameraFilter(filter);
    return(rflag);
  }
  
  if ( filter_wheel != EXT_FILTER_WHEEL )
  {
    return(0);
  } 
 
  /* Correct errors in filter number request */
  
  if (filter < 1 )
  {
    filter = 1;
  }
  if (filter > filter_max )
  {
    filter = 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);
  
  return(1);

}

/* Write ccd temperature to a system file */

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

  fprintf(outfile, "%4.2lf\n", temperature);      
  fclose(outfile);
}

/* Read and parse the initial configuration file */

void read_config(void)
{
  char configstr[121];
  char *configptr = configstr;
  int n;
  char new_filter_name[24];
    
  configfile = (char *) malloc (MAXPATHLEN);
  strcpy(configfile,CONFIGFILE);  
  fp_config = fopen(configfile, "r");
  
  if ( fp_config == NULL )
  {
    fprintf(stderr,"New camera configuration not found.\n");
    fprintf(stderr,"Using default camera parameters.\n");
    return;
  }
  else
  {
    fprintf(stderr,"Camera parameters redefined.\n");
  }
  

  while ( configstr == fgets(configstr,80,fp_config) )
  {
    
    configptr = strstr(configstr,"ccd.frame_count_multi");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%d",&frame_count_multi); 
        if (frame_count_multi < 2)
        {
          frame_count_multi = 2;
          frame_count_max = frame_count_multi;
          fprintf(stderr,"Configuration error: attempt to set multi frame count < 2\n");
        }
        else
        {       
          frame_count_max = frame_count_multi;
          fprintf(stderr,"Multi mode frame count: %d\n",frame_count_multi);
        }
      }  
    }

    configptr = strstr(configstr,"ccd.filter_max");
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%d",&filter_max);
        if (filter_max < 0 )
        {
          filter_max = 0;
          fprintf(stderr,"Configuration error: attempt to set max filter count < 0\n");    
        }
        else
        {
          fprintf(stderr,"Max filter count: %d\n",filter_max);
        }  
      }  
    } 
        
    configptr = strstr(configstr,"ccd.filter_01");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 1, new_filter_name);
        }
        fprintf(stderr,"Filter 1: %s\n",new_filter_name);
      }  
    }
  
    configptr = strstr(configstr,"ccd.filter_02");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 2, new_filter_name);
        }
        fprintf(stderr,"Filter 2: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_03");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 3, new_filter_name);
        }
        fprintf(stderr,"Filter 3: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_04");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 4, new_filter_name);
        }
        fprintf(stderr,"Filter 4: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_05");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 5, new_filter_name);
        }
        fprintf(stderr,"Filter 5: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_06");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 6, new_filter_name);
        }
        fprintf(stderr,"Filter 6: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_07");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 7, new_filter_name);
        }
        fprintf(stderr,"Filter 7: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_08");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 8, new_filter_name);
        }
        fprintf(stderr,"Filter 8: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_09");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 9, new_filter_name);
        }
        fprintf(stderr,"Filter 9: %s\n",new_filter_name);
      }  
    }

    configptr = strstr(configstr,"ccd.filter_10");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(new_filter_name,configptr,21);
        n=strlen(new_filter_name);
        if ((n >0 ) && (n < 21))
        {  
          new_filter_name[n-1]='\0';
          set_filter_name(1, 10, new_filter_name);
        }
        fprintf(stderr,"Filter 10: %s\n",new_filter_name);
      }  
    }
 
    configptr = strstr(configstr,"ccd.base_name");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%s",base_name);
        fprintf(stderr,"Base name: %s\n",base_name);
      }  
    }
     
    configptr = strstr(configstr,"ccd.fits_telescope");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        strncpy(fits_telescope,configptr,64);
        n=strlen(fits_telescope);
        if ((n >0 ) && (n < 64))
        {  
          fits_telescope[n-1]='\0';
        }
        else
        {
          fits_telescope[0]='\0';
        }        
        fprintf(stderr,"FITS telescope: %s\n",fits_telescope);
      }  
    } 
    configptr = strstr(configstr,"ccd.ccd_script");    
    if ( configptr != NULL)
    {
      configptr = strstr(configstr,"=");
      if ( configptr != NULL)
      {
        configptr = configptr + 1;
        sscanf(configptr,"%s",ccd_script);
        fprintf(stderr,"External script %s\n",ccd_script);
      }  
    }


  }
  
  fclose(fp_config);

}


