/* program pdprp.c - the PDP replacement project software
   started March, 1997 by Scot J. Kleinman 
   version 1.00 placed in operation 11June97
   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

   HISTORY
   -------
   27Jun97  SJK v1.01 - placed in operation at BBSO
    3Jul97  SJK v1.10 - Added timeout check for Light Release signal (DWL)
                        and Ready for Light signal (RFL)
                        and updated the way errors are handled in general
                        incl. making pdprp suspend cycle until user acknowledges
                        the error requestor (except for LBA case so far)
                        (needs pdprp.tcl v1.10)
                        DOESN'T WORK - FIXES ADDED IN V1.11
   21Aug97 SJK v1.11 -  Added fixes for the non-working v1.10.
                        needs pdprp.tcl v1.11 (which has been modified to
                        also handle ghosting of dwell time when appropriate)
   12Oct   SJK v1.20 -  Added support for OSL and VMG Vaxes.  Fixed a bug so
                        we do not continue cycling if there is an RFL Timeout
                        error box open.  Fixed some <= loops that should
                        have been just <. Fixed a bug in start_cycling
                        where it went to the first bench without checking
                        to see if the host was ready or not. Fixed serial port
                        parameters (was not in canonical mode, for example).
                        Flushed all serial ports on startup- this means - start
                        pdprp first before starting up host computer cycling.
                        Fixed start-up race condition. The only requirement is
                        that pdprp is started BEFORE the host computer programs
                        are started- after that, you can start cycling in any
                        order.  (Eg., mac start sequence then pdprp start26 or
                        pdprp start26 then mac start sequence.) Fixed type of
                        Io_s.port from int to unsigned long int. Fixed nib1 and
                        nib2 to return unsgined char not int.
                        Needs pdprp.tcl version 1.20
   14Oct   SJK v1.21    Fixed one-bench cycles and holds so they work-
                        previously they would not communicate with the
                        host.  Now, a one-bench cycle tells the host to
                        take a picture every cycle time, whereas a hold
                        tells the host to take a picture as soon as the
                        host says it's ready (actually, I cheat and use a
                        one second cycle time).
                        Changed timeouts to be TWICE the cycle time unless
                        cycle time is < 5s in which case you are on your own -
                        no timeouts will be reported.
                        Needs pdprp.tcl version 1.21
   16Oct   SJK v1.30    Put serial check routines in the main event loop
                        so we always respond to serial commands whether or
                        not we are cycling.   This elimates many problems in synching
                        with vaxes. Fixed bug in hold where cycle time got
                        left at 1 on de-hold. Added a bell on Error messages:
                        3 bells for an error box and one for an error message update
                        needs pdprpv1.30 which fixes some hold bench bugs

 */

#include<stdio.h>
#include<stdlib.h> /*atoi()*/
#include<string.h> /*string functions*/
#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 0x310UL /*base address of DIO board*/
#define PORTS 24     /*Number of i/o ports (each one with 8 bits)*/

                     /*The next group are all error codes*/
#define ELBA 0       /*light not available error*/
#define EDWL 1       /*Done With Light signal not received from host*/
#define ERFL 2       /*host not Ready For Light*/
#define MAXEC 3      /*total # of error codes listed above*/

                      /*The next group are for Serial port assignments*/
#define MACSPORT 0    /*Sfd index for Macintosh serial communication*/
#define OSLSPORT 1    /*Sfd index for OSLVax serial communication*/
#define VMGSPORT 2    /*Sfd index for VMGVax serial communiation*/
#define NUMSPORT 3    /*total # of serial ports listed above*/

                      /*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*/
#define END 3         /*End  - host program sequence terminated VAX only*/
#define IFR 4         /*Information request - currently VAX only*/

#define BELL '\007'   /*the ASCII code for the bell*/

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
    {
    unsigned long port;                       /*port #:PortA1,PortB1,...*/
    unsigned char bit;                        /*bit for reading/writing*/
    };
typedef struct Io_s Iostruct;

  /*set up ports to be accessed*/
int initialize_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); 

 /*check serial ports for input and respond to simple requests*/
void check_serial(void);

 /*find the telescope of the bench controlled by serial port sport*/
int find_sport_tel(int sport);

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

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

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

  /*see if the wait for an RFL (Ready For Light) signal has timed out  yet*/
int rfl_timeout_check(Bstruct *telescope[], int bench, int scope, time_t now);

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

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

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

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

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

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

  /*reset Enotify array - called by pdprp.tcl*/
int error_denotify (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);

  /*form the serial output info string for the vaxen*/
void form_vaxsout_string(int scope, 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 active 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*/
int Enotify[MAXEC][MAXBENCH]; /*Flags for user already notified of error; 1=yes*/
int Erfltime[MAXBENCH]; /*holds timers for time-out error on RFL signal*/

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];   /*holds error codes and benches to ignore*/
int Rflto=0;                   /*Boolean for is there an RFL TimeOut error*/

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*/
int Srfl[8];                     /*incoming ready for light from serial ports*/
int Sdwl[8];                     /*incoming done with light from serial ports*/

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

time_t Tzero;  /*temporary*/

/*MAIN*/
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++)
            Enotify[i][j]=Ignore[i][j]=0;
    for (i=0;i<MAXBENCH;i++)
        Erfltime[i]=0;
    for (i=0;i<8;i++)
        Srfl[i]=Sdwl[i]=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 now 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)
        {
        check_serial();
        if (Cycling26 || Cycling10)
            check_cycle();
        Tcl_GlobalEval(Interp,"update\n");
        }

    return(0);

    }

/*INITIALIZE_IO*/
/* 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];
    char buffer[80]; /*used to flush serial buffers*/

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

    /* initialize 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",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 */
        /*First, get current parameters*/
        tcgetattr(Sfd[i], &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[i], TCSANOW, &sopts);
        
        /*Clear/flush the input buffer*/
        while(serial_in(Sfd[i],buffer)>0);
        }

    return (0);
    }

/*OPEN_SPORT*/
/* 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);
    }

/*TCL_INITIALIZE*/
/* 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);
    /*Reset the Enotify array*/
    Tcl_CreateCommand (interp, "Cdenotify", (Tcl_CmdProc *) error_denotify,
                       (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);
    }

/*P_INITIALIZE*/
/* 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;
    }

/*READ_BDATA*/
/* 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);
    }

/*ASSIGN_DBASE*/
/* 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;
    }

/*START_CYCLING*/
/* procedure start_cycling starts the bench cycling
   called by the pdprp.tcl Tcl/Tk GUI start command
   calls read_bdata(), goto_bench(), error_popup(), error_ignore()
         ready_for_light, rfl_timeout_check(), and check_serial
*/
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*/
    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*/
    int initial_bench = 0; /*bench to go to initially*/

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

    scope = atoi(argv[1]);

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

    /*clear timer for RFL timeout check*/
    Erfltime[bench[initial_bench]->bnum]=0;

    /*Check to make sure bench is ready*/
    while (!ready_for_light(bench,initial_bench,scope))
        {
        /*not ready for light- check for timeout*/
        if (rfl_timeout_check(bench,initial_bench,scope,time()))
            break; /*timed out and ignored so proceed*/
        else
            if (Rflto) /*timed out and not ignored*/
                {
                fprintf(stderr, "start_cyc: Bench %s not returning RFL\n",
                                 bench[initial_bench]->bench);
                /*XXXpopup box will say check_tel error not start_cycle error*/
                return(TCL_OK);
                }
        check_serial(); /*check serial ports for new commands*/
        }

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

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

    /*go to initial bench (make this selectable later by changing initial_bench)*/
    if ((gberr=goto_bench(bench, ttime, scope, initial_bench))!=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,ELBA);
                     if (Ignore[ELBA][bench[0]->bnum])
                         error_update(3);
                     else
                         error_popup(3, scope);
                     break;
            }
        }
    
    /*set cycling flags*/
    if (scope==26)
        Cycling26=1;
    else
        Cycling10=1;

    return (TCL_OK);
    }

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

    scope = atoi(argv[1]);
  
    /*in each section below, I set set tel[bench]->dt to non 0 to fool
      goto_bench into not notifying host that light is now there.
      This is ok, since on re-start, the real values get read back in*/

    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)*/
        Bench26[0]->dt=1;
        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,ELBA);
                         if (Ignore[ELBA][Bench26[0]->bnum])
                             error_update(3);
                         else
                             error_popup(3,scope);
                         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)*/
        Bench10[0]->dt=1;
        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,ELBA);
                         if (Ignore[ELBA][Bench10[0]->bnum])
                             error_update(3);
                         else
                             error_popup(3, scope);
                         break;
                }
            }
        sprintf(s,"bcolor .f10.f%s.fcb.cb1 green\n",Bench10[0]->bench);
        Tcl_GlobalEval(Interp,s); 
        On10=0;
        }
    return (TCL_OK);
    }

/*GOTO_BENCH*/
/* 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 LBA (lightbeam available) signal not received
   -2 if serial input from host not read correctly
   -3 if host computer not ready
   calls nib1(),nib2(),bitcheck(),get_serial(),form_sout_string(),
         serial_out(), and bcolor in pdprp.tcl 
   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;       /*is set to tel[bench]->bnum*/
    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 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);
        printf("XXX obyte=%u;readback=%u\n", Obyte[1],inb(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*/
                {
                /*I've timed out waiting for light available signal
                  so tell calling routine about it, but carry on as if
                  everything is ok. (The calling routine will display
                  an error box, so the user can decide if things really
                  are screwy or not....)*/
                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);
        printf("XXX obyte=%u;readback=%u\n", Obyte[1],inb(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*/
                {
                /*see coments in this loop in the 10" section*/
                rval=-1;
                break;
                }
            /*continue to allow user interaction with GUI while waiting*/
            Tcl_GlobalEval(Interp,"update\n");
            }
        }

    printf("  LBA 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;
        else if (strncmp ("OSLVax",tel[bench]->host,6)==0)
            sport=OSLSPORT;
        else if (strncmp ("VMGVax",tel[bench]->host,6)==0)
            sport=VMGSPORT;

        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);
            }
        if ((sport==OSLSPORT)||(sport==VMGSPORT))
            serial_out(Sfd[sport],"LBA\r");
        }

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

    return(rval);
    }

/*CHECK_SERIAL*/
/* procedure check_serial checks all the serial ports for incoming commands.
   It responds to IFR and END from Vaxes.
   It fills global arrays for each signal for program access later.
   called by main() 
             start_cycling()
   calls get_serial()
         find_sport_tel()
         serial_out()
         form_vaxsout_string()
*/
void check_serial()

    {
    int sport;  /*serial port to check*/
    int scom;   /*incoming serial command return code*/
    int tel;    /*telescope on which the camera controlled by the host
                  connected to the given serial port is located*/
    char vistring[80]; /*Vax Information response STRING*/
    char s[80];  /*command sent to Tcl/Tk interpreter*/

    for (sport=0;sport<NUMSPORT;sport++)
        {
        scom=get_serial(sport); /*get and parse command*/
        switch (scom)
            {
            case 0  : break; /*no command received*/
            case RFL: Srfl[sport]=1; /*Ready for Light*/
                      break;
            case DWL: Sdwl[sport]=1; /*Done with Light*/
                      break;
            case END: tel=find_sport_tel(sport); /*End - sequence terminated*/
                      if (tel<27) 
                          {
                          /*respond and stop cycling*/
                          serial_out(Sfd[sport],"RDY\r");
                          sprintf(s, "Cstop %d\n",tel);
                          Tcl_GlobalEval(Interp,s);
                          /*XXXI should pop up some dialog box, but for now just stop*/
                          }
                       else
                          {
                          /*tel >27 so it must be 36 which means this serial port
                            talks to a host controlling benches on both telescopes
                            so respond and stop cycling on both telescopes*/
                          serial_out(Sfd[sport],"RDY\r");
                          sprintf(s, "Cstop 10\n");
                          Tcl_GlobalEval(Interp,s);
                          sprintf(s,"Cstop 26\n");
                          Tcl_GlobalEval(Interp,s);
                          /*XXXI should pop up some dialog box, but for now just stop*/
                          }
                      break;
            case IFR: tel=find_sport_tel(sport); /*Information request*/
                      /*This is only VAX - so tel can only be 10 or 26*/
                      form_vaxsout_string(tel,vistring);
                      serial_out(Sfd[sport],vistring);
                      break;
            }
        }
    }

/*FIND_SPORT_TEL*/
/* function find_sport_tel returns the telescope of the bench
   controlled by the given serial port
   returns 36 if both telescopes contain a bench controlled by sport
   called by check_serial()
*/
int find_sport_tel(int sport)

    {
    char host[9]; /*host computer name*/
    int i;
    int tel=0;    /*the telescope we are after*/

    /*determing host name given serial port*/
    if (sport==MACSPORT)
        sprintf(host,"CCDMac");
    else if (sport==OSLSPORT)
        sprintf(host,"OSLVax");
    else if (sport==VMGSPORT)
        sprintf(host,"VMGVax");
    else
        fprintf(stderr,"ERROR: find_sport -unknown serial port %d\n",sport);
 
    for (i=0;i<Num26ben;i++)
        if (strncmp(host,Bench26[i]->host,6)==0)
           tel=26;
    for (i=0;i<Num10ben;i++)
        if (strncmp(host,Bench10[i]->host,6)==0)
            if (tel==26) /*already found this host on 26"*/
                tel=36;
            else 
                tel=10;

    if (tel==0)
        fprintf(stderr,"ERROR:find_sport- can't find bench controlled by %s\n",host);
        /*this is a major problem- the rest of the program won't work*/

    return(tel);
    }

/*CHECK_CYCLE*/
/* procedure check_cycle does all the cycle checking and updating stuff
   called by main()
   calls check_tel()
         goto_bench()
         bcolor in pdprp.tcl
*/
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,10))>=0)
            {
            /*telescope is ready to switch*/
            /*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,
                                     ELBA);
                             if (Ignore[ELBA][Bench10[next_ben]->bnum])
                                 error_update(3);
                             else
                                 error_popup(3, 10);
                             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,26))>=0)
            {
            /*telescope is ready to switch*/
            /*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,
                                     ELBA);
                             if (Ignore[ELBA][Bench26[next_ben]->bnum])
                                 error_update(3);
                             else
                                 error_popup(3, 26);
                             break;
                    }
                }
            On26 = next_ben;
            }
    return;
    }

/*CHECK_TEL*/
/* 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.
           -2 if timed out waiting for light release
           return value from light_released
   called by check_cycle() 
   calls light_released(), ready_for_light()
         error_popup(), error_update()
*/
int check_tel(Bstruct *tel[], time_t timer[], int bench, int maxben, int scope)
    /*tel is either Bench10 or Bench26, similar with timer = Time26 or Time10
      and maxben is either Num26ben or Num10ben*/
    {
    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; are we 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,scope))==0) /*light not now released?*/
                 if ((now>(timer[bench]+tel[bench]->ct)) &&
                     (!Enotify[EDWL][tel[bench]->bnum]) &&
                     (tel[bench]->ct>=5))  /*nope - wait at most two cycle times
                                             unless ct<5 in which case no timeouts
                                             at all will be reported*/
                     {
                     /*two cycle times has passed and the bench has not released 
                       the light*/
                     fprintf(stderr, "check_tel: Bench %s not returning DWL\n",
                                      tel[bench]->bench);
                     sprintf(Tclerrs[0],"check_tel: Error");
                     sprintf(Tclerrs[1],"bench: %s - check_tel waited two cycle times",
                                         tel[bench]->bench);
                     sprintf(Tclerrs[2], "Light not released by bench");
                     sprintf(Tclerrs[3], "%d %d",tel[bench]->bnum,EDWL);

                     Enotify[EDWL][tel[bench]->bnum]=1;
                     if (Ignore[EDWL][tel[bench]->bnum])
                         {
                         error_update(3);
                         Enotify[EDWL][tel[bench]->bnum]=0;
                         /*I'm ignoring - so continue as if it's ok*/
                         /*set things up and fall through without returning*/
                         tel[bench]->lr=1;
                         lerr=1;
                         }
                     else
                         {
                         error_popup(3, scope);
                         return(-2);
                         }
                     }
                 else 
                     return(-1);     /*light not released and not timed out*/
             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*/
    /*Or I am simulating light release by Ignoring the error condition*/

    if (Rflto)   /*if I'm in an RFL timeout with active error box*/
        return(-1); /*don't do anything*/
    for (i=bench+1;i<maxben;i++)
        if (timer[i] <= now)
            if (ready_for_light(tel,i,scope))
                {
                Erfltime[tel[i]->bnum]=0;
                return(i);
                }
            else
                if (rfl_timeout_check(tel,i,scope,now)) /*timed out & ignored*/
                    return(i);
    if (Rflto)   /*I just got an RFL timeout box, so don't do anything*/
        return(-1);
    for (i=0;i<=bench;i++)
        if (timer[i] <= now)
            if (ready_for_light(tel,i,scope))
                {
                Erfltime[tel[i]->bnum]=0;
                return(i);
                }
            else
                if (rfl_timeout_check(tel,i,scope,now)) /*timed out & ignored*/
                    return(i);

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

/*LIGHT_RELEASED*/
/* 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
         (return values between -1 and -5 inclusive are reserved for check_tel())
  called by check_tel()
*/
int light_released(Bstruct *tel[], int bench, int scope)
    /*tel is the telescope structure to check (Bench10 or Bench26)*/
    /*bench is the bench # of the bench to check*/ 
    /*scope is the telescope - either 10 or 26*/

    {
    int sport = -1; /*index for serial port to check in Sfd*/

     /*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;
    else if (strncmp("OSLVax",tel[bench]->host,6)==0)
        sport=OSLSPORT;
    else if (strncmp("VMGVax",tel[bench]->host,6)==0)
        sport=VMGSPORT;

    if (Sdwl[sport]==1) /*got a DWL signal*/
        {
        Sdwl[sport]=0; /*reset it*/
        tel[bench]->lr=1;
        if ((sport==OSLSPORT)||(sport=VMGSPORT))
            serial_out(Sfd[sport],"RDY\r");
        return(1);
        }
    else
       return(0);
    }

/*READY_FOR_LIGHT*/
/* 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)
   called by check_tel()
*/
int ready_for_light(Bstruct *tel[], int bench,int scope)
    /*tel is the telescope structure to check (Bench10 or Bench26)*/
    /*bench is the bench # of the bench to check*/ 
    /*scope is the telescope - either 10 or 26*/
    
    {
    int sport = -1; /*index for serial port to be used in Sfd[]*/
   

    if (strncmp("CCDMac",tel[bench]->host,6)==0)
        sport=MACSPORT;
    else if (strncmp("OSLVax",tel[bench]->host,6)==0)
        sport=OSLSPORT;
    else if (strncmp("VMGVax",tel[bench]->host,6)==0)
        sport=VMGSPORT;
    
    if (sport==-1) /*either None or unknown host*/
        {
        printf("ready_for_light returning yes\n");
        return(1);
        }

    if (Srfl[sport]==1)
        {
        Srfl[sport]=0;
        return(1);
        }
    else
        return(0);

    }

/*RFL_TIMEOUT_CHECK*/
/* I didn't get an RFL when the bench was ready- so see if it has timed
   out.
   returns 0 Not timed out or timed out and not ignored
           1 if timeout and ignore
   sets global variable Rflto to 1 if timed out and not ignored

   called by check_tel(), start_cycling
   calls error_update() and error_popup()
*/
int rfl_timeout_check(Bstruct *tel[], int bench, int scope, time_t now)
    /*tel[] is either Bench26[] or Bench10[]*/
    
    {
    int bn; /*short for tel[bench]->bnum*/
    int rval = 0; /*return value*/

    /*bench is not ready for light even though it's time has
      come - so check to see if it has timed out- which I 
      define here as two cycle times after its timer becomes ready*/
    bn=tel[bench]->bnum;
    if (Erfltime[bn]==0)
        Erfltime[bn]=now+2*tel[bench]->ct;  /*start counting for time-out*/
    else
        if ((now > Erfltime[bn]) && (!Enotify[ERFL][bn]) && (tel[bench]->ct>=5))
            /*check for time-out unless ct<5*/
            {
            /*Yup- I waited two cycle times and I stil do not get an RFL*/
            /*so do the error thing*/
            fprintf(stderr, "check_tel: Bench %s not returning RFL\n",
                             tel[bench]->bench);
            sprintf(Tclerrs[0],"check_tel: Error");
            sprintf(Tclerrs[1],"bench: %s - check_tel waited two cycle times",
                    tel[bench]->bench);
            sprintf(Tclerrs[2], "Computer not reporting Ready For Light");
            sprintf(Tclerrs[3], "%d %d",bn,ERFL);
            Enotify[ERFL][bn]=1;
            if (Ignore[ERFL][bn])
                {
                error_update(3);
                Enotify[ERFL][bn]=0;
                Erfltime[bn]=0;
                rval=1;
                }
            else
                {
                Rflto=1;
                error_popup(3, scope);
                }
            }

    return (rval);
    }

/*NIB1*/
/* function nib1 sets the first nibble (4 bits) of the given byte to the
   supplied value
   called by goto_bench() 
*/
unsigned char 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);
    }

/*NIB2*/
/* function nib2 sets the second nibble (4 bits) of the given byte to the
   supplied value
   called by goto_bench() 
*/
unsigned char 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);
    }

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

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

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

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

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

    printf("%c\n",BELL);
    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);
    }

/*ERROR_IGNORE*/
/*function error_ignore set up the global Ignore array with
  information on which errors to ignore- ie. not notify the
  observer about. Also clears any global ERROR flags
  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;

    if (errcode==ERFL)
        Rflto=0;

    return(TCL_OK);
    }

/*ERROR_DENOTIFY*/
/*function error_denotify clears the Enotify array once an error
  has been acknowledged by the user- either through the Stop,
  Continue, or Ignore  command in pdprp.tcl.
  called by podprp.tcl
*/
int error_denotify (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*/

    if (argc==3) /*$errstring(3) in pdprp.tcl was passed as two paramteters*/
        { 
        errbench = atoi(argv[1]); 
        errcode = atoi(argv[2]);
        }
    else /*$errstring(3) in pdprp.tcl was passed as one parameter*/
        sscanf(argv[1],"%d %d",&errbench,&errcode);

    Enotify[errcode][errbench]=0;

    if (errcode==ERFL)
        Rflto=0;

    return(TCL_OK);
    }
/*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
*/
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",string);

    return(err);
    }

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

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

/*GET_SERIAL*/
/* 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 goto_bench()
             light_released()
             ready_for_light()
   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);
    printf("XXXget_serial got %s\n",command);

    if (sport==MACSPORT)   /*Talking to Mac*/
        {
        if (strncmp("L",command,1)==0) /*Ready For Light*/
            return(RFL); 
        if (strncmp("R",command,1)==0) /*Done With Light*/
            return(DWL);
        }

     else if ((sport==OSLSPORT)||(sport=VMGSPORT))
        {
        if (strncmp("WAI",command,3)==0) /*Ready for Light*/
            return(RFL);
        if (strncmp("SET",command,3)==0) /*Ready for Light*/
            return(RFL);
        if (strncmp("EVM",command,3)==0) /*Done with Light*/
            return(DWL);
        if (strncmp("LBR",command,3)==0) /*Done with Light*/
            return(DWL);
        if (strncmp("IFR",command,3)==0) /*Got IFR*/
            return(IFR);
        if (strncmp("END",command,3)==0) /*Got END*/
            return(END);
        }
     else  /*sport != MAC or OSL/VMG SPORT & I don't know how to handle it*/

        {
        fprintf(stderr,"get_serial: don't know serial port %d\n",sport);
        return(-1);
        }


    /*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);
    }

/*FORM_SOUT_STRING*/
/* procedure form_sout_string forms the output string 
   sent through the serial port to the Mac 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);
    }

/*FORM_VAXSOUT_STRING*/
/* procedure form_vaxsout_string forms the string to be sent out
   the serial port to the Vaxes when they request information.
   The format is
    chars  content
    -----  -------
    char   IFO
    char   space
    int    telescope # 0026 or 0010
    char   space
    int    RA (" from disk center) direction? XXX
    char   space
    int    DEC (" from disk center) direction? XXX
    char   space
    int    region number

   called by get_serial()
*/
void form_vaxsout_string(int scope, char *string)

    {
    /*Currently, all I know that it wants is telescope*/
    sprintf(string,"IFO %2d 1 2 0\r\0",scope);
    }
