/* program pdprp.c - the PDP replacement project software
   started March, 1997 by Scot J. Kleinman 
   on a Linux PC (i486; kernel 2.0.27; Redhat 4.1)
   with a Computer Boards Inc. CIO-DIO24 digital IO board.

   it starts pdprp.tcl and receives calls from it

   Program must be compiled  with a -O (or -O2 or higher) 
   for the outb and inb commands to work.
   It needs the tk and tcl libraries for Tcl/Tk stuff.
   Here is the command line for compiling:
   cc pdprp.c -o pdprp -O2 -I/usr/X11R6/include/ -L/usr/X11R6/lib/ -ltk -ltcl -ldl -lX11 -lm -lc

   ioperm needs to be run as root.  so the executable should
   be owned by root and chmod'ed to 4711.

   All global variables, structures, etc. start with a capital
   letter. 

   Variables which are #defined are in all caps.

   Note current limits: bench name 9 chars; camera name 31 chars
                        host computer name 9 chars;
   and see the #defines below

 */

#include<stdio.h>
#include<stdlib.h> /*atoi()*/
#include<time.h>  /*for the time_t (long) and time() defs*/
#define time() time(NULL)
#include<asm/io.h>  /*needed for outb()*/
#include<unistd.h>  /*needed for ioperm() & g/setuid*/
#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 */



#define MAXBENCH 16  /*max # of total benches*/
#define MAX26BEN 4   /*max # of benches allowed in 26" cycle: 26E1/2,26C,26W,SF
                       change if ADD 26E1 AND 26E2 or the SJ/SF synch stuff*/
#define MAX10BEN 3   /*max # of benches allowed in 10" cycle:10E,10C,10W*/
#define BASE 0x300UL /*base address of DIO board*/
#define PORTS 24     /*Number of i/o ports (each one with 8 bits)*/

#define LBA 0        /*error code for light not available error*/
#define MAXEC 1      /*total # of error codes listed above*/

                      /*The next group are for Serial port assignments*/
#define MACSPORT 0    /*Sfd index for Macintosh serial communication*/

                      /*The next group are input serial command codes*/
#define RFL 1         /*Ready for light*/
#define DWL 2        /*Done With Light -used to be lightbeam released*/

struct Bench_s 
    {
    int bnum;         /*bench code, ex. 7*/
    int lnum;         /*LBC number for bench- this is what is sent by LBC*/
    char bench[9];    /*name of Bench, ex. 26W*/
    char camera[31];  /*camera name, ex. OSL*/
    int ct;           /*cycle time*/
    int dt;           /*dwell time*/
    char host[9];     /*host computer*/
    int lr;           /*light released: 1=yes; 0=no*/
    };
typedef struct Bench_s Bstruct;

struct Io_s
    {
    int port;                                 /*port #:0=A,1=B,...*/
    unsigned char bit;                        /*bit for reading/writing*/
    };
typedef struct Io_s Iostruct;

  /*set up ports to be accessed*/
int intialize_io(void);                  

  /*set up and start Tcl/Tk GUI*/
int tcl_initialize(Tcl_Interp *interp);

 /*open a serial port*/
int open_sport(char *terminal);

  /*set up i/o port/pin assignments*/
void p_initialize(void);                

  /*read bench.params bench assignment file*/
int read_bdata(void);      

  /*build bench database*/
void assign_dbase(int bench_num, char *name); 

  /*start bench cycling - argv[1] contains scope*/
int start_cycling(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

  /*stop bench cycling - argv[1] contains scope*/
int stop_cycling(ClientData cd, Tcl_Interp *interp, int argc, char **argv);

  /*go to specified bench*/
int goto_bench(Bstruct *telescope[], time_t time[], int scope, int bench);

  /*deal with cycling*/
void check_cycle(void); 

  /*ready to switch?*/
int check_tel(Bstruct *telescope[], time_t time[], int current_bench,
              int NumXXben);     

  /*check for data computer release of light at current bench*/
int light_released(Bstruct *telescope[], int bench);

  /*check host computer for ready for light signal*/
int ready_for_light(Bstruct *telescope[], int bench);

  /*set first nibble of byte*/
int nib1(unsigned char *byte, unsigned char value);

  /*set 2nd nibble of byte*/
int nib2(unsigned char *byte, unsigned char value);

  /*return values of a given bit from a given port*/
int bitcheck(int port, unsigned char bit);

  /*pop up an error box in Tcl/Tk GUI with a 3-line message*/
void error_popup(int numstr);

  /*update the last error box only in the GUI (for ignored errors)*/
void error_update(int numstr);

  /*build up error Ignore array*/
int error_ignore (ClientData cd, Tcl_Interp *interp, int argc, char **argv);

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

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

  /*get and parse a command from the serial port*/
int get_serial(int sport); 

  /*form the serial output string saying light is there*/
void form_sout_string(int bench, char *string);

Bstruct Bench[MAXBENCH];    /*bench name and timing information*/
Bstruct *Bench26[MAX26BEN]; /*active 26" benches*/
Bstruct *Bench10[MAX10BEN]; /*active 10" benches*/

Iostruct Lbcsig[MAXBENCH];  /*port/pin assignments for LBC OK signal*/

time_t Time26[MAX26BEN];           /*timers for each 26" bench*/
time_t Time10[MAX10BEN];           /*timers for each 10" bench*/

int B26e1 = 0;  /*bench # assignments*/
int B26e2 = 0;  /*assigned in assign_dbase()*/
int B26c  = 0;  /*which is called by read_bdata()*/
int B26w  = 0;
int Bsj    = 0;
int Bsf    = 0;
int B10e   = 0;
int B10w   = 0;
int B10c   = 0;

int Num26ben;       /*Number of actuve 26" benches*/
int Num10ben;       /*Number of active 10" benches*/
int Cycling26=0;    /*1=Cycling 26" benches; 0=not*/
int Cycling10=0;    /*1=Cycling 10" benches; 0=not*/
int On26=0;         /*Current 26" bench*/
int On10=0;         /*Current 10" bench*/
unsigned char Obyte[PORTS];    /*array of output bytes 0->portA, 1->portB,...*/

char Tclerrs[10][68];          /*array of error strings for GUI Error box*/
int Ignore[MAXEC][MAXBENCH]; /*hold error codes and benches to ignore*/

unsigned long Base = BASE;      /*base address of the DIO board*/
unsigned long PortA1 = BASE+0;   /*address of port A1 on DIO board*/
unsigned long PortB1 = BASE+1;   /*address of port B1 on DIO board*/ 
unsigned long PortC1 = BASE+2;   /*address of port C1 on DIO board*/
unsigned long Control1 = BASE+3; /*control address for ports 1 on DIO board*/

int Sfd[8];                      /*serial port file descriptors*/

Tcl_Interp *Interp;     /*the Tcl/Tk GUI interface*/

time_t Tzero;  /*temporary*/

main()
    {
    int err;        /*prcodedure return value*/
    int now; 
    int i,j;

    /*Initialize arrays*/
    for (i=0; i<=MAXEC;i++)
        for (j=0;j<=MAXBENCH;j++)
            Ignore[i][j]=0;
 
    now=time();

    /* set up IO ports */
    if ((err=initialize_io())!=0)
        return(err);
    /* read in data now, even though start will read it in later.
       this is just in case user quits without starting*/ 
    if ((err=read_bdata())!=0)
        return(err);

    /*set up Tcl/Tk GUI*/
    Interp=Tcl_CreateInterp();
    if (err=tcl_initialize(Interp))
        return(err);

    Tzero=time();
  
    /*Begin main event loop*/
      while(1)
        {
        if (Cycling26 || Cycling10)
            check_cycle();
        Tcl_GlobalEval(Interp,"update\n");
        }

    return(0);

    }

/* function intialize_io sets up the ioports for access 
  called by main()
*/
int initialize_io()

    {
    int err;
    int i;
    uid_t user; /*current user id*/
    struct termios sopts; /*serial port options*/
    char term[11];

    /*give permission to acess IO ports
      0x300 is base port address
      +0 = Port A1
      +1 = Port B1
      +2 = Port C1
      +3 = Control1
      +4 = Port A2
      ...
    */

    /* ioperm must have root permission to run sucessfully*/
    /*first store current user id - then su to root*/
    user=getuid();
    setuid(0);

    err=ioperm(Base,4,1);

    /*go back to original user*/
    setuid(user);

    if (err!=0)     /*error in ioperm call*/
        {
        printf("ioperm returned %d.\n",err);
        printf("The program probably does not have root permission,\n");
        printf("required by ioperm.\n");
        return(err);
        }

    /* now set up control for I/O modes:Port A1&B1 are output; C1 is input*/
    outb(137,Control1);

    /* intitilize global array of output bytes */
    for (i=0;i<24;i++)
        Obyte[i]=0;

    /* Now set up serial ports */

    for (i=0;i<8;i++)
        {
        sprintf(term,"/dev/ttyC%d\0",i);
        if ((Sfd[i] = open_sport(term))==-1)
            return(-1);

        /*make read return 0 bytes if none available*/
        fcntl(Sfd[i], F_SETFL, FNDELAY);

        /* set some port parameters */
        tcgetattr(Sfd[i], &sopts);
        /*turn off echo*/
        sopts.c_lflag &= ~(ECHO);
        /*apparently handles \n correctly*/
        sopts.c_iflag |= (IGNCR);
        /*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;
 
        /*now make these settings active now*/
        tcsetattr(Sfd[i], TCSANOW, &sopts);
        }

    return (0);
    }

/* function open_sport opens the given serial port and
   returns the file descriptor to it
   called by initialize_io()
*/
int open_sport(char *term)

    {
    int fd; /*file desriptpr*/

    fd = open (term,O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd==-1)
        fprintf(stderr,"open_sport: Unable to open %s - %s\n",
                        term,strerror(errno));
    return (fd);
    }

/* procedure tcl_initialize starts up the Tcl/Tk GUI. The
   code for which is in ./pdprp.tcl
   called by main()
*/
int tcl_initialize(Tcl_Interp *interp)

    {

    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*/
    /*This one starts the cycling*/
    Tcl_CreateCommand (interp, "Cstart", (Tcl_CmdProc *) start_cycling,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
    /*This one stops the cycling*/
    Tcl_CreateCommand (interp, "Cstop", (Tcl_CmdProc *) stop_cycling,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);
    /*This one builds up the error Ignore array*/
    Tcl_CreateCommand (interp, "Cignore", (Tcl_CmdProc *) error_ignore,
                       (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL);

    /*tell Tcl/Tk which file to execute*/
    Tcl_EvalFile(interp,"./pdprp.tcl");

    /*update the GUI display which in this case shows the interface*/
    Tcl_GlobalEval(interp,"update\n");

    return(0);
    }

/* procedure p_initialize sets fills the global arrays with port and
  pin assignments for I/O
  called by read_bdata();
*/
void p_initialize()

    {
    /*Lbcsig[] holds what port and bit to use to look for the LBC
      ok when it is done moving to a bench*/
    Lbcsig[B26e1].port = PortC1;
    Lbcsig[B26e1].bit = 0;
    Lbcsig[B26e2].port = PortC1;
    Lbcsig[B26e2].bit = 1;
    Lbcsig[B26c].port = PortC1;
    Lbcsig[B26c].bit = 2;
    Lbcsig[B26w].port = PortC1;
    Lbcsig[B26w].bit = 3;
    Lbcsig[Bsf].port = PortC1;
    Lbcsig[Bsf].bit = 4;
    Lbcsig[B10e].port = PortC1;
    Lbcsig[B10e].bit = 5;
    Lbcsig[B10c].port = PortC1;
    Lbcsig[B10c].bit = 6;
    Lbcsig[B10w].port = PortC1;
    Lbcsig[B10w].bit = 7;
    }

/* procedure read_bdata reads the bench assignment and timing information
   from the bench.params file.
   Returns values are:
   0  OK
   -1 can't open bench.params
   -2 bench.params values are inconsistent
   -3 too many 26" bench assignments
   -4 too many 10" bench assignments

   called by start_cycling()
   callas assign_dbase()
   calls p_initialize()
*/
int read_bdata()
 
    {
    FILE *ifp;      /*file pointer with bench info*/
    char s[81];     /*input string*/
    int bnum;       /*bench code*/
    int lnum;       /*LBC number*/
    char bname[9];  /*bench name*/
    char bcam[31];  /*camera name*/
    char bhc[9];    /*bench host computer*/
    int bct, bdt;   /*bench cycle and dwell times*/
    int i;          /*counter*/
    int i26,i10;    /*26" and 10" bench assignment counters*/
    int on;         /*bench on flag: 1=yes; 0=no*/

    /*initialize bench array*/
    for (i=0;i<MAXBENCH;i++)
        Bench[i].ct=Bench[i].dt=0;

    /* read in bench information form bench.params*/
    ifp=fopen("bench.params","r");
    if (ifp==NULL)
       {
       printf("cannot open file bench.params.\n");
       return(-1);
       }

    while (fgets(s,81,ifp)!=NULL)
       if (s[0]!='#')
           {
           sscanf(s,"%d %d %d %s %s %d %d %s",&bnum,&lnum,&on,bname,bcam,&bct,&bdt,bhc);
           assign_dbase(bnum,bname);
           Bench[bnum].bnum=bnum;
           Bench[bnum].lnum=lnum;
           strcpy(Bench[bnum].bench,bname);
           strcpy(Bench[bnum].camera,bcam);
           Bench[bnum].ct=bct;
           Bench[bnum].dt=bdt;
           strcpy(Bench[bnum].host,bhc);
           if (!on) /*if bench not on set ct and dt to 0*/
               {
               Bench[bnum].ct=0;
               Bench[bnum].dt=0;
               }
           }

    /*check bench.params assignments for inconsitencies*/
    if ((Bench[B26e1].ct!=0)&&(Bench[B26e2].ct!=0))
        {
        printf("Can't have both 26E benches active at once.\n");
        return(-2);
        }
    for (i=0;i<MAXBENCH;i++)
        if (Bench[i].dt>Bench[i].ct)
            {
            printf("Bench %s has dwell time %d > cycle time %d.\n",
                    Bench[i].bench,Bench[i].dt,Bench[i].ct);
            return(-2);
            }
    /*build up Bench26 and Bench10 structures -modify this if change B26e1/e2
      and sj/sf*/

    i26=i10=0;
    if (Bench[B26e1].ct)
        Bench26[i26++]=&Bench[B26e1];
    else if (Bench[B26e2].ct)
        Bench26[i26++]=&Bench[B26e2];
    if (Bench[B26c].ct)
        Bench26[i26++]=&Bench[B26c];
    if (Bench[B26w].ct)
        Bench26[i26++]=&Bench[B26w];
    if (Bench[Bsf].ct)
        Bench26[i26++]=&Bench[Bsf];
    if (i26>MAX26BEN)
        {
        printf("Too many 26\" bench assignments.\n");
        return(-3);
        }
    Num26ben = i26;

    if (Bench[B10e].ct)
        Bench10[i10++]=&Bench[B10e];
    if (Bench[B10c].ct)
        Bench10[i10++]=&Bench[B10c];
    if (Bench[B10w].ct)
        Bench10[i10++]=&Bench[B10w];
    if (i10>MAX10BEN)
        {
        printf("Too many 10\" bench assignments.\n");
        return(-4);
        }
    Num10ben = i10;

    /*setup pin assignments in global arrays*/
    p_initialize();

    for (i=0;i<Num26ben;i++)
       printf("%d %d %s\n",i,Bench26[i]->bnum,Bench26[i]->bench);
    for (i=0;i<Num10ben;i++)
       printf("%d %d %s\n",i,Bench10[i]->bnum,Bench10[i]->bench);
    return(0);
    }

/* procedure assign_dbase builds up the global data base of bench assignments
   called by read_bdata()
*/
void assign_dbase(int num, char *name)

    {
    if (!strncmp(name,"26e1",4))  /*if name=="26E1"*/
        B26e1=num;
    else if  (!strncmp(name,"26e2",4))
        B26e2=num;
    else if  (!strncmp(name,"26c",3))
        B26c=num;
    else if  (!strncmp(name,"26w",3))
        B26w=num;
    else if  (!strncmp(name,"sj",2))
        Bsj=num;
    else if  (!strncmp(name,"sf",2))
        Bsf=num;
    else if  (!strncmp(name,"10e",3))
        B10e=num;
    else if  (!strncmp(name,"10c",3))
        B10c=num;
    else if  (!strncmp(name,"10w",3))
        B10w=num;

    return;
    }

/* procedure start_cycling starts the bench cycling
   called by the pdprp.tcl Tcl/Tk GUI start command
   calls read_bdata(), goto_bench(), and perhaps error_popup
*/
int start_cycling(ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    /*this is how Tcl/Tk passes parameters. I only expect one arg: scope*/
   
    {
    int err;  
    int i;
    int scope;   /*10 or 26*/
    time_t now;  /*current time*/
    char s[80];  /*command for Tcl/Tk GUI*/
    int numxxben; /*either Num26ben or Num10ben*/
    Bstruct **bench; /*either points to Bench26 or Bench10*/
    time_t *ttime; /*either points to Time26 or Time10*/
    int gberr;    /*return value from goto_bench*/

    /*read bench information and initialize*/
    if ((err=read_bdata())!=0)
        return(err);

    scope = atoi(argv[1]);

    if (scope==26)
        {
        if (Num26ben>0)
            Cycling26 = 1;
        numxxben = Num26ben;
        bench = Bench26;
        ttime = Time26;     
        On26=0;
        }
    else
        {
        if (Num10ben>0)
            Cycling10 = 1;
        numxxben = Num10ben;
        bench = Bench10;
        ttime = Time10;     
        On10=0;
        }
    

    now=time()+1;    /*Assume all this stuff takes ~1sec*/
    /*start timers*/
    for(i=0;i<numxxben;i++)
        ttime[i]=now+bench[i]->ct;

    /*reset light release signal*/
    bench[0]->lr=0;

    /*go to initial bench (make this selectable later)*/
    if ((gberr=goto_bench(bench, ttime, scope, 0))!=0)
        {
        printf("start_cycling: scope=%d, gberr=%d\n",scope,gberr);
        sprintf(Tclerrs[0],"start_cycling: Error");
        sprintf(Tclerrs[1],"bench: %s - goto_bench error:", bench[0]->bench);
        switch (gberr)
            {
            case -1: strcpy(Tclerrs[2], "Light Beam Available not returned");
                     sprintf(Tclerrs[3],"%d %d",bench[0]->bnum,LBA);
                     if (Ignore[LBA][bench[0]->bnum])
                         error_update(3);
                     else
                         error_popup(3);
                     break;
            }
        }
    
    return (TCL_OK);
    }

/* procedure stop_cycling stops the bench cycling
   and send light to "home" bench (same as start_cycling starts with
   called by the pdprp.tcl Tcl/Tk GUI stop command
*/ 
int stop_cycling(ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    /*this is how Tcl/Tk passes parameters. I only expect one arg: scope*/

    {
    int scope;  /*10 or 26*/
    char s[80]; /*command to send to Tcl/Tk GUI*/
    int gberr;  /*return value from goto_bench*/
    char tclerrs1[80],tclerrs2[80]; /*error strings for GUI error box*/

    scope = atoi(argv[1]);

    if ((scope==26)&&(Cycling26))
        {
        Cycling26=0;
        sprintf(s,"bcolor .f26.f%s.fcb.cb1 normal\n",Bench26[On26]->bench);
        Tcl_GlobalEval(Interp,s); 
        /*go to initial bench (make this selectable later)*/
        if ((gberr=goto_bench(Bench26, Time26, 26, 0))!=0)
            {
            printf("stop_cycling: scope=26, gberr=%d\n",gberr);
            sprintf(Tclerrs[0],"stop_cycling: Error");
            sprintf(Tclerrs[1],"bench: %s - goto_bench error:", Bench26[0]->bench);
            switch (gberr)
                {
                case -1: strcpy(Tclerrs[2], "Light Beam Available not returned");
                         sprintf(Tclerrs[3],"%d %d",Bench26[0]->bnum,LBA);
                         if (Ignore[LBA][Bench26[0]->bnum])
                             error_update(3);
                         else
                             error_popup(3);
                         break;
                }
            }
        sprintf(s,"bcolor .f26.f%s.fcb.cb1 green\n",Bench26[0]->bench);
        Tcl_GlobalEval(Interp,s); 
        On26=0;
        }
    else if (Cycling10)
        {
        Cycling10=0;
        sprintf(s,"bcolor .f10.f%s.fcb.cb1 normal\n",Bench10[On10]->bench);
        Tcl_GlobalEval(Interp,s); 
        /*go to initial bench (make this selectable later)*/
        if ((gberr=goto_bench(Bench10, Time10, 10, 0))!=0)
            {
            printf("stop_cycling: scope=10, gberr=%d\n",gberr);
            sprintf(Tclerrs[0],"stop_cycling: Error");
            sprintf(Tclerrs[1],"bench: %s - goto_bench error:", Bench10[0]->bench);
            switch (gberr)
                {
                case -1: strcpy(Tclerrs[2], "Light Beam Available not returned");
                         sprintf(Tclerrs[3],"%d %d",Bench10[0]->bnum,LBA);
                         if (!Ignore[LBA][Bench10[0]->bnum])
                             error_popup(3);
                         else
                             error_update(3);
                         break;
                }
            }
        sprintf(s,"bcolor .f10.f%s.fcb.cb1 green\n",Bench10[0]->bench);
        Tcl_GlobalEval(Interp,s); 
        On10=0;
        }
    return (TCL_OK);
    }

/* procedure goto_bench tells the LBC to go to the bench
   specified by the index bench in the telescope structure tel
   returns:
   0 if ok
   -1 if LA (light available) signal not received
   -2 if serial input from host not read correctly
   -3 if host computer not ready
   called by start_cycling() and check_cycle()
*/
int goto_bench(Bstruct *tel[], time_t timer[], int scope, int bench)

    { 
    /*LBC is controlled through PortB1 and hence Obyte[1]*/
    /*10" takes bits 0-3; 26" uses bits 4-7*/

    int dwell;  /*dwell time for bench*/
    int i; /*just a shortcut to avoid messy code here*/
    char s[80]; /*command string sent to Tcl/Tk*/
    time_t t10la, t26la; /*time when started to look for light available sig*/
    int rval=0;          /*return value*/
    char icode[3];       /*input return code from CCDMAC*/
    int serr;            /*return value for serial i/o routines*/
    char ostring[29];    /*serial output string sent to say light is there*/
    int sport = -1;      /*index in Sfd of host serial port in use*/

    /*reset light release signals*/
    tel[bench]->lr=0;

    printf("  Goto bench code %d:%s on %d Tel. LBC# %d\n",
           tel[bench]->bnum,tel[bench]->bench,scope,tel[bench]->lnum);
    printf("   time is %ld\n",time()-Tzero);

    if (scope==10)
        {
        outb(nib1(&Obyte[1],tel[bench]->lnum),PortB1);
        t10la=time();
        /* check for LBC done */
        i = tel[bench]->bnum;
        /*Signal is high when light is there*/
        while (!bitcheck(Lbcsig[i].port,Lbcsig[i].bit))
            {
            if (time()>(t10la+3))   /*wait up to 3 seconds*/
                {
                rval=-1;
                break;
                }
            /*continue to allow user interaction with GUI while waiting*/
            Tcl_GlobalEval(Interp,"update\n");
            }
        }
    else
        {
        outb(nib2(&Obyte[1],tel[bench]->lnum),PortB1);
        t26la=time();
        /* check for LBC done */
        i = tel[bench]->bnum;
        /*Signal is low when light is there*/
        while (bitcheck(Lbcsig[i].port,Lbcsig[i].bit))
            {
            if (time()>(t26la+3))   /*wait up to 3 seconds*/
                {
                rval=-1;
                break;
                }
            /*continue to allow user interaction with GUI while waiting*/
            Tcl_GlobalEval(Interp,"update\n");
            }
        }

    printf("  LBC returns ok - time is %ld\n",time()-Tzero);

    /* ADD Synch mode? */
    /* ADD Check filter */

    /*turn active bench red*/
    sprintf(s,"bcolor .f%d.f%s.fcb.cb1 red\n",scope,tel[bench]->bench);
    Tcl_GlobalEval(Interp,s);
   
    /* if not dwelling, communicate with host computer */
    if (tel[bench]->dt == 0)      /*i.e. not dwelling*/
        {
        if (strncmp ("CCDMac",tel[bench]->host,6)==0)
            sport=MACSPORT;
        /*add other computers here*/
        /*but for now just do the following if it's the mac*/
        if (sport==MACSPORT)
            {
            /*before telling computer light is there, clear the input
              serial buffer*/
            while(get_serial(sport)!=0);
            /*now form the output string and send to host computer*/
            form_sout_string(tel[bench]->bnum,ostring);
            serial_out(Sfd[sport],ostring);
            }
        }

    /*set timer*/
    if (dwell=tel[bench]->dt)
        timer[bench]=time()+dwell;
    else
       timer[bench]=time()+tel[bench]->ct;

    return(rval);
    }

/* procedure check_cycle does all the cycle checking and updating stuff
   called by main()
   calls goto_bench()
*/
void check_cycle()

    {
    int next_ben;  /*next bench to go to*/
    char s[80];    /*command to send to the Tcl/Tk GUI*/
    int gberr;     /*return value from goto_bench*/

    /*have to add synch stuff here*/

    /*check 10" bench to see if it is ready to switch*/
    if (Cycling10) 
        if ((next_ben=check_tel(Bench10,Time10,On10,Num10ben))>=0)
            {
            /*turn off red light on bench in GUI*/
            sprintf(s,"bcolor .f10.f%s.fcb.cb1 normal\n",Bench10[On10]->bench);
            Tcl_GlobalEval(Interp,s); 
            if ((gberr=goto_bench(Bench10,Time10,10,next_ben))!=0)
                {
				printf("check_cycle: scope=10, gberr=%d\n",gberr);
                sprintf(Tclerrs[0],"check_cycle: Error");
                sprintf(Tclerrs[1],"bench: %s - goto_bench error:",
                        Bench10[next_ben]->bench);
                switch (gberr)
                    {
                    case -1: strcpy(Tclerrs[2], "Light Beam Available not returned");
                             sprintf(Tclerrs[3],"%d %d",Bench10[next_ben]->bnum,LBA);
                             if (!Ignore[LBA][Bench10[next_ben]->bnum])
                                 error_popup(3);
                             else
                                 error_update(3);
                             break;
                    }
                }
            On10 = next_ben;
            }

    /*check 26" bench to see if it is ready to switch*/
    if (Cycling26)
        if ((next_ben=check_tel(Bench26,Time26,On26,Num26ben))>=0)
            {
            /*turn off red light on bench in GUI*/
            sprintf(s,"bcolor .f26.f%s.fcb.cb1 normal\n",Bench26[On26]->bench);
            Tcl_GlobalEval(Interp,s); 
            if ((gberr=goto_bench(Bench26,Time26,26,next_ben))!=0)
                {
				printf("check_cycle: scope=26, gberr=%d\n",gberr);
                sprintf(Tclerrs[0],"check_cycle: Error");
                sprintf(Tclerrs[1],"bench: %s - goto_bench error:",
                        Bench26[next_ben]->bench);
                switch (gberr)
                    {
                    case -1: strcpy(Tclerrs[2], "Light Beam Available not returned");
                             sprintf(Tclerrs[3],"%d %d",Bench26[next_ben]->bnum,LBA);
                             if (!Ignore[LBA][Bench26[next_ben]->bnum])
                                 error_popup(3);
                             else
                                 error_update(3);
                             break;
                    }
                }
            On26 = next_ben;
            }
    return;
    }

/* procedure check_tel checks to see if the given telescope is ready to
  change benches.  bench is the telescope index of the current bench.
  Returns: next bench number if ready
           -1 if not.
   called by check_cycle() 
   calls light_released() and ready_for_light()
*/
int check_tel(Bstruct *tel[], time_t timer[], int bench, int maxben)

    {
    time_t now;  /*current time*/
    int i;
    int lerr; /*light_released() return value*/
 
    now=time();
    if ((tel[bench]->dt)&&(!tel[bench]->lr)) /*are we in dwell mode?*/
        if (timer[bench] >= now)  /*yes- done dwelling?*/
            return(-1);               /*nope - not done dwelling*/
        else                   
            {                         /*yes - done dwelling*/
            tel[bench]->lr = 1;   /*simulate the light release signal*/
            /*set time for next cycle*/
            timer[bench]=now+tel[bench]->ct - tel[bench]->dt;
            }
    else  /*not in dwell mode or dwell over - check light release*/
         if (!tel[bench]->lr)     /*if light wasn't previously released*/
             {
             if ((lerr=light_released(tel,bench))==0) /*light not now released*/
                 return(-1);     /*nope- light still not released*/
             if (lerr<0) /*got some kind of error from light_released()*/
                 return(lerr);
             }

    /*check if any cycle timers are ready*/
    /*I only get here if light is released or dwell time is over*/
    for (i=bench+1;i<maxben;i++)
        if (timer[i] <= now)
            if (ready_for_light(tel,i))
                return(i);
    for (i=0;i<=bench;i++)
        if (timer[i] <= now)
            if (ready_for_light(tel,i))
                return(i);

    /*no cycle timers ready or benches not ready*/    
    return(-1);
    }

/* procedure light_released sees if the light from the specifed bench has been
  released by host computer
  returns 1 if light released
          0 if light not released
         -2 if strange serial command received
  called by check_tel()
*/
int light_released(Bstruct *tel[], int bench)
    /*tel is the telescope structure to check (Tel10 or Tel26)*/
    /*bench is the bench # of the bench to check*/ 

    {
    char icode[3]; /*input serial port control code*/
    int sport = -1; /*index for serial port to check in Sfd*/
    int scmd;       /*input serial command code*/

     /*if all hosts have same language, I can simplify this 
       be setting the Sfd[] index given Bench[bench]->host and making
       all benches go through all this shit below*/

    if (strncmp("CCDMac",tel[bench]->host,6)==0)
        sport=MACSPORT;
    /*XXXAdd other benches here*/

    /*but for now, just say light is released if not CCDMac,
      the only one I know about.*/
    if (sport!=MACSPORT)
        {
        tel[bench]->lr=1;
        return(1);
        }

    scmd=get_serial(sport);
    switch (scmd)
        {
        case DWL: /*bingo - done with light*/
                  tel[bench]->lr=1;
                  return(1);
                  break;

        case -1: /*Unknown command*/
                 return(-2);
                 break;

        case 0: /*No signal received- light not released*/
                return(0);
                break;

        case RFL: /*Ready for light*/
                  /*I should not get this here*/
                  fprintf(stderr,"light_released got an RFL from\n");
                  fprintf(stderr,"get_serial where it shouldn't have\n");
                  fprintf(stderr,"from serial port %d.\n",sport);
                  return(-2);
                  break;
        }
    }

/* function ready_for_light checks to see if ready for light
   signal has been sent by host computer.
   returns 1 if yes (host is ready for light)
           0 if no  (host not ready for light)
          -2 if strange serial command received
   called by check_tel()
*/
int ready_for_light(Bstruct *tel[], int bench)
  
    {
    int sport = -1; /*index for serial port to be used in Sfd[]*/
    char icode[3];  /*input control code from host computer*/
    int scmd;       /*input serial command code*/
   

    if (strncmp("CCDMac",tel[bench]->host,6)==0)
        sport=MACSPORT;
    /*add other host computers here*/

    /*for now send yes if not CCDMac*/
    if (sport!=MACSPORT)
        {
        printf("ready_for_light returning yes\n");
        return(1);
        }

    scmd=get_serial(sport);
    switch (scmd)
        {
        case RFL: /*bingo - ready for light
                  return(1);
                  break;

        case -1: /*Unknown command*/
                 return(-2);
                 break;

        case 0: /*No signal received- light not released*/
                return(0);
                break;

        case DWL: /*Done with light*/
                  /*I should not get this here*/
                  fprintf(stderr,"ready_for_light got an DWL from\n");
                  fprintf(stderr,"get_serial where it shouldn't have\n");
                  fprintf(stderr,"from serial port %d.",sport);
                  return(-2);
                  break;
        }

    }

/* function nib1 sets the first nibble (4 bits) of the given byte to the
   supplied value
   called by goto_bench() 
*/
int nib1(unsigned char *byte, unsigned char value)

    {
    unsigned char mask = 0x0F;

    if (value > 15)
        {
        printf ("nib1: Cannot set a nibble to value %d which is >15!\n",value);
        exit (-1);
        }

    *byte &= ~mask;  /*strip low 4 bits*/
    *byte |= value;  /*set low 4 bits to value*/
    
    return (*byte);
    }

/* function nib2 sets the second nibble (4 bits) of the given byte to the
   supplied value
   called by goto_bench() 
*/
int nib2(unsigned char *byte, unsigned char value)

    {
    unsigned char mask = 0xF0;


    if (value > 15)
        {
        printf ("nib2: Cannot set a nibble to value %d which is >15!\n",value);
        exit (-1);
        }

    *byte &= ~ mask;         /*strip upper 4 bits*/
    *byte |= (value << 4);   /*set upper 4 bits to value*/

    return (*byte);
    }

/* function bitcheck returns the value of the given bit from the 
   given input port.
   called by goto_bench()
*/
int bitcheck (int port, unsigned char bit)
   
   {
   return( (inb(port)&(1<<bit))>>bit );
   }

/*procedure error_popup pops up an error box in the Tcl/Tk GUI
  with a three-line  message and a dismiss button
  called by start_cycling, stop_cycling, check_cycle
  calls errstr and berror in pdprp.tcl
*/
void error_popup (int numstr)

    {
    char s[80];
    int i;
    
    for (i=0;i<=numstr;i++)
        {
        sprintf(s,"errstr %d {%s}\n",i,Tclerrs[i]);
        Tcl_GlobalEval(Interp,s);
        }

    sprintf(s,"berror %d\n",numstr);
    Tcl_GlobalEval(Interp,s); 
    }

/*procedure error_update just allows the Tcl/TK GUI
  to update the last error message without popping
  up an error box. Used for ignored messages
  called by start_cycling, stop_cycling, check_cycle
  calls errstr and lerrupdate in pdprp.tcl
*/
void error_update (int numstr)

    {
    char s[80];
    int i;

    for (i=0;i<=numstr;i++)
        {
        sprintf(s,"errstr %d {%s}\n",i,Tclerrs[i]);
        Tcl_GlobalEval(Interp,s);
        }
    
    strcpy(s, "lerrupdate ignore\n");
    Tcl_GlobalEval(Interp,s);
    }

/*function error_ignore set up the global Ignore array with
  information on which errors to ignore- ie. not notify the
  observer about.
  called by the Tcl/Tk code routine errignore
*/
int  error_ignore (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
    /*This is what the Tcl/Tk interface needs to call a C routine*/
    /*I expect two int parameters as input: errbench and errcode*/
    {
    int errbench; /*bench which has the error*/
    int errcode;  /*error code*/


    errbench = atoi(argv[1]); 
    errcode = atoi(argv[2]);

    Ignore[errcode][errbench]=1; 

    return(TCL_OK);
    }

/* 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
*/
int serial_out (int sfd, char *string)
   
    {
    int n;
    int err;

    n=strlen(string);
    if ((err=write(sfd,string,n))<0)
        fprintf(stderr, "serial port write failed: %s\n");

    return(err);
    }

/* 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 get_serial()
*/
int serial_in (int sfd, char *string)
 
    {
    int n=0;

    if ((n=read(sfd,string,80))>0)
        string[n]='\0';

    return (n);
    }

/* function get_serial gets a command from the serial port
   and parses it,
   returning the command code corresponding
             0 if no command
            -1 if unknown command
   called by
   calls serial_in()
*/
int get_serial(int sport)
    /*sport is the index in Sfd of the serial port to use*/ 
    {
    int serr;         /*serial_in return value*/
    char command[80]; /*the input serial command string*/
 
    serr=serial_in(Sfd[sport], command);
    if (serr<=0) /*no command*/
        return(0);

    if (strncmp("L",command,1)==0) /*Ready For Light*/
        return(RFL); 
    if (strncmp("R",command,1)==0) /*Done With Light*/
        return(DWL);

    /*Must have gotten an unknown command, then*/
    fprintf(stderr,"get_serial: got unknown command %s\n",command);
    fprintf(stderr,"from port %d.\n",sport);
    return(-1);
    }

/* procedure form_sout_string forms the output string 
   sent through the serial port to the host computer
   saying light is there. The string's format is:
   chars  contents
   -----  --------
   0-3    RA (" from disk center)
   4      E or W
   5-8    DEC (" from disk center)
   9      N or S
   10-11  Bench code where light is
   12-19  Filter name  string
   20-27  Wavlength name string
   28     \n (newline)
   29     \0

called by goto_bench()
*/
void form_sout_string(int bnum, char *string)

    {
    /*I don't know any of these parameters but bench code*/
    if (bnum<10)
        sprintf(string,"0001E0002N0%1dFILTERXXWAVELENX\n\0",bnum);
    else
        sprintf(string,"0001E0002N%2dFILTERXXWAVELENX\n\0",bnum);
    }
