/* program telcon.c - the Telescope Control code
   started October, 1997 by Scot J. Kleinman 
   and Anders Johannesson

   telcon.c is basically a wrapper for telcon.tcl which
   does all the GUI work. Here, we call up telcon.tcl
   and define some new commands for it to use.
   the main event loop is run in telcon.tcl- all we do here is 
   keep track of some variables and define new commands for telcon.tcl

   1.00  1Dec97  SJK - placed into official operation  at BBSO 1Dec97
                       needs pdprp v1.40
 
   1.01  1Dec97 SJK - name changes
                      needs lbc v1.41

   1.02         SJK - adds point and click on sun to move telescope feature

 */

#include "soleph.h" /* Header file for BBSO ephemeris programs */

#include<stdio.h>
#include<tcl.h>
#include<tk.h>      /*for Tcl/Tk GUI*/
/* these 4 are for serial port control */
#include<unistd.h>  /* UNIX standard function defs */
#include<fcntl.h>   /* File control definitions */
#include<errno.h>   /* Error number definitions */
#include<termios.h> /* POSIX terminal control definitions */

 /*initialize serial port for PC Guider*/
int initialize_serial(void);

 /*read a string from a serial port*/
int serial_in (int sfd, char *string);

  /*send a string out a serial port*/
int serial_out (int sfd, char *string);

 /*initialize tcl/tk GUI*/
int tcl_initialize(Tcl_Interp *interp, char *scope);

 /*send the Tcl/Tk script the telescope we are controlling*/
int t_telltel(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

 /*initialize and/or convert coords in a coordinate structure*/
int t_init_struct(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

 /*update coords in a coordinate structure*/
int t_update(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

 /*update mode of coordinate update i.e. flexure, rotation, or both*/
int t_upfor(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

 /*send move commands to telescope through PC Guider*/
int t_move(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

 /*send user command to PC Guider and return response*/
int t_pcg(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

 /*get coord structure from data base with supplied name*/
Cstruct *getcoords(char name[]);

Tcl_Interp *Interp;     /*the Tcl/Tk GUI interface*/
Cstruct Coords;         /*the current coordinate structure*/
EphType  Eph;           /*the current Ephemeris*/

int Scope;              /*26 or 10 - telescope we are controlling*/
int Sfd;                /*serial file descriptor for PC Guider serial port*/

double H_ref=-2.0;       /*reference Hour Angle for Flexure - hour angle for which
                          we assume flexure is zero - this will have to be done betterXXX*/
double Flex_LUT [2][145]; /*The global flexure array- will get set to the
                            corresponding array for the 10 or 26" telescope*/

/*MAIN*/
main(int argc, char *argv[])
    {
    int err;

    if (argc<2)
        {
        fprintf(stderr,"Need one argument: the scope (10 or 26)\n");
        exit(1);
        }
    Scope = atoi(argv[1]);

    /*initialize serial port*/
    err=initialize_serial();    
    if (err==-1)
        {
        printf("Can't open serial port for PC Guider communications.\n");
        printf("Make sure you are running on Ingrid and serial port is connected\n");
        exit (1);
        /*ZZZ uncomment that when running for real*/
        }

    /*Initialize flexure arrays*/
    if (Scope==10)
        err=get_flexure_LUT(TELESCOPE_10INCH,Flex_LUT);
    else
        err=get_flexure_LUT(TELESCOPE_26INCH,Flex_LUT);
    if (err==FLEX_FILE_ERR)
        {
        printf("I can't read the Flexure data.\n");
        printf("It's probably not there or in the wrong format.\n");
        exit (1);
        }

    /*set up Tcl/Tk GUI*/
    Interp=Tcl_CreateInterp();
 
    if (err=tcl_initialize(Interp,argv[1]))
        return(err);

    /*since I have an update loop in the .tcl file, I never reach
      this part of the program unless I get an err above or maybe
      when I quit the GUI*/

    return(0);

    }

/*INITIALIZE_SERIAL*/
/* set up the serial port to talk to the PC Guider
   it is connected to /dev/ttyC7
   called by main()
   calls serial_in()
   returns 0 if ok
          -1 if can't open serial port
*/
int initialize_serial()
    {
    struct termios sopts; /*serial port options*/
    char buffer[80]; /*used to flush input serial buffer*/

    if ((Sfd=open("/dev/ttyC7",O_RDWR | O_NOCTTY | O_NDELAY)) == -1)
        return(-1);

    /*set serial parameters*/
    /*make read return 0 bytes if none available*/
    fcntl(Sfd, F_SETFL, FNDELAY);

    /*First, get current parameters*/
    tcgetattr(Sfd, &sopts);

    /*turn off echo*/
    sopts.c_lflag &= ~(ECHO);

    /*set canonical (line at a time mode)*/
    sopts.c_lflag |= ICANON;

    /*maps input CR to NL*/
    sopts.c_iflag |= ICRNL;
    sopts.c_iflag &= ~(IGNCR);
    sopts.c_iflag &= ~(INLCR);

    /*accept \r as EOL in addition to \n*/
    sopts.c_cc[VEOL]='\r';

    /*turn on output processing so other o_flags have an effect*/
    sopts.c_oflag |= (OPOST);
    /*allow me to send a CR instead of NL if I want*/
    sopts.c_oflag &= ~ONLRET;
    sopts.c_oflag &= ~ONLCR;

    sopts.c_oflag &= ~OCRNL;

    /*now baud rates*/
    cfsetispeed(&sopts,B9600);
    cfsetospeed(&sopts,B9600);
    /* now set to 8N1 */
    sopts.c_cflag &= ~CSIZE;
    sopts.c_cflag |= CS8;
    sopts.c_cflag &= ~PARENB;
    sopts.c_cflag &= ~CSTOPB;

    /*turn off hardware flow control*/
    sopts.c_cflag &= ~CRTSCTS;
    /*turn off software flow control on input and output*/
    sopts.c_iflag &= ~IXON;
    sopts.c_iflag &= ~IXOFF;

    /*make these settings active now*/
    tcsetattr(Sfd, TCSANOW, &sopts);

    /*Clear/flush the input buffer*/
    while(serial_in(Sfd,buffer)>0);

    return(0);
    }

/*SERIAL_IN*/
/* function serial_in reads a string from the given
   serial port (described by its file descriptor).
   it will read a max of 80 bytes
   returns # bytes read or the error number
   called by serial_initialize()
             t_move()
             t_getpos()
             t_pcg()
*/
int serial_in (int sfd, char *string)

    {
    int n=0;

    if ((n=read(sfd,string,80))>0)
        string[n]='\0';
    /*uncomment the previous two lines and delete
      the next two for real operations*/
    /*strcpy(string,"OK\n");*/
    /*n=4;*/
    return (n);
    }

/*SERIAL_OUT*/
/* function serial_out sends the given string out the
   given serial port (described by its file descriptor)
   returns the # of bytes written or the err value
   called by t_move()
             t_getpos()
             t_pcg()
*/
int serial_out (int sfd, char *string)

    {
    int n;
    int err;
    char s[80];

    /*n=strlen(string);*/
    /*printf("SERIAL_OUT:%s\n",string);*/
    /*err=10;*/
    /*use the above 3 lines for testing and the below 2 for real*/
    if ((err=write(sfd,string,n))<0)
        fprintf(stderr,"serial port write failed: %s\n",string);

    return(err);
    }

/*TCL_INITIALIZE*/
/* procedure tcl_initialize starts up the Tcl/Tk GUI. The
   code for which is in ./telcon.tcl
   called by main()
   returns -1 if failed
           0 otherwise
*/
int tcl_initialize(Tcl_Interp *interp, char *scope)

    {

    if (Tcl_Init(interp) == TCL_ERROR)
         {
         fprintf(stderr, "Tcl_Init failed: %s\n",interp->result);
         return(-1);
         }

    if (Tk_Init(interp) == TCL_ERROR)
         {
         fprintf(stderr, "Tk_Init failed: %s\n",interp->result);
         return(-1);
         }
    
    /*create new commands which Tcl/Tk GUI will call*/
    /*initialize telecope structure and /or  convert coords*/
    Tcl_CreateCommand (interp, "Cinits", (Tcl_CmdProc *) t_init_struct,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);

    /*return value of Scope- what telescope we are controlling*/
    Tcl_CreateCommand (interp, "Ctelltel", (Tcl_CmdProc *) t_telltel,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);

    /*update coordinates if necessary*/
    Tcl_CreateCommand (interp, "Cupdate", (Tcl_CmdProc *) t_update,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);

    /*update the update mode - Flexure, Rotation or both*/
    Tcl_CreateCommand (interp, "Cupfor", (Tcl_CmdProc *) t_upfor,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);

    /*issue move telescope command via PC Guider*/
    Tcl_CreateCommand (interp, "Cmove", (Tcl_CmdProc *) t_move,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
    
    /*issue user command to PC Guider and return response*/
    Tcl_CreateCommand (interp, "Cpcg", (Tcl_CmdProc *) t_pcg,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
    
    /*tell Tcl/Tk which file to execute*/
    Tcl_EvalFile(interp,"./telcon.tcl");

    return(0);
    }

/*T_TELLTEL*/
/*simply returns the value of Scope to the Tcl/Tk routine*/
int t_telltel (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    {
    sprintf(interp->result,"%d",Scope);
    return (TCL_OK);
    }

/*T_INIT_STRUCT*/
/*this is a wrapper for init_struct() in init_struct.c which
  initializes and/or conerts coordinates in a coordinate 
  structure defined in soleph.h
  called by telcon.tcl
  calls init_struct()
*/
int t_init_struct (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    {
    /*we expect 5 passed paramaters, region name, 2 coordinates, a flag for coordinate
      type and one for update type*/
    TargetType ntarget;      /*new target structure*/
    int err;                 /*return value of init_struct*/
    Cstruct *lcoords;        /*local coordinate sturcture
                               either Coords or Dbcoords*/

    strcpy(ntarget.name,argv[1]);
    ntarget.coord1 = atof(argv[2]);
    ntarget.coord2 = atof(argv[3]);
    ntarget.conversion_mode = atoi(argv[4]);
    ntarget.update_mode = atoi(argv[5]);

    if (strncmp(argv[1],"current",7)==0)
        lcoords=&Coords;
    else
        lcoords=getcoords(argv[1]);

    err=init_struct(lcoords,ntarget,&Eph,INIT_ON);

    if (atoi(argv[4])==CART)
        sprintf(interp->result,"%d %9.5f %9.5f %6.2f %5.2f %7.2f %8.2f",
                err,lcoords->rlong,lcoords->rlat,Eph.p,Eph.b,Eph.l0,Eph.rad);
    else
        sprintf(interp->result,"%d %7.1f %7.1f %6.2f %5.2f %7.2f %8.2f",
                err,lcoords->xpos,lcoords->ypos,Eph.p,Eph.b,Eph.l0,Eph.rad);

    return(TCL_OK);
    }

/*T_UPDATE*/
/*this is a wrapper for update() which is in update_struct.c which
  updates a coordinate structure for solar rotation and/or telescope
  flexure as demanded by the user
  called by telcon.tcl
  calls update()
*/
int t_update (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    {
    /*we expect only one argument- the region name*/
    Cstruct *lcoords;        /*local coordinate sturcture
                               either Coords or Dbcoords*/
    int ierr;                /*return value from update()*/

    if (strncmp(argv[1],"current",7)==0)
        lcoords=&Coords;
    else
        lcoords=getcoords(argv[1]);

    ierr=update(lcoords,H_ref,Flex_LUT,&Eph);

    sprintf(interp->result,"%d %9.5f %9.5f %7.1f %7.1f %5.1f %5.1f %7.2f",
            ierr,lcoords->rlong, lcoords->rlat,lcoords->xpos,lcoords->ypos,
            lcoords->f_offx, lcoords->f_offy,Eph.l0);
   
    return(TCL_OK);
    }

/*GETCOORDS*/
/*get coords gets a Cstruct from the database corresponding to the name
  name
  called by t_init_struct
*/
Cstruct *getcoords(char *name)
    {
    /*this is bogus for now- return the global Cstruct*/
    return(&Coords);
    }

/*T_UPFOR*/
/*set the coord structure flag determining how the coordinated will be updated
  called by telcon.tcl
  calls init_struct()
*/
int t_upfor (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    {
    /*we expect three arguments: name of region, type of update to change (
      either "flex" or "rot") and a flag saying to turn it on or off (1/0)*/
    Cstruct *lcoords;        /*local coordinate sturcture
                               either Coords or Dbcoords*/
    TargetType ntarget;      /*target structure we will use this to re-setup lcoords*/
    int err;                 /*return value (unchecked) form init_struct*/
    int rup, fup;            /*current values of rupdate and fupdate*/

    if (strncmp(argv[1],"current",7)==0)
        lcoords=&Coords;
    else
        lcoords=getcoords(argv[1]);

    ntarget.coord1 = lcoords->xpos;
    ntarget.coord2 = lcoords->ypos;
    ntarget.conversion_mode = CART;

    if (strncmp(argv[2],"flex",4)==0)
        {
        /*I am updating for flexure*/
        rup=lcoords->rupdate;
        fup=atoi(argv[3]);
        }
    else
        {
        /*I am updating for rotation*/
        fup=lcoords->fupdate;
        rup=atoi(argv[3]);
        }

    ntarget.update_mode=rup+2*fup;
    err=init_struct(lcoords,ntarget,&Eph,INIT_OFF);

    return (TCL_OK);
    }

/*T_MOVE*/
/*t_move sends move commands to the PC Guider to move the telescope
  called by telcon.tcl
  calls serial_out()
        serial_in()
*/
int t_move (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    {
    /*I expect two arguments: X and Y to move telescope to*/
    double x,y;
    char s[80];   /*command string to send to PC Guider*/
    int serr;     /*return value from serial routines*/
    char rs[80];  /*return string from serial_in*/
    int n;        /*number of bytes read/returned by serial_in*/
    char junk[80]; /*garbage scanned from the return string*/
    int en=0;       /*the error number from rs if PC-Guider returns an error*/

    x=atof(argv[1]);
    y=atof(argv[2]);

    sprintf(s,"atma %2dRA %7.1f\n",Scope,x);
    serr=serial_out(Sfd,s);
    /*XXX do something with serr*/

    /*wait for command response*/
    while ((serr=serial_in(Sfd,rs))<=0); /*XXXadd a timeout?*/
    /*set no error for now -let ifs below fill interp if there is an error*/
    sprintf(interp->result,"%d %s",0,"OK");
    if (strncmp("ERROR",rs,5)==0)  /*error from PC GUIDER*/
        {
        fprintf(stderr,"error issuing %2d RA move command:%s\n",Scope,rs);
        /*get error code from rs*/
        sscanf(rs,"%s %d",junk,&en);
        sprintf(interp->result,"%d %s",en,rs);
        }

    sprintf(s,"atma %2dDEC %7.1f\n",Scope,y);
    serr=serial_out(Sfd,s);
    /*XXX do something with serr*/
    /*wait for command response*/
    while ((serr=serial_in(Sfd,rs))<=0); /*XXXadd a timeout?*/
    if (strncmp("ERROR",rs,5)==0)  /*error from PC GUIDER*/
        {
        fprintf(stderr,"error issuing %2d DEC move command:%s\n",Scope,rs);
        /*get error code from rs*/
        sscanf(rs,"%s %d",junk,&en);
        sprintf(interp->result,"%d %s",en,rs);
        }
    
    return(TCL_OK);
    }

/*T_PCG*/
/*sends user command to PC-GUIDER for testing and returns response
  called by telcon.tcl
  calls serial_out()
        serial_in()
*/
int t_pcg (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    {
    /*I expect only one parameter- the command*/
    char command[80];  /*the user entered command (from .tcl)*/
    char response[80]; /*the command response*/
    int serr;

    strcpy(command,argv[1]);
    command[strlen(command)]='\n';
    /*send command*/
    serr=serial_out(Sfd,command);
    /*wait for response*/
    while ((serr=serial_in(Sfd,response))<=0); /*XXXadd a timeout?*/
    /*return with response*/
    sprintf(interp->result,"%s",response);
    return(TCL_OK);
    }
