Skip to content. | Skip to navigation

Personal tools

ir.cpp

ir.cpp

ir.cpp

/////////////////////////////////////////////////////////////////////////////
// IR.CPP
//
//  Last updated: 7/14/2005
//
//  Part of the source code for the Vane End Actuator Control System
//
//  This file contains all variables/constants/functions for updating
//  the instrument rotators.
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// CIR::Init - Initialize all DMC variables
void CIR::Init(int address)
{
  char line[80];

  Address = address;

  // Safe starting values (later read from VANE.INI)
	P = 0.5;
  I = 0.0;
  D = 1.0;
  Acc = 100000;
  Dec = 100000;
  Spd = 50000;
  ErrorLimit = 10000;
  VelocProfFilter = 1.0000;
  TorqueLimit = 9.9999;
  Revolution = 30000000;
  HomeJogSpeed = 50000;
  CCWHome = 0;
  MinHomeDistance = 0;
  MinBackOffLimitDistance = 0;
  Enabled = 0;
  SpdP = 1.0;
  SpdI = 0.0;
  SpdD = 0.0;
  SpdIL = 0.1;

	// All other variables
  NewMove = 0;
  LastCommand = 1;	// Go command
  UpdateStep = 0;
  DMCCommandStep = 0;
  DMCReceiveStep = 0;
  ErrorCount = 0;
  CommandCount = 0;
  Homed = 0;
  Homing = 0;
  Encoder = 0;
  CWLimit = Revolution;
  CCWLimit = -Revolution;
  CWSLimitS = 0;
  CCWSLimitS = 0;
  Command = 0;	// Go 0 degrees
  JogCommand = 0;
  ErrorFlag = 0;
  Motion = IRMNone;
  DMCMoving = 0;
  MotorPower = 0;
  Limit = IRLNone;
  LockPin = 0;
  Brake = 0;
	MasterEncErr = EncoderOK;
	SlaveEncErr = EncoderOK;
  AmplifierPower = 0;
  IndexLatched = 0;
  LatchedEncoder = 0;
  Zone = IRZCCW;
  Velocity = 0;
  Profile = 0;
  Volts = 0;
  Req = 0;
  Val = 0;
  Vel = 0;
  GoSFlag = 0;
  GoSPosition = 0;
  GoSVelocity = 0;
  GoSStarting = 0;
  GoSDisplayError = 0;
  DisplayMCurrent = 0;
  DisplayMEStatus = 0;
  DisplaySEStatus = 0;
  inTol = 0;
  inTolTime = 0;
  dispflag = 0;

	if (Address == IRNASW)
		IRPort = NASWIRPORT;
  else if (Address == IRNASE)
    IRPort = NASEIRPORT;
  else if (Address == IRCASS)
    IRPort = CASSIRPORT;
  else if (Address == IRAUX1)
    IRPort = AUX1IRPORT;
  else if (Address == IRAUX2)
    IRPort = AUX2IRPORT;
  else if (Address == IRAUX3)
    IRPort = AUX3IRPORT;

}

/////////////////////////////////////////////////////////////////////////////
// CIR::Update - Update instrument rotator communication/display
int CIR::Update()
// Returns 0 on success, 1 on error, -1 on not done
{
	char cmd[255];
	char s[16];
	int e = -1;
	int TS,TI;	// Store responses from TS and TI commands
	float ftemp;
	static int stopping = 0;	// Flags if rotator is stopping after hitting a soft limit

	// Only update DMC if enabled
	if (Enabled)
	{
		// Initialize DMC
		if (UpdateStep == 0)
		{
			e = SerDMCCommand("EO 0\r",Response,200); // Echo off
			if (e == 0) UpdateStep = 1;
		}
		else if (UpdateStep == 1)
		{
			e = SerDMCCommand("STX;AMX;MOX\r",Response,2000); // Stop + Motor off
			if (e == 0) UpdateStep = 2;
		}
		else if (UpdateStep == 2)
		{
			sprintf(cmd,"ER %d\r",ErrorLimit);
			e = SerDMCCommand(cmd,Response,200); // Internal servo error level
			if (e == 0) UpdateStep = 3;
		}
		else if (UpdateStep == 3)
		{
			e = SerDMCCommand("OE 1\r",Response,200);	// Off on error
			if (e == 0) UpdateStep = 4;
		}
		else if (UpdateStep == 4)
		{
			// Avoid acting on clamp at CASS-Mag2 (output 1)
			if ((CurrentIR == IRCASS) && (Telescope == 2))
			{
				e = 0;
				UpdateStep = 5;
			}
			else
			{
			  e = SerDMCCommand("OP 0\r",Response,200); // Digital output port clear all bits
			  if (e == 0) UpdateStep = 5;
			}
		}
    else if (UpdateStep == 5)
    {
      // For now only the three folded port controllers are in daisy chain.
      // So change factory default setting (Master Reset) for AUX port
      // communication at Folded Port 1 and Folded Port 2 controllers
      if ((CurrentIR == IRAUX1) || (CurrentIR == IRAUX2))
      {
        e = SerDMCCommand("CC 19200,1,1,0\r",Response,200); // Com setup
        if (e == 0) UpdateStep = 6;
      }
      else
      {
        e = 0;
        UpdateStep = 6;
      }
    }
		else if (UpdateStep == 6)
		{
			e = SerDMCCommand("MT 1\r",Response,200); // Motor type
			if (e == 0) UpdateStep = 7;
		}
		else if (UpdateStep == 7)
		{
			e = SerDMCCommand("CN 1,-1,-1\r",Response,200); // Configuration
			if (e == 0) UpdateStep = 8;
		}
		else if (UpdateStep == 8)
		{
			if ((CurrentIR == IRNASE) || (CurrentIR == IRNASW))
			{
				e = SerDMCCommand("CE2\r",Response,200); // Encoder Configuration, reverse quadrature
				if (e == 0) UpdateStep = 9;
			}
			else // IR is any Folded Port and CASS
			{
				e = SerDMCCommand("CE3\r",Response,200); // Encoder Configuration, reverse pulse and direction
				if (e == 0) UpdateStep = 9;
			}
		}
		else if (UpdateStep == 9)
		{
			e = SerDMCCommand("BN\r",Response,2000); // Store all previous config
			if (e == 0) UpdateStep = 10;
		}
		else if (UpdateStep == 10)
		{
			sprintf(cmd,"AC %d\r",Acc);
			e = SerDMCCommand(cmd,Response,200); // Acceleration
			if (e == 0) UpdateStep = 11;
		}
		else if (UpdateStep == 11)
		{
			sprintf(cmd,"DC %d\r",Dec);
			e = SerDMCCommand(cmd,Response,200); // Deceleration
			if (e == 0) UpdateStep = 12;
		}
		else if (UpdateStep == 12)
		{
			sprintf(cmd,"KP %0.2f\r",P);
			e = SerDMCCommand(cmd,Response,200); // P
			if (e == 0) UpdateStep = 13;
		}
		else if (UpdateStep == 13)
		{
			sprintf(cmd,"KI %0.2f\r",I);
			e = SerDMCCommand(cmd,Response,200); // I
			if (e == 0) UpdateStep = 14;
		}
		else if (UpdateStep == 14)
		{
			sprintf(cmd,"KD %0.2f\r",D);
			e = SerDMCCommand(cmd,Response,200); // D
			if (e == 0) UpdateStep = 15;
		}
		else if (UpdateStep == 15)
		{
			sprintf(cmd,"SP %d\r",Spd);
			e = SerDMCCommand(cmd,Response,200); // Speed
			if (e == 0) UpdateStep = 16;
		}
		else if (UpdateStep == 16)
		{
			sprintf(cmd,"IT %0.4f\r",VelocProfFilter);
			e = SerDMCCommand(cmd,Response,200); // Independent Time Constant
			if (e == 0) UpdateStep = 17;
		}
		else if (UpdateStep == 17)
		{
			sprintf(cmd,"TL %0.4f\r",TorqueLimit);
			e = SerDMCCommand(cmd,Response,200); // Max Torque output
			if (e == 0) UpdateStep = 18;
		}
		// Speed servo init
		else if (UpdateStep == 18)
		{
			sprintf(cmd,"MAXV=%d\r",Spd);
			e = SerDMCCommand(cmd,Response,200); // Max Speed
			if (e == 0) UpdateStep = 19;
		}
		else if (UpdateStep == 19)
		{
			sprintf(cmd,"P=%f\r",SpdP);
			e = SerDMCCommand(cmd,Response,200); // Speed Servo P
			if (e == 0) UpdateStep = 20;
		}
		else if (UpdateStep == 20)
		{
			sprintf(cmd,"I=%f\r",SpdI);
			e = SerDMCCommand(cmd,Response,200); // Speed Servo I
			if (e == 0) UpdateStep = 21;
		}
		else if (UpdateStep == 21)
		{
			sprintf(cmd,"D=%f\r",SpdD);
			e = SerDMCCommand(cmd,Response,200); // Speed Servo D
			if (e == 0) UpdateStep = 22;
		}
		else if (UpdateStep == 22)
		{
			sprintf(cmd,"INTLIM=%f\r",SpdIL);
			e = SerDMCCommand(cmd,Response,200); // Speed Servo Integral limit
			if (e == 0) UpdateStep = 30;
		}
		// Initialization complete.  Now do normal update loop
		//////////////////////////
		// Status checks
		else if (UpdateStep == 30)
    {
      // Master Encoder analog Set-Up signal
      e = SerDMCCommand("MG@AN[1]\r",Response,200);
      if (e == 0)
      {
        if (sscanf(Response,"%f",&MasterEncErr) == 1)
        {
					if ((Address == ActiveIR) && DisplayMEStatus && !GoSDisplayError && !DisplayMCurrent)
          {
						PutString16("MEncSU",12,60,ibty+26+96,BLACK,SHECMEDGRAY);
            sprintf(s,"%1.5f",MasterEncErr);
            PutString16(s,12,60,ibty+26+8*16,BLACK,SHECMEDGRAY);
            IRErrChart.Add(MasterEncErr);
          }
          UpdateStep = 330;
        }
        else
          e = 1;
      }
    }
		else if (UpdateStep == 330)
    {
      // Slave Encoder analog Set-Up signal
      e = SerDMCCommand("MG@AN[3]\r",Response,200);
      if (e == 0)
      {
        if (sscanf(Response,"%f",&SlaveEncErr) == 1)
        {
					if ((Address == ActiveIR) && DisplaySEStatus && !GoSDisplayError && !DisplayMCurrent)
          {
						PutString16("SEncSU",12,60,ibty+26+96,BLACK,SHECMEDGRAY);
            sprintf(s,"%1.5f",SlaveEncErr);
            PutString16(s,12,60,ibty+26+8*16,BLACK,SHECMEDGRAY);
            IRErrChart.Add(SlaveEncErr);
          }
          UpdateStep = 331;
        }
        else
          e = 1;
      }
    }
		else if (UpdateStep == 331)
    {
      // Motor current sensed by amplifier
      if (DisplayMCurrent)
      {
        e = SerDMCCommand("MG@AN[2]\r",Response,200);
        if (e == 0)
        {
          if (sscanf(Response,"%f",&ftemp) == 1)
          {
            //MotorCurrent = ftemp;
            MotorCurrent = ftemp * 4.0;
					  if ((Address == ActiveIR) && DisplayMCurrent && !GoSDisplayError && !DisplayMEStatus && !DisplaySEStatus)
            {
						  PutString16("MotCur",12,60,ibty+26+96,BLACK,SHECMEDGRAY);
              sprintf(s,"%2.4f",MotorCurrent);
              PutString16(s,12,60,ibty+26+8*16,BLACK,SHECMEDGRAY);
              IRErrChart.Add(MotorCurrent);
            }
            UpdateStep = 332;
          }
          else
            e = 1;
        }
      }
      else
			{
      	e = 0;
				UpdateStep = 332;
      }
    }
	// Read Brake level for M2 CASS. Up means brake in.
	else if (UpdateStep == 332) {
		if (Telescope == 2) {
			e = IRs[IRCASS].SerDMCCommand("MG@AN[4]\r",Response,200);
			if (e == 0) {
				if (sscanf(Response,"%f",&ftemp) == 1) {
					if (ftemp > 4.0) {
						IRs[IRCASS].Brake = 1;
					}
					else {
						IRs[IRCASS].Brake = 0;
					}
					UpdateStep = 31;
				}
				else {
					e = 1;
				}
			}
		}
		else {
			e = 0;
			UpdateStep = 31;
		}
	}
	else if (UpdateStep == 31) {

		e = SerDMCCommand("TPX\r",Response,200); // Get encoders

		if (e == 0) {

			if (sscanf(Response,"%d",&Encoder) == 1) {

				DisplayEncoder();
				DisplayCommand();
				if (Homing == 1) UpdateStep = 310;
				else {

					// Set soft limits status
					if (Encoder <= (CCWLimit + (Revolution / 360) * SLDist))
						CCWSLimitS = 1;
					else
						CCWSLimitS = 0;

					if (Encoder >= (CWLimit - (Revolution / 360) * SLDist))
						CWSLimitS = 1;
					else
						CWSLimitS = 0;

					// if moving out of software limits, stop.
					if ((DMCMoving) && (!stopping) && (
					   ((Motion == IRMCW) &&
					   // Distance s = s0 - Vel^2/2a, a = Dec, negative
					   ((Encoder + ((Velocity * Velocity) / (2 * Dec)))
					   >= CWLimit - (Revolution / 360) * SLDist)) ||
					   ((Motion == IRMCCW) &&
					   // a = Dec, positive
					   ((Encoder - ((Velocity * Velocity) / (2 * Dec)))
					   <= CCWLimit + (Revolution / 360) * SLDist))
					   )) {

						BSerDMCCommand("STX\r",Response,200); // Stop any motion
						NewMove = 0;
						EDSLog.Add(113,"Soft limit hit, stopping");
						stopping = 1;
					}
					if (!DMCMoving) stopping = 0;
					UpdateStep = 32;
				}
			}
			else e = 1;
		}
	}
	else if (UpdateStep == 310) {
		if ((Homing == 1) && (abs(HomeEncStart - Encoder) > (Revolution / 2))) {
			HomeStep = 16;
		}
		UpdateStep = 32;
	}
		else if (UpdateStep == 32)
    {
    	e = SerDMCCommand("TSX\r",Response,200); // Get switches
      if (e == 0)
      {
				if (sscanf(Response,"%d",&TS) == 1)
        {
        	if (TS & 1) IndexLatched = 1;
          else IndexLatched = 0;
          if (TS & 2) Zone = IRZCCW;
					else Zone = IRZCW;
					if (TS & 4) Limit &= IRLCW;
          else Limit |= IRLCCW;
          if (TS & 8) Limit &= IRLCCW;
          else Limit |= IRLCW;
					if (TS & 32) MotorPower = 0;
          else MotorPower = 1;
          if (TS & 64) ErrorFlag = 1;
          else ErrorFlag = 0;
					if (TS & 128) DMCMoving = 1;
          else DMCMoving = 0;

          // Mark the end of motion
          if (!DMCMoving)
          	Motion = IRMNone;

          DisplaySwitches();
			if ((Telescope == 1) ||
				((Telescope == 2) &&
				 (CurrentIR == IRCASS)) &&
			    (!Homing && Homed && IndexLatched))
			{
				UpdateStep = 320;
			}
			else UpdateStep = 33;
        }
				else
					e = 1;
			}
		}

		// Read latch position if run over it
		else if (UpdateStep == 320) {
			e = SerDMCCommand("RL\r",Response,200);
			if (e == 0) {
				EDSLog.Add(984,"Reading %s latch position",IRNames[Address]);
				if (sscanf(Response,"%d",&LatchedEncoder) == 1) {
					EDSLog.Add(984,"%s encoder latch found at %d",
						IRNames[Address],
						LatchedEncoder);
					if ((Telescope == 1) &&
						(CurrentIR == IRAUX2) &&
						(Velocity < 0))
					{
						EDSLog.Add(984,"%s current drift is %+6.3f deg",
							IRNames[Address],
							(float)(LatchedEncoder + 513) *
								(360.0/(float)Revolution));
					}
					else if ((Telescope == 1) &&
							 (CurrentIR == IRNASW) &&
							 (Velocity < 0))
					{
						EDSLog.Add(984,"%s current drift is %+6.3f deg",
							IRNames[Address],
							(float)(LatchedEncoder + 2200) *
								(360.0/(float)Revolution));
					}
					else if ((Telescope == 1) &&
							 (CurrentIR == IRNASE) &&
							 (Velocity > 0))
					{
						EDSLog.Add(984,"%s current drift is %+6.3f deg",
							IRNames[Address],
							(float)(LatchedEncoder - 3800) *
								(360.0/(float)Revolution));
					}
					else if ((Telescope == 2) &&
							 (CurrentIR == IRCASS) &&
							 (Velocity > 0))
					{
						EDSLog.Add(984,"%s current drift is %+6.3f deg",
							IRNames[Address],
							(float)(LatchedEncoder - 500) *
								(360.0/(float)Revolution));
					}
					else {
						EDSLog.Add(984,"%s current drift is %+6.3f deg",
							IRNames[Address],
							(float)LatchedEncoder*(360.0/(float)Revolution));
					}
					UpdateStep = 321;
				}
				else e = 1;
			}
		}
		// Keep latch armed
		else if (UpdateStep == 321) {
			e = SerDMCCommand("AL\r",Response,200);
			if (e == 0) {
				UpdateStep = 33;
			}
		}

		else if (UpdateStep == 33)
		{
			e = SerDMCCommand("TI\r",Response,200); // Get inputs
			if (e == 0)
			{
				if (sscanf(Response,"%d",&TI) == 1)
				{
					// Power Amplifier status (on-1/off-0)
					if (TI & 4) {
						AmplifierPower = 1;
					}
					else {
						AmplifierPower = 0;
						// Turn Galil's built-in servo off (avoiding current peaks)
						if (MotorPower) BSerDMCCommand("MOX\r",Response,200);
					}
					// NASW/NASE/CASS-Mag1 have only manual Lock Pin (not Brake)
					if ((CurrentIR == IRNASW) || (CurrentIR == IRNASE) || ((CurrentIR == IRCASS) && (Telescope == 1)))
				  {
						if (TI & 2) LockPin = 1;
						else LockPin = 0;
					}
					// CASS-Mag2 has manual Lock Pin and Latches
					else if ((CurrentIR == IRCASS) && (Telescope == 2))
					{
						if (TI & 2) LockPin = 1;
						else LockPin = 0;
					}
					// AUX IRs DO NOT have Brake neither Lock Pin
          // so no read inputs for them
					DisplayInputs();
					UpdateStep = 334;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 334)
		{
      // Always update clamp status for CASS at Mag2
			if (Telescope == 2)
      {
			  e = IRs[IRCASS].SerDMCCommand("TI\r",Response,200); // Get inputs
			  if (e == 0)
			  {
				  if (sscanf(Response,"%d",&TI) == 1)
				  {
						if (TI & 8) M2Cass.LatchReleased = 0;
						else M2Cass.LatchReleased = 1;
						if (TI & 16) M2Cass.LatchEngaged = 0;
						else M2Cass.LatchEngaged = 1;
						if (TI & 32) M2Cass.IndexC = 0;
						else M2Cass.IndexC = 1;
						if (TI & 64) M2Cass.IndexB = 0;
						else M2Cass.IndexB = 1;
						if (TI & 128) M2Cass.IndexA = 0;
						else M2Cass.IndexA = 1;
            M2Cass.M3Position = 4 * M2Cass.IndexC + 2 * M2Cass.IndexB + M2Cass.IndexA;
            M2Cass.DisplayPort();
					  UpdateStep = 34;
					}
				  else
					  e = 1;
				}
      }
      else
      {
        e = 0;
        UpdateStep = 34;
      }
		}
		else if (UpdateStep == 34)
		{
			e = SerDMCCommand("TEX\r",Response,200); // Get error
			if (e == 0)
			{
				if (sscanf(Response,"%d",&Error) == 1)
				{
					if ((Address == ActiveIR) && !GoSDisplayError && !DisplayMCurrent && !DisplayMEStatus && !DisplaySEStatus)
          {
						PutString16(" Error",12,60,ibty+26+96,BLACK,SHECMEDGRAY);
            PutString16("      ",12,60,ibty+26+8*16,BLACK,SHECMEDGRAY);
						IRErrChart.Add(Error*360.0/float(Revolution));
					}
					UpdateStep = 35;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 35)
		{
			e = SerDMCCommand("TVX\r",Response,200); // Get velocity
			if (e == 0)
			{
				if (sscanf(Response,"%f",&ftemp) == 1)
				{
					Velocity = ftemp;
					DisplayVelocity();
					if (Address == ActiveIR)
						IRVelChart.Add(Velocity*360.0/float(Revolution));
					UpdateStep = 36;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 36)
		{
			// Check to see if servo code is running
			e = SerDMCCommand("MG_XQ\r",Response,200);
			if (e == 0)
			{
				if (sscanf(Response,"%f",&ftemp) == 1)
				{
					GoSFlag = ftemp;
					// Servo code wasn't fully running, so don't query ERR or MAXV
					if (GoSFlag < IRServoMain)
						UpdateStep = 39;
					else
						UpdateStep = 37;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 37)
		{
			// Check to see if servo code was reset...
			// MAXV is set to 1000 on the start of IRSERVO.DMC.
			// VANE sets it to it's normal higher value.
			// If we read it, and it's back down to 1000,
			// the servo code reset due to an error.
			e = SerDMCCommand("MAXV=\r",Response,200);
			if (e == 0)
			{
				if (sscanf(Response,"%f",&ftemp) == 1)
				{
					if (ftemp == 1000.0)
					{
						EDSLog.Add(500,"Warning! IR Servo code restart");
						UpdateStep = 18;	// Reinitialize servo variables
					}
					else
						UpdateStep = 38;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 38)
		{
			// Check speed servo error
			e = SerDMCCommand("ERR=\r",Response,200);
			//e = SerDMCCommand("HPOS=\r",Response,200);
			if (e == 0)
			{
				if (sscanf(Response,"%f",&GoSError) == 1)
				{
					if ((Address == ActiveIR) && GoSDisplayError && !DisplayMCurrent && !DisplayMEStatus && !DisplaySEStatus)
					{
						PutString16("GoSErr",12,60,ibty+26+96,BLACK,SHECMEDGRAY);
            sprintf(s,"%f",GoSError);
            PutString16(s,12,60,ibty+26+8*16,BLACK,SHECMEDGRAY);
						IRErrChart.Add(GoSError*360.0/float(Revolution));
					}
					if (fabs(GoSError) < ErrorTolerance)
					{
						if (!inTol)
							TolTime = msTimer;
						inTol = 1;
					}
					else
						inTol = 0;
					if (inTol && ((msTimer - TolTime) > TimeTolerance))
						inTolTime = 1;
					else
						inTolTime = 0;
					UpdateStep = 381;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 381) {
			e = SerDMCCommand("INTERR=\r",Response,200);
			if (e == 0) {
				if (sscanf(Response,"%f",&GoSIntError) == 1) {
					UpdateStep = 382;
				} else {
					e = 1;
				}
			}
		} else if (UpdateStep == 382) {
			e = SerDMCCommand("OLDERR=\r",Response,200);
			if (e == 0) {
				if (sscanf(Response,"%f",&GoSOldError) == 1) {
					UpdateStep = 383;
				} else {
					e = 1;
				}
			}
		} else if (UpdateStep == 383) {
			e = SerDMCCommand("TVEL=\r",Response,200);
			if (e == 0) {
				if (sscanf(Response,"%f",&GoSTVel) == 1) {
					UpdateStep = 384;
				} else {
					e = 1;
				}
			}
		} else if (UpdateStep == 384) {
			e = SerDMCCommand("TPOS=\r",Response,200);
			if (e == 0) {
				if (sscanf(Response,"%f",&GoSTPos) == 1) {
					UpdateStep = 39;
				} else {
					e = 1;
				}
			}
		}
		else if (UpdateStep == 39)
		{
			e = SerDMCCommand("TTX\r",Response,200); // Get torque (volts)
			if (e == 0)
			{
				if (sscanf(Response,"%f",&ftemp) == 1)
				{
					Volts = ftemp * 10.0;
					DisplayVolts();
					if (Address == ActiveIR)
						IRVoltChart.Add(ftemp);
					UpdateStep = 391;
				}
				else
					e = 1;
			}
		}
		else if (UpdateStep == 391) {
			// If a move (Go, Jog, Servo, Release CASS-M2 clamp)
			// has been requested, execute
			if ((NewMove == 1) || (NewMove == 2) || (NewMove == 4) || (NewMove == 5)) {
				if ((NewMove == 1) && DMCMoving)
					UpdateStep = 40;	// Perform a stop
				else
					UpdateStep = 41;	// A stop isn't necessary
			}
			// If a change port is in progress, execute
			else if (NewMove == 6)
				UpdateStep = 49;
			// If a home is in progress, execute
			else if (Homing || (NewMove == 3))
				UpdateStep = 50;
			else if (NewMove == 7)
				UpdateStep = 46;
			else if (NewMove == 8)
				UpdateStep = 47;
			else
				UpdateStep = 30;	// Back to TP command
		}
    //////////////////////////
		// Begin moves
		else if (UpdateStep == 40)
		{
			if (!DMCMoving || (NewMove == 2) || (NewMove == 4))	// Only go if stopped or jog or servoing
				UpdateStep = 41;
			else
			{
				e = SerDMCCommand("STX\r",Response,200);		// Stop any motion
	      if (e == 0)
	      	UpdateStep = 30;												// Wait for stop
			}
    }
		else if (UpdateStep == 41)
    {
    	// If we're doing a speed servo, make sure servo program is running
    	if (NewMove == 4)
      {
        // Should we try to start the servo?
        if (!GoSStarting && (GoSFlag < 0))
          UpdateStep = 42;
        // We've tried starting the servo.. is it ready (are we in #MAIN)?
        else if (GoSFlag >= IRServoMain)
          UpdateStep = 43;
        else if (msTimer - GoSTimer > 1000)
        {
          EDSLog.Add(500,"IR servo program failed to start");
          e = 1;
        }
        // Keep checking for the program to start
        else
        	UpdateStep = 30;
      }
      else
      	// All other moves
      	UpdateStep = 43;
    }
    else if (UpdateStep == 42)
    {
      // Start servo program
      e = SerDMCCommand("XQ#AUTO\r",Response,200);
      if (e == 0)
      {
				GoSTimer = msTimer;
      	UpdateStep = 30;
			}
		}
		else if (UpdateStep == 43)
    {
    	// If we're doing a release clamp for Cass at Mag2
    	if (NewMove == 5)
      {
        if (Timer - M2Cass.ClampTimer > M2CTime)
          UpdateStep = 44;
        else
          // Keep waiting for clamps to retract
          UpdateStep = 30;
      }
      else
      	// All other moves
      	UpdateStep = 44;
    }
		else if (UpdateStep == 44)
    {
    	if (MotorPower)															// Only go if motor on
      	UpdateStep = 45;
			else
			{
				e = SerDMCCommand("SHX\r",Response,200); // Turn motor on
				if (e == 0)
					UpdateStep = 45;
			}
		}
		else if (UpdateStep == 45)
		{
			if (NewMove == 1)		// Go
			{
				sprintf(cmd,"SP%d;PA%d;BGX\r",Spd,Command);
				e = SerDMCCommand(cmd,Response,200); // Go absolute
			}
			else if (NewMove == 2)	// Jog
			{
				sprintf(cmd,"JG%d;BGX\r",JogCommand);
				e = SerDMCCommand(cmd,Response,200); // Jog
				//if (e == 0)
				//	EDSLog.Add(900,"%d  %d  %d",JogCommand,Encoder,msTimer);
			}
			else if (NewMove == 4)	// Servo
			{
				// Do some jitter correction for time lag
				// This corrects for the 1-130 msec delay between us getting
				// the GOS command from the TCS and actually sending the updated
				// command to the instrument rotator in the IR update loop.
				GoSPosition = round((float)GoSPosition+
														(float)GoSVelocity*(float)(msTimer-GoSCmdTime)/1000.0);

				sprintf(cmd,"HPOS=%d;HVEL=%d;HCMD=1\r",GoSPosition,GoSVelocity);
				e = BSerDMCCommand(cmd,Response,200); // GoS

				if (dispflag)
					//EDSLog.Add(900,"RelayT: %d",msTimer-GoSCmdTime);
					EDSLog.Add(900,"%d  %d  %d  %f",GoSPosition,GoSVelocity,msTimer,GoSError);
			}
			else if (NewMove == 5)		// Release Clamp of Cass at Mag2
			{
				sprintf(cmd,"SP%d;PA%d;BGX\r",Spd,Command);
				e = SerDMCCommand(cmd,Response,200); // Go absolute
			}
			else
				UpdateStep = 30;
			if (e == 0)
			{
				UpdateStep = 30;												// Back to normal
				LastCommand = NewMove;									// Record command type
				DisplayCommand();
				NewMove = 0;
			}
			else if (e == 1)
      	NewMove = 0;
    }

	// Engage M2 CASS clamp routine
	else if (UpdateStep == 46) {
		// Check for abort conditions
		if (!IRs[IRCASS].Enabled || (IRs[IRCASS].ErrorCount != 0)) {
			EDSLog.Add(100,"Clamps not toggled: IR not enabled/com error");
			M2Cass.ClampStep = 101;
		}
		else if (IRs[IRCASS].LockPin) {
			EDSLog.Add(100,"Clamps not toggled: Lock Pin in (unlock manually)");
			M2Cass.ClampStep = 101;
		}
		else if (IRs[IRCASS].DMCMoving) {
			EDSLog.Add(100,"Clamps not toggled: Move in progress (use STOP)");
			M2Cass.ClampStep = 101;
		}
		else if (M2Cass.LatchEngaged && M2Cass.LatchReleased) {
			EDSLog.Add(100,"Error in clamp position sensors");
			M2Cass.ClampStep = 101;
		}
		else if (!IRs[IRCASS].AmplifierPower) {
			EDSLog.Add(100,"Move ignored: Motor amplifier off (check lock pin)");
			M2Cass.ClampStep = 101;
		}
		// Are we starting the procedure?
		else if (M2Cass.Clamping == 0) {
			M2Cass.Clamping = 1;
			M2Cass.ClampStep = 0;
		}
		if (M2Cass.ClampStep == 0) {
			// Is it already clamped?
			if (M2Cass.LatchEngaged && !M2Cass.LatchReleased) {
				// nothing to do, jump to end of routine
				M2Cass.ClampStep = 100;
			}
			else {
				M2Cass.ClampStep = 1;
			}
			UpdateStep = 30;
		}
		// Motor off
		else if (M2Cass.ClampStep == 1) {
			e = SerDMCCommand("MOX\r",Response,200);
			if (e == 0) {
				if (!MotorPower) {
					M2Cass.ClampStep = 2;
				}
				UpdateStep = 30;
			}
		}
		// Engage clamp
		else if (M2Cass.ClampStep == 2) {
			e = SerDMCCommand("SB1\r",Response,200);
			if (e == 0) {
				if (Brake) {
					M2Cass.ClampTimer = Timer;
					M2Cass.ClampStep = 3;
				}
				UpdateStep = 30;
			}
		}
		// Wait for the clamp to engage
		else if (M2Cass.ClampStep == 3) {
			if (M2Cass.LatchEngaged && !M2Cass.LatchReleased) {
				M2Cass.ClampStep = 100;
			}
			else if (Timer - M2Cass.ClampTimer > M2CTime) {
				EDSLog.Add(100,"Time out clamping CASS");
				M2Cass.ClampStep = 101;
			}
			UpdateStep = 30;
		}
		else if (M2Cass.ClampStep == 100) {
			// Observing port change
			if (NewMove == 6) {
				UpdateStep = 49;
				M2Cass.ChangeStep = 15;
			}
			// Homing
			else if (NewMove == 3) {
				UpdateStep = 50;
			}
			// Back to monitoring
			else {
				NewMove = 0;
				UpdateStep = 30;
			}
			M2Cass.Clamping = 0;
			EDSLog.Add(911,"CASS clamped");
		}
		// Abort
		else if (M2Cass.ClampStep == 101) {
			M2Cass.Clamping = 0;
			if (NewMove == 6) M2Cass.Changing = 0;
			if (NewMove == 3) IRs[IRCASS].Homing = 0;
			UpdateStep = 30;
			NewMove = 0;
		}
	}
	else if (UpdateStep == 47) {
		// Check for abort conditions
		if (!IRs[IRCASS].Enabled || (IRs[IRCASS].ErrorCount != 0)) {
			EDSLog.Add(101,"Clamps not toggled: IR not enabled/com error");
			M2Cass.ClampStep = 101;
		}
		else if (IRs[IRCASS].LockPin) {
			EDSLog.Add(101,"Clamps not toggled: Lock Pin in (unlock manually)");
			M2Cass.ClampStep = 101;
		}
		else if (M2Cass.LatchEngaged && M2Cass.LatchReleased) {
			EDSLog.Add(101,"Error in clamp position sensors");
			M2Cass.ClampStep = 101;
		}
		else if (!IRs[IRCASS].AmplifierPower) {
			EDSLog.Add(101,"Move ignored: Motor amplifier off (check lock pin)");
			M2Cass.ClampStep = 101;
		}
		// Are we starting the procedure?
		else if (M2Cass.Releasing == 0) {
			M2Cass.Releasing = 1;
			M2Cass.ReleaseStep = 0;
		}
		if (M2Cass.ReleaseStep == 0) {
			// Is it already released?
			if (!M2Cass.LatchEngaged && M2Cass.LatchReleased) {
				// nothing to do, jump to end of routine
				M2Cass.ReleaseStep = 100;
			}
			else {
				M2Cass.ReleaseStep = 1;
			}
			UpdateStep = 30;
		}
		// Motor off
		else if (M2Cass.ReleaseStep == 1) {
			e = SerDMCCommand("MOX\r",Response,200);
			if (e == 0) {
				if (!MotorPower) {
					M2Cass.ReleaseStep = 2;
				}
				UpdateStep = 30;
			}
		}
		// Release clamp
		else if (M2Cass.ReleaseStep == 2) {
			e = SerDMCCommand("CB1\r",Response,200);
			if (e == 0) {
				if (!Brake) {
					M2Cass.ClampTimer = Timer;
					switch (M2Cass.M3Position) {
						case M3NASW:
						case M3NASE:
						case M3AUX1:
						case M3AUX2:
						case M3AUX3:
							// It if it locked in an observing station,
							// unlock dog
							M2Cass.ReleaseStep = 3;
							break;
						default:
							// Otherwise, just release.
							M2Cass.ReleaseStep = 10;
					}
				}
				UpdateStep = 30;
			}
		}
		// Wait for right latch to retract
		// There is no sensor to tell weather the right latch is actually
		// retracted! Just give it a little time and hope for the best.
		else if (M2Cass.ReleaseStep == 3) {
			if (Timer - M2Cass.ClampTimer > M2CTime) {
				M2Cass.ReleaseStep = 4;
			}
			UpdateStep = 30;
		}
		// Motor power
		else if (M2Cass.ReleaseStep == 4) {
			e = SerDMCCommand("SHX\r",Response,200);
			if (e == 0) {
				if (MotorPower) {
					M2Cass.ReleaseStep = 5;
				}
				UpdateStep = 30;
			}
		}
		// Small move clockwise to unlock dog
		else if (M2Cass.ReleaseStep == 5) {
			sprintf(cmd,"SP%d;PR%d;BGX\r",Spd,M2CPRRel);
			e = SerDMCCommand(cmd,Response,200);
			if (e == 0) {
				M2Cass.ReleaseStep = 51;
				UpdateStep = 30;
			}
		}
		// Wait for stop.
		else if (M2Cass.ReleaseStep == 51) {
			if (!DMCMoving) {
				M2Cass.ReleaseStep = 6;
			}
			UpdateStep = 30;
		}
		// And move back to position
		else if (M2Cass.ReleaseStep == 6) {
			sprintf(cmd,"SP%d;PR%d;BGX\r",Spd,-M2CPRRel);
			e = SerDMCCommand(cmd,Response,200);
			if (e == 0) {
				M2Cass.ReleaseStep = 61;
				UpdateStep = 30;
			}
		}
		// Wait for stop.
		else if (M2Cass.ReleaseStep == 61) {
			if (!DMCMoving) {
				M2Cass.ReleaseStep = 20;
			}
			UpdateStep = 30;
		}
		// Wait for the clamp to release
		else if (M2Cass.ReleaseStep == 10) {
			if (Timer - M2Cass.ClampTimer > M2CTime) {
				EDSLog.Add(101,"Time out releasing CASS");
				M2Cass.ReleaseStep = 101;
			}
			else {
				M2Cass.ReleaseStep = 100;
			}
			UpdateStep = 30;
		}
		else if (M2Cass.ReleaseStep == 20) {
			if (!M2Cass.LatchEngaged && M2Cass.LatchReleased) {
				M2Cass.ReleaseStep = 100;
			}
			else {
				EDSLog.Add(101,"Error releasing CASS");
				M2Cass.ReleaseStep = 101;
			}
			UpdateStep = 30;
		}
		else if (M2Cass.ReleaseStep == 100) {
			// Observing port change
			if (NewMove == 6) {
				UpdateStep = 49;
				M2Cass.ChangeStep = 1;
			}
			// Homing
			else if (NewMove == 3) {
				UpdateStep = 50;
				HomeStep = 2;
			}
			// Back to monitoring
			else {
				NewMove = 0;
				UpdateStep = 30;
			}
			M2Cass.Releasing = 0;
			EDSLog.Add(911,"CASS released");
		}
		else if (M2Cass.ReleaseStep == 101) {
			M2Cass.Releasing = 0;
			if (NewMove == 6) M2Cass.Changing = 0;
			if (NewMove == 3) IRs[IRCASS].Homing = 0;
			UpdateStep = 30;
			NewMove = 0;
		}
	}

    /////////////////////////////////
		// Observing Port change routine
		else if (UpdateStep == 49)
		{
      if (NewMove == 6)
      {
        // If we're at the requested observing port
        if ((M2Cass.Target == M2Cass.M3Position) &&
			(!M2Cass.Changing) &&
			(M2Cass.LatchEngaged) &&
			!(M2Cass.LatchReleased))
        {
          M2Cass.ChangeStep = 0;
					NewMove = 0;
					M2Cass.Changing = 0;
          UpdateStep = 30;
          EDSLog.Add(985,"CASS change port operation completed");
      	}
        else if (!M2Cass.Changing)
        {
          if (Homed)
          {
        	  M2Cass.Changing = 1;
					  M2Cass.ChangeStep = 0;					// Prepare
          }
					else
					{
						// CASS not homed, ignore change port operation
            M2Cass.ChangeStep = 0;
						NewMove = 0;
						M2Cass.Changing = 0;
            UpdateStep = 30;
            EDSLog.Add(85,"CASS IR not homed");
					}
        }
        // Start checking clamps status
        if (M2Cass.ChangeStep == 0)
        {
          // Latches already released
          if (!M2Cass.LatchEngaged && M2Cass.LatchReleased)
            M2Cass.ChangeStep = 1;
          // Latches engaged
          else if ((M2Cass.LatchEngaged && !M2Cass.LatchReleased) ||
                   (!M2Cass.LatchEngaged && !M2Cass.LatchReleased))
            M2Cass.ChangeStep = 2;
          else if (M2Cass.LatchEngaged && M2Cass.LatchReleased)
					{
						// Latch sensors in weird state, ignore change port operation
						NewMove = 0;
						M2Cass.Changing = 0;
            EDSLog.Add(85,"Latch position sensor error, port change aborted");
					}
          UpdateStep = 30;
        }
        else if (M2Cass.ChangeStep == 1)
        {
          // Turn motor on
          e = SerDMCCommand("SHX\r",Response,200);
          if (e == 0)
          {
          	if (MotorPower)
							M2Cass.ChangeStep = 8; // Wait for power on
           	UpdateStep = 30;
          }
        }
		// Call release clamp routine
		else if (M2Cass.ChangeStep == 2) {
			UpdateStep = 47;
		}
				else if (M2Cass.ChangeStep == 8)
				{
				  sprintf(cmd,"SP%d;PA%d;BGX\r",Spd,Command);
				  e = SerDMCCommand(cmd,Response,200); // Go absolute
					//Wait for movement to start
					if (e == 0)
					{
					  M2Cass.ChangeStep = 9;
						UpdateStep = 30;
            EDSLog.Add(985,"Moving to target port...");
          }
        }
				else if (M2Cass.ChangeStep == 9)
				{
          // If motion has stopped we were interrupted by the user or got lost
          if (!MotorPower)
          {
					  NewMove = 0;
					  M2Cass.Changing = 0;
            EDSLog.Add(85,"Port change aborted by user");
				  }
          else if (!DMCMoving)
					  M2Cass.ChangeStep = 10; // Wait for stop
          UpdateStep = 30;
        }
				else if (M2Cass.ChangeStep == 10)
				{
          if (M2Cass.Target == M2Cass.M3Position)
            M2Cass.ChangeStep = 11;
          else
          {
            // Not in any clamping position, we got lost
					  NewMove = 0;
					  M2Cass.Changing = 0;
            UpdateStep = 30;
            EDSLog.Add(85,"Port change aborted, got lost, try again");
          }
        }
        // Call engage clamp routine
		else if (M2Cass.ChangeStep == 11) {
			UpdateStep = 46;
		}
				else if (M2Cass.ChangeStep == 15)
				{
					NewMove = 0;
					M2Cass.Changing = 0;
          UpdateStep = 30;
          EDSLog.Add(985,"Change Port operation successful");
				}
      }
			// Change Port was aborted by an error or user intervention
			else
      {
      	UpdateStep = 30;
        M2Cass.Changing = 0;
      }
    }
    /////////////////////////////////

    /////////////////////////////////
		// Homing routine
		else if (UpdateStep == 50)
		{
			if ((Telescope == 1) && (CurrentIR == IRCASS))\
			{
				HomeStep = 0;
				NewMove = 0;
				Homing = 0;
				UpdateStep = 30;
				EDSLog.Add(80,"%s IR homing aborted, not operative",IRNames[Address]);
			}
		// Homing using encoder index mark
			else if ((Telescope == 2) &&
				 ((CurrentIR == IRNASW) ||
				  (CurrentIR == IRAUX2) ||
				  (CurrentIR == IRNASE)))
			{
    	  if (NewMove == 3)
        {
      	  if (!Homing)
          {
        	  Homing = 1;

		HomeEncStart = Encoder;
					  if (DMCMoving)
	            HomeStep = 0;					// Perform a stop
            else if (!MotorPower)
          	  HomeStep = 1;					// Enable motor power
            else if (Limit == IRLNone)
						  HomeStep = 2;					// Prepare latch for home
            else
            {
          	  // We're in some weird state, ignore the home
              HomeStep = 0;
              NewMove = 0;
              Homing = 0;
              UpdateStep = 30;
							EDSLog.Add(80,"%s IR homing aborted",IRNames[Address]);
            }
          }
      	  // Stop any motion
				  if (HomeStep == 0)
          {
	    	    e = SerDMCCommand("STX\r",Response,200);		// Stop any motion
	          if (e == 0)
            {
          	  if (!DMCMoving)
								HomeStep = 1;													// Wait for stop
           	  UpdateStep = 30;
            }
          }// End home step 0
          // Enable motor power
          else if (HomeStep == 1)
          {
	    	    e = SerDMCCommand("SHX\r",Response,200);		// Enable motor power
	          if (e == 0)
            {
          	  if (MotorPower)
		      	    HomeStep = 2;													// Wait for power on
           	  UpdateStep = 30;
            }
				  }// End home step 1
          // Begin move
          else if (HomeStep == 2)
					{
						if (MotorPower && !DMCMoving)
						{
							// Start to move CCW/CW until change of zone
							if (Zone == IRZCW)
								sprintf(cmd,"SP%d;PR%d;BGX\r",Spd,-MinHomeDistance);
							else if (Zone == IRZCCW)
								sprintf(cmd,"SP%d;PR%d;BGX\r",Spd,MinHomeDistance);
						  e = SerDMCCommand(cmd,Response,200);
              //Wait for movement to start
		          if (e == 0)
						  {
							  zoneflag = Zone;
           		  HomeStep = 3;
           		  UpdateStep = 30;
	            }
	          }
						// Abort home if necessary home conditions weren't met
            else
            {
          	  NewMove = 0;
              Homing = 0;
              UpdateStep = 30;
							EDSLog.Add(81,"%s IR homing aborted",IRNames[Address]);
            }
          }// End home step 2
          // Wait for zone to change or motion to stop
          else if (HomeStep == 3)
          {
        	  // If zone changed, stop movement
        	  if (zoneflag != Zone)
					  {
          	  HomeStep = 4;
              UpdateStep = 30;
            }
            // If motion has stopped we were interrupted by the user or got lost
            else if (!DMCMoving)
						{
           	  Homing = 0;
              NewMove = 0;
              UpdateStep = 30;
							EDSLog.Add(82,"%s IR homing aborted",IRNames[Address]);
            }
            // Keep waiting
            else
          	  UpdateStep = 30;
          } // End home step 3
      	  // Stop any motion
      	  if (HomeStep == 4)
          {
	    	    e = SerDMCCommand("STX\r",Response,200);	// Stop any motion
					  if (e == 0)
            {
          	  if (!DMCMoving)
		      	    HomeStep = 5;													// Wait for stop
           	  UpdateStep = 30;
            }
					}// End home step 4
					// In the new zone, prepare to find index:
					else if (HomeStep == 5) {
						if ((Telescope == 2) &&
							(CurrentIR == IRNASE)) {
							sprintf(cmd,"JG%d;FIX;BGX\r",- HomeJogSpeed);
						}
						else {
							sprintf(cmd,"JG%d;FIX;BGX\r",HomeJogSpeed);
						}
						e = SerDMCCommand(cmd,Response,200);
            // Wait for movement to change/continue
		        if (e == 0)
					  {
					 	  UpdateStep = 30;
              HomeStep = 6;
            }
				  }// End home step 5
          // Wait for motion to stop
		else if (HomeStep == 6)
          {
			// Wait for motion to stop
            if (!DMCMoving)
			{
				HomeStep = 12;
			}
            UpdateStep = 30;
		}//End home step 6
          // Stop motion
          else if (HomeStep == 12)
          {
	    	    e = SerDMCCommand("STX\r",Response,200);		// Stop any motion
	          if (e == 0)
            {
          	  // Wait for stop
          	  if (!DMCMoving)
		      	    HomeStep = 13;
           	  UpdateStep = 30;
            }
          } // End home step 12
          // Read index position
				  else if (HomeStep == 13)
          {
	    	    e = SerDMCCommand("TP\r",Response,200);		// Read index position
	          if (e == 0)
            {
          	  if (sscanf(Response,"%d",&LatchedEncoder) != 1)
								e = 1;
              else
              {
								EDSLog.Add(984,"%s index mark at %d",IRNames[Address],LatchedEncoder);
            	  HomeStep = 14;		// Wait for motion to stop completely
                StableCount = 0;
	           	  UpdateStep = 30;
              }
            }
          } // End home step 13
          // Wait for speed to come to zero
				  // (so that we set the encoder accurately, not while moving)
          else if (HomeStep == 14)
          {
        	  // Wait for motion to come to a complete stop
					  if (abs(Velocity) < 2)
          	  StableCount++;
            else
          	  StableCount = 0;
            if (StableCount > 5)
          	  HomeStep = 15;
						UpdateStep = 30;
						// If motion has started something's wrong, so abort home
            if (DMCMoving)
        	  {
         	    Homing = 0;
              NewMove = 0;
              UpdateStep = 30;
							EDSLog.Add(87,"%s IR homing aborted",IRNames[Address]);
            }
          } // End home step 14
          // Set homed encoder location
          else if (HomeStep == 15)
          {
        	  if (!DMCMoving)
					  {
          	  // Set homed encoder position
							sprintf(cmd,"DP%d\r",Encoder);
		    	    e = SerDMCCommand(cmd,Response,200);
              // Success! Home completed
		          if (e == 0)
	            {
								Homed = 1;
                Homing = 0;
                NewMove = 0;
      	     	  UpdateStep = 30;
								EDSLog.Add(985,"%s position set to %d",IRNames[Address],Encoder);
								EDSLog.Add(985,"%s Instrument Rotator homed",IRNames[Address]);
              }
            }
						// If motion has started something's wrong, so abort home
            else
					  {
           	  Homing = 0;
              NewMove = 0;
              UpdateStep = 30;
							EDSLog.Add(88,"%s IR homing aborted",IRNames[Address]);
            }
					} // End home step 15

		// This step is jumped into if something happens that
		// needs to stop the rotator and abort homing.
		else if (HomeStep == 16) {
			// Stop any motion
			e = SerDMCCommand("STX\r",Response,200);
			// Wait to stop and abort homing
			if (e == 0) {
				if (!DMCMoving) {
					Homing = 0;
					NewMove = 0;
					EDSLog.Add(89,"%s IR homing aborted: switch not found",IRNames[Address]);
				}
				UpdateStep = 30;
			}
		}

        }
        // Homing was aborted by an error or user intervention
        else
        {
      	  UpdateStep = 30;
          Homing = 0;
        }
			}
			// End of homing routine using encoder index mark

			// Homing using high speed capture latch
			else if ((Telescope == 1) ||
				 ((Telescope == 2) &&
				  (CurrentIR == IRCASS)))
			{
        // These have sector switch
    	  if (NewMove == 3)
        {
      	  if (!Homing)
          {
        	  Homing = 1;

		HomeEncStart = Encoder;

					  if (DMCMoving)
	            HomeStep = 0;					// Perform a stop
            else if (!MotorPower)
          	  HomeStep = 1;					// Enable motor power
            else if (Limit == IRLNone)
						  HomeStep = 2;					// Prepare latch for home
            else
            {
          	  // We're in some weird state, ignore the home
              HomeStep = 0;
              NewMove = 0;
              Homing = 0;
              UpdateStep = 30;
							EDSLog.Add(80,"%s IR homing aborted",IRNames[Address]);
            }
          }
      	  // Stop any motion
				  if (HomeStep == 0)
          {
	    	    e = SerDMCCommand("STX\r",Response,200);		// Stop any motion
	          if (e == 0)
            {
          	  if (!DMCMoving)
								HomeStep = 1;													// Wait for stop
           	  UpdateStep = 30;
            }
          }// End home step 0
          // Enable motor power
          else if (HomeStep == 1)
          {
			if (CurrentIR == IRCASS) {
				UpdateStep = 47;
			}
			else {
	    	    e = SerDMCCommand("SHX\r",Response,200);		// Enable motor power
	          if (e == 0)
            {
          	  if (MotorPower)
		      	    HomeStep = 2;													// Wait for power on
           	  UpdateStep = 30;
            }
			}
				  }// End home step 1
          // Begin move
          else if (HomeStep == 2)
					{
						if (MotorPower && !DMCMoving)
						{
							// Start to move CCW/CW until change of zone
							if (Zone == IRZCW)
								sprintf(cmd,"SP%d;PR%d;BGX\r",Spd,-MinHomeDistance);
							else if (Zone == IRZCCW)
								sprintf(cmd,"SP%d;PR%d;BGX\r",Spd,MinHomeDistance);
						  e = SerDMCCommand(cmd,Response,200);
              //Wait for movement to start
		          if (e == 0)
						  {
							  zoneflag = Zone;
           		  HomeStep = 3;
           		  UpdateStep = 30;
	            }
	          }
						// Abort home if necessary home conditions weren't met
            else
            {
          	  NewMove = 0;
              Homing = 0;
              UpdateStep = 30;
							EDSLog.Add(81,"%s IR homing aborted",IRNames[Address]);
            }
          }// End home step 2
          // Wait for zone to change or motion to stop
          else if (HomeStep == 3)
          {
        	  // If zone changed, stop movement
        	  if (zoneflag != Zone)
					  {
          	  HomeStep = 4;
              UpdateStep = 30;
            }
            // If motion has stopped we were interrupted by the user or got lost
            else if (!DMCMoving)
						{
           	  Homing = 0;
              NewMove = 0;
              UpdateStep = 30;
							EDSLog.Add(82,"%s IR homing aborted",IRNames[Address]);
            }
            // Keep waiting
            else
          	  UpdateStep = 30;
          } // End home step 3
      	  // Stop any motion
      	  if (HomeStep == 4)
          {
	    	    e = SerDMCCommand("STX\r",Response,200);	// Stop any motion
					  if (e == 0)
            {
          	  if (!DMCMoving)
				// NASE rotator at Baade is the only one that approaches
				// the index mark from different directions depending
				// on what zone it starts the homing. This little hack
				// forces it to approach the index mark always from the CW
				// zone, moving in the CCW direction. This is necessary
				// due to instabilities on the index detection on the CCW
				// side of the pulse.
				if (((Telescope == 1) &&
					 (CurrentIR == IRNASE) &&
					 (Zone == IRZCCW)) ||
					((Telescope == 2) &&
					 (CurrentIR == IRCASS) &&
					 (Zone == IRZCCW))) {
					HomeStep = 2;
				}
				else {
					HomeStep = 41;	// Wait for stop
				}
           	  UpdateStep = 30;
            }
					}// End home step 4

			else if (HomeStep == 41) {
				if (((Telescope == 1) &&
				     (CurrentIR == IRNASE) &&
				     (Zone == IRZCW)) ||
				    ((Telescope == 2) &&
				     (CurrentIR == IRCASS) &&
				     (Zone == IRZCW))) {
					sprintf(cmd,"AL;JG%d;BGX\r",-HomeJogSpeed);
				}
				else {
					sprintf(cmd,"AL;JG%d;BGX\r",HomeJogSpeed);
				}
				e = SerDMCCommand(cmd,Response,200);
				if (e == 0) {
					EDSLog.Add(984,"Moving to %s latch",IRNames[Address]);
					HomeStep = 42;
					UpdateStep = 30;
				}
			}
			else if (HomeStep == 42) {
				if(IndexLatched) {
					HomeStep = 43;
				}
				else if (!DMCMoving) {
					Homing = 0;
					NewMove = 0;
					EDSLog.Add(82,"%s IR homing aborted",IRNames[Address]);
				}
				UpdateStep = 30;
			}
			else if (HomeStep == 43) {
				e = SerDMCCommand("STX\r",Response,200);
				if (e == 0) {
					UpdateStep = 30;
					if (!DMCMoving) {
						HomeStep = 44;
					}
				}
			}
			else if (HomeStep == 44) {
				e = SerDMCCommand("RL\r",Response,200);
				if (e == 0) {
					UpdateStep = 30;
					if (sscanf(Response,"%d",&LatchedEncoder) != 1) {
						e = 1;
					} else {
						EDSLog.Add(984,"%s encoder latch found at %d",
							IRNames[Address],
							LatchedEncoder);
						EDSLog.Add(984,"%s drift from last homing: %+6.3f",
							IRNames[Address],
							(float)LatchedEncoder*(360.0/(float)Revolution));
						HomeStep = 12;
					}
				}
			}
          // Stop motion
          else if (HomeStep == 12)
          {
	    	    e = SerDMCCommand("STX\r",Response,200);		// Stop any motion
	          if (e == 0)
            {
          	  // Wait for stop
          	  if (!DMCMoving)
				HomeStep = 14;
           	  UpdateStep = 30;
            }
          } // End home step 12
          // Wait for speed to come to zero
				  // (so that we set the encoder accurately, not while moving)
          else if (HomeStep == 14)
          {
        	  // Wait for motion to come to a complete stop
					  if (abs(Velocity) < 2)
          	  StableCount++;
            else
          	  StableCount = 0;
            if (StableCount > 5)
          	  HomeStep = 15;
						UpdateStep = 30;
						// If motion has started something's wrong, so abort home
            if (DMCMoving)
        	  {
         	    Homing = 0;
              NewMove = 0;
              UpdateStep = 30;
							EDSLog.Add(87,"%s IR homing aborted",IRNames[Address]);
            }
          } // End home step 14
          // Set homed encoder location
          else if (HomeStep == 15)
          {
        	  if (!DMCMoving)
					  {
          	  // Set homed encoder position
							sprintf(cmd,"DP%d\r",Encoder - LatchedEncoder);
		    	    e = SerDMCCommand(cmd,Response,200);
              // Success! Home completed
		          if (e == 0)
	            {
					HomeStep = 150;
					UpdateStep = 30;
					EDSLog.Add(985,"%s position set to %d",IRNames[Address],Encoder - LatchedEncoder);
					EDSLog.Add(985,"%s Instrument Rotator homed",IRNames[Address]);
				}
            }
						// If motion has started something's wrong, so abort home
            else
					  {
           	  Homing = 0;
              NewMove = 0;
              UpdateStep = 30;
							EDSLog.Add(88,"%s IR homing aborted",IRNames[Address]);
            }
					} // End home step 15

			// Left latch armed for drift monitoring
			else if (HomeStep == 150) {
				e = SerDMCCommand("AL\r",Response,200);
				if (e == 0) {
					Homed = 1;
					Homing = 0;
					NewMove = 0;
					UpdateStep = 30;
				}
			}

		// This step is jumped into if something happens that
		// needs to stop the rotator and abort homing.
		else if (HomeStep == 16) {
			// Stop any motion
			e = SerDMCCommand("STX\r",Response,200);
			// Wait to stop and abort homing
			if (e == 0) {
				if (!DMCMoving) {
					Homing = 0;
					NewMove = 0;
					EDSLog.Add(89,"%s IR homing aborted: switch not found",IRNames[Address]);
				}
				UpdateStep = 30;
			}
		}

        }
        // Homing was aborted by an error or user intervention
        else
        {
      	  UpdateStep = 30;
          Homing = 0;
        }
      }
      // End of homing routine for NASW,NASE,AUX1,AUX2,AUX3 for MAG1,MAG2
    }
    // End of homing routine.

		// On an error reinitialize the DMC
		if (e == 1)
    {
			UpdateStep = 0;
      NewMove = 0;
      Homing = 0;
      M2Cass.Changing = 0;
      GoSStarting = 0;
		}
    return(e);
	}
	else
  	return(0);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::Enable - Enable DMC communication
void CIR::Enable()
{
	if (!Enabled)
  {
	  Enabled = 1;
	  UpdateStep = 0;
	  DMCCommandStep = 0;
	  DMCReceiveStep = 0;
    ErrorCount = 0;
    CommandCount = 0;
		NewMove = 0;
    Homing = 0;
    Command = 0;
		JogCommand = 0;
		GoSPosition = 0;
		GoSVelocity = 0;
		DisplayStatus();
    GoSDisplayError = 0;
    DisplayMCurrent = 0;
    DisplayMEStatus = 0;
    DisplaySEStatus = 0;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CIR::Disable - Disable DMC communication
void CIR::Disable()
{
	Enabled = 0;
  DisplayAll();
}

/*
/////////////////////////////////////////////////////////////////////////////
// CIR::Free - Free instrument rotator to be moved manually
void CIR::Free()
// WARNING!! THIS ROUTINE IS VERY DANGEROUS!
// If the instrument rotator is freed to rotate, and an out of balance
// instrument is connected, the instrument will spin, possibly
// hurting/killing someone and/or destroying the instrument.
// Use with extreme caution, and only when you HAVE to move the instrument
// rotator by hand.
//
// You should give the Reset command for this instrument rotator after
// you are done to re-enable error checking
{
	char c;
	char tmp[255];

  // Only free motion if the DMC is on, apparently working and LockPin is out
  if (Enabled && (ErrorCount == 0) && !LockPin && AmplifierPower)
	{
  	// Halt system updates
  	PauseUpdate();
    // Query user to make sure they really want to do this
		InputLog.Add("  WARNING!!",BRIEF);
		InputLog.Add("  The FREE command",BRIEF);
		InputLog.Add("  is very dangerous!",BRIEF);
		InputLog.Add("  Proceed? (Y/N)",BRIEF);
    InputLog.DisplayAll();
		while (!kbhit())
			Watchdog();
		c = toupper(getch());
		InputLog.Add("* %c",BRIEF,c);
		if (c != 'Y')
			return;

    // Release clamp at CASS-Mag2
    if ((CurrentIR == IRCASS) && (Telescope == 2))
		{
      BSerDMCCommand("CB1;SHX\r",Response,200);
      sprintf(tmp,"SP%d;PR%d;BGX\r",M2CSPRel,M2CPRRel);
		  BSerDMCCommand(tmp,Response,200);
    }
    // Turn off motor
		BSerDMCCommand("STX;AMX;MOX\r",Response,200);
    // Disable error checking
		BSerDMCCommand("OE0\r",Response,200);
  }
  else if (LockPin)
		EDSLog.Add(101,"Free command ignored: Lock Pin in");
  else if (!AmplifierPower)
    EDSLog.Add(101,"Free command ignored: Motor amplifier off (check lock pin)");
  else
		EDSLog.Add(101,"Free command ignored: IR not enabled/error");
}
*/

/////////////////////////////////////////////////////////////////////////////
// CIR::Stop - Stop motion
void CIR::Stop()
{
  if (Enabled)
  {
    PauseIR();
		BSerDMCCommand("STX\r",Response,200);
    NewMove = 0;			// Prevent a move being processed from starting
	}
  else
		EDSLog.Add(102,"Stop ignored: IR not enabled");
}

/////////////////////////////////////////////////////////////////////////////
// CIR::TogglePower - Toggle IRs motor power
void CIR::TogglePower()
{
  // Only toggle the power if the DMC is on and apparently working
  if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(103,"Power not toggled: IR not enabled/com error");
	else if (DMCMoving)
		EDSLog.Add(103,"Power not toggled: Move in progress");
	else if (!AmplifierPower && !ErrorFlag)
		EDSLog.Add(103,"Power not toggled: Motor amplifier off (check lock pin)");
  else
  {
		// Let current operation finish
    PauseIR();
    // Toggle the motor power, motor off/motor on
		if (MotorPower)
			BSerDMCCommand("MOX\r",Response,200);
		else
			BSerDMCCommand("SHX\r",Response,200);
  }
}

/////////////////////////////////////////////////////////////////////////////
// CIR::ServoOff - Turn off gently Galil's built in servo (or motor power)
void CIR::ServoOff()
{
  // Only turn servo off if the DMC is on
  if (!Enabled)
		EDSLog.Add(103,"Could not put servo off: IR not enabled");
  else if (MotorPower)
  {
    // Turn off motor servo
	  BSerDMCCommand("STX;AMX;MOX\r",Response,200);
    NewMove = 0;			// Prevent a move being processed from starting
		EDSLog.Add(986,"Rotator%d servo off (check instrument lock pin)",CurrentIR);
  }
}

/////////////////////////////////////////////////////////////////////////////
// CIR::MoveSteps - Move given number of steps. Uses "IP" command
void CIR::MoveSteps(int steps)
{
	char s[50];

  // Only move if the DMC is on, apparently working and LockPin is out
  if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(104,"Move ignored: IR not enabled/error");
  else if (LockPin)
		EDSLog.Add(104,"Move ignored: LockPin in");
	else if (!AmplifierPower)
    EDSLog.Add(104,"Move ignored: Motor amplifier off (check lock pin)");
	else if (NewMove)
		EDSLog.Add(104,"Move ignored: Another move is being processed");
	else if ((steps < 0) && (CCWSLimitS == 1))
		EDSLog.Add(104,"Move Ignored: CCW Soft limit active");
	else if ((steps > 0) && (CWSLimitS == 1))
		EDSLog.Add(104,"Move Ignored: CW Soft limit active");
	else
  {
	  if ((CurrentIR == IRCASS) && (Telescope == 2))
      if (M2Cass.LatchEngaged && !M2Cass.LatchReleased)
		  {
        EDSLog.Add(104,"Move ignored: Clamp engaged");
        return;
      }
		// Let current operation finish
    PauseIR();
    // Turn motor power on if necessary
		if (!MotorPower)
			BSerDMCCommand("SHX\r",Response,200);
    // Go given number of steps
    sprintf(s,"IP%d\r",steps);
    BSerDMCCommand(s,Response,200);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CIR::MoveAngle - Move given number of degrees. Uses "IP" command
void CIR::MoveAngle(float angle)
{
  char s[50];
  int steps;

  // Only move if the DMC is on, apparently working and LockPin is out
  if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(105,"Move ignored: IR not enabled/error");
  else if (LockPin)
		EDSLog.Add(105,"Move ignored: LockPin in");
	else if (!AmplifierPower)
		EDSLog.Add(105,"Move ignored: Motor amplifier off (check lock pin)");
	else if (NewMove)
		EDSLog.Add(105,"Move ignored: Another move is being processed");
	else if ((angle < 0) && (CCWSLimitS == 1))
		EDSLog.Add(105,"Move Ignored: CCW Soft limit active");
	else if ((angle > 0) && (CWSLimitS == 1))
		EDSLog.Add(105,"Move Ignored: CW Soft limit active");

  else
	{
	  if ((CurrentIR == IRCASS) && (Telescope == 2))
      if (M2Cass.LatchEngaged && !M2Cass.LatchReleased)
      {
		    EDSLog.Add(105,"Move ignored: Clamp engaged");
        return;
      }
		// Let current operation finish
    PauseIR();
		// Turn motor power on if necessary
    if (!MotorPower)
			BSerDMCCommand("SHX\r",Response,200);
		steps = round(angle * (float)Revolution / 360.0);
    sprintf(s,"IP%d\r",steps);
		BSerDMCCommand(s,Response,200);
    Command += steps;
		DisplayCommand();
  }
}

/////////////////////////////////////////////////////////////////////////////
// CIR::Go - Go to given angle (in degrees)
void CIR::Go(float angle,int relative)
{
	int target;

    // Calculate target command
    if (relative)
			target = Encoder + round(angle * (float)Revolution / 360.0);
    else
			target = round((angle * (float)Revolution / 360.0) + CCWHome);

  // Only move if the DMC is on, apparently working and the LockPin is out
  if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(106,"Move ignored: IR not enabled/error");
  else if (LockPin)
		EDSLog.Add(106,"Move ignored: LockPin in");
	else if (!AmplifierPower)
		EDSLog.Add(106,"Move ignored: Motor amplifier off (check lock pin)");
	else if (NewMove)
		EDSLog.Add(106,"Move ignored: Another move is being processed");
	else if ((target < Encoder) && (CCWSLimitS == 1))
		EDSLog.Add(106,"Move Ignored: CCW Soft limit active");
	else if ((target > Encoder) && (CWSLimitS == 1))
		EDSLog.Add(106,"Move Ignored: CW Soft limit active");

  else
	{
	  if ((Address == IRCASS) && (Telescope == 2))
      if (M2Cass.LatchEngaged && !M2Cass.LatchReleased)
      {
        EDSLog.Add(106,"Move ignored: Clamp engaged");
        return;
      }
  	NewMove = 1;	// Flag that a move has been requested
	Command = target;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CIR::Jog - Move at given angular velocity (in degrees/sec)
void CIR::Jog(float speed)
{
	// Only move if the DMC is on, apparently working and the LockPin is out
  if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(107,"Move ignored: IR not enabled/error");
  else if (LockPin)
		EDSLog.Add(107,"Move ignored: LockPin in");
	else if (!AmplifierPower)
		EDSLog.Add(107,"Move ignored: Motor amplifier off (check lock pin)");
	else if (NewMove)
		EDSLog.Add(107,"Move ignored: Another move is being processed");
	else if ((speed < 0) && (CCWSLimitS == 1))
		EDSLog.Add(107,"Move Ignored: CCW Soft limit active");
	else if ((speed > 0) && (CWSLimitS == 1))
		EDSLog.Add(107,"Move Ignored: CW Soft limit active");
  else
  {
	  if ((CurrentIR == IRCASS) && (Telescope == 2))
      if (M2Cass.LatchEngaged && !M2Cass.LatchReleased)
      {
        EDSLog.Add(107,"Move ignored: Clamp engaged");
        return;
      }
  	NewMove = 2;	// Flag that a jog has been requested
    // Calculate target speed
		JogCommand = round(speed * (float)Revolution / 360.0);
	}
}

/////////////////////////////////////////////////////////////////////////////
// CIR::GoS - Go to given angle (in degrees) and given speed (deg/sec)
// at instant time using a servo
void CIR::GoS(float angle,float veloc,char *time)
{
	int h,m,s,hs;
	struct dostime_t t;

	// Only move if the DMC is on, apparently working and the LockPin is out
	if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(108,"Move ignored: IR not enabled/error");
  else if (LockPin)
		EDSLog.Add(108,"Move ignored: LockPin in");
	else if (!AmplifierPower)
		EDSLog.Add(108,"Move ignored: Motor amplifier off (check lock pin)");
	else if ((NewMove == 1)||(NewMove == 2)||(NewMove == 3))
		EDSLog.Add(108,"Move ignored: Another move is being processed");
	else if (GoSFlag == 0)
		EDSLog.Add(108,"Move ignored: IR servo program not running");
	else if ((veloc < 0) && (CCWSLimitS == 1))
		EDSLog.Add(108,"Move Ignored: CCW Soft limit active");
	else if ((veloc > 0) && (CWSLimitS == 1))
		EDSLog.Add(108,"Move Ignored: CW Soft limit active");
	else
	{
	  if ((CurrentIR == IRCASS) && (Telescope == 2))
      if (M2Cass.LatchEngaged && !M2Cass.LatchReleased)
      {
        EDSLog.Add(108,"Move ignored: Clamp engaged");
        return;
	    }
		NewMove = 4;	// Flag that a servo move has been requested

		// Record the time we recieved this command so that we can do jitter
		// correction for Vane program.
		GoSCmdTime = msTimer;

		//if (dispflag)
		//	EDSLog.Add(900,"%f  %f  %s",angle,veloc,time);

		// Do jitter correction for Vane - TCS time lag (serial link).
		// This accounts for the time elapsed between the TCS sending the GoS
		// command and the Vane computer recieving it.
		// Vane serial parsing is neglected.
		gettime(&t);
		VaneUTTime = t.hour*60*60*100+t.minute*60*100+t.second*100+t.hsecond;
		sscanf(time,"%2d%2d%2d%2d",&h,&m,&s,&hs);
		TCSUTTime = h*60*60*100+m*60*100+s*100+hs;
		angle = angle + veloc * (float)(abs(VaneUTTime-TCSUTTime)) / 100.0;

		//EDSLog.Add(900,"RelayT: %d ms",(VaneUTTime-TCSUTTime)*10);
		//EDSLog.Add(900,"%d  %d  %f",VaneUTTime*10,TCSUTTime*10,angle);

		// Calculate target command in encoder counts
		GoSPosition = round((angle * (float)Revolution / 360.0) + CCWHome);
		GoSVelocity = round(veloc * (float)Revolution / 360.0);

		// Update display with target position
		Command = GoSPosition;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CIR::Home - Home instrument rotator (go to index and set encoders)
void CIR::Home()
{
  // Only move if the DMC is on, apparently working and the LockPin is out
  if (!Enabled || (ErrorCount != 0))
		EDSLog.Add(110,"Home ignored: IR not enabled/error");
  else if (LockPin)
		EDSLog.Add(110,"Home ignored: LockPin in");
	else if (!AmplifierPower)
		EDSLog.Add(110,"Home ignored: Motor amplifier off (check lock pin)");
	else if (NewMove)
		EDSLog.Add(110,"Home ignored: Another move is being processed");
  else
  {
  	NewMove = 3;	// Flag that a home has been requested
  }
}

/////////////////////////////////////////////////////////////////////////////
// CIR::Reset - Reset communcation and rewrite all config info to DMC
void CIR::Reset()
{
	// Only finish update if DMC is enabled
	if (Enabled)
		PauseIR(); // Let current operation finish
	// Reset communication
	UpdateStep = 0;
	DMCCommandStep = 0;
	DMCReceiveStep = 0;
	ErrorCount = 0;
	CommandCount = 0;
	NewMove = 0;
	Homing = 0;
	Command = 0;
	JogCommand = 0;
	GoSPosition = 0;
	GoSVelocity = 0;
	DisplayStatus();
}

/////////////////////////////////////////////////////////////////////////////
// CIR::ResetParams - Reset IR PID and speed params
void CIR::ResetParams()
{
	// Only reset DMC params if no errors and DMC is enabled
	if (Enabled && (ErrorCount == 0))
	{
		// Let current operation finish
		PauseIR();
		// Reset parameters
	  UpdateStep = 12;
  }
}

/////////////////////////////////////////////////////////////////////////////
// CIR::SerDMCReceive - Wait for a response from a DMC
int CIR::SerDMCReceive(char *rsp,int Timeout)
// Put the DMC's (on port IRPort) response in rsp, and wait for Timeout
// milliseconds before giving up.
// Returns a 0 on success, 1 on a failure, -1 on not done yet
// See the DMC manual for more info on communication with the DMC cards.
{
	int ResponseDone = 0;

	if (DMCReceiveStep == 0)
	{
		ResponseLength = 0;
		ResponsesGotten = 0;
		DMCTime = Timer; 	// Record the current time to prepare to wait
		DMCReceiveStep = 1;
		return(-1);
	}
	else if (DMCReceiveStep == 1)
	{
		// Wait for response
		while (SerialCharReady(IRPort) && (ResponseLength < 79))
		{
			rsp[ResponseLength++] = SerialGetChar(IRPort);
			if (rsp[ResponseLength-1] == ':')
      {
      	ResponsesGotten++;
        if (ResponsesNeeded == ResponsesGotten)
					ResponseDone = 1;
      }
		}
		// Null-terminate the string
		rsp[ResponseLength] = 0;

		// Stop getting data when a ':' is received, or the timeout has been reached.
		// (Timer - DMCTime) gives the time in milliseconds since we started looking
		// for data.
		if (((Timer - DMCTime) < Timeout) && !ResponseDone)
			return(-1);  // Response not yet complete, timeout not reached

		if (ResponseDone)
		{
			DMCReceiveStep = 0;
			return(0);
		}
		// A communication failure means that a ':' was not received
		// before a Timeout occurred
	}
	DMCReceiveStep = 0;
	return(1);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::BSerDMCReceive - Wait for a response from a DMC (blocking)
int CIR::BSerDMCReceive(char *rsp,int Timeout)
{
	int temp;

	do
		temp = SerDMCReceive(rsp,Timeout);
	while (temp == -1);
	return(temp);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::SerDMCCommand - Send a DMC a command and wait for a response
int CIR::SerDMCCommand(char *Data,char *rsp,int Timeout)
{
	// This routine sends the command in string Data to the DMC on port IRPort,
	// and puts the response from the DMC in string Response.  The routine will
	// timeout after Timeout milliseconds.
	// Returns a 0 on success, 1 on failure, -1 on not done yet.
	// A 1 means that the DMC timed-out (didn't give a ':' prompt back
	// before Timeout milliseconds)

	char temp[255],s[255];
	int e = -1;
  int i = 0;

	// Select proper IR if necessary
	if (DMCCommandStep == 0)
	{
  	// Select instrument rotator if necessary
  	if (IRSelect != Address)
    {
    	sprintf(s,"%%%d\r",Address);
			ClearSerialBuffer(IRPort);
			SerialBufPutString(IRPort,s);
			Log.Add("CIR::SerDMCCommand(%d): Sent \"%s\" to COM%d:",2,__LINE__,s,IRPort+1);
			ResponsesNeeded = 1;
	    DMCCommandStep = 1;
    }
    // Otherwise send the command right away
    else
    	DMCCommandStep = 2;
	}
	// Wait for IR selection response
	else if (DMCCommandStep == 1)
	{
		e = SerDMCReceive(rsp,200);
		if (e == 1)
		{
			IRSelect = -1;		// Don't assume we know which DMC is selected anymore
			Log.Add("!CIR::SerDMCCommand(%d): Received \"%s\"",2,__LINE__,rsp);
			if (rsp[0] == '?')
			{
				DMCCommandStep = 4;
				e = -1;
			}
			else
				DMCCommandStep = 0;
		}
    else if (e == 0)
    {
			Log.Add("CIR::SerDMCCommand(%d): Received \"%s\"",2,__LINE__,rsp);
    	IRSelect = Address;
      DMCCommandStep = 2;
      e = -1;
    }
	}
	// Send command
	else if (DMCCommandStep == 2)
	{
		ClearSerialBuffer(IRPort);
		SerialBufPutString(IRPort,Data);
		Log.Add("CIR::SerDMCCommand(%d): Sent \"%s\" to COM%d:",2,__LINE__,Data,IRPort+1);
    i=0;
		ResponsesNeeded = 0;
    while (Data[i] != 0)
    {
    	if ((Data[i] == 13) || (Data[i] == ';'))
      	ResponsesNeeded++;
    	i++;
    }
    DMCCommandStep = 3;
	}
	// Wait for response
	else if (DMCCommandStep == 3)
	{
		e = SerDMCReceive(rsp,Timeout);
		if (e == 1)
		{
			Log.Add("!CIR::SerDMCCommand(%d): Received \"%s\"",2,__LINE__,rsp);
			if (rsp[0] == '?')
			{
				DMCCommandStep = 4;
				e = -1;
			}
			else
				DMCCommandStep = 0;
		}
    else if (e == 0)
    {
			Log.Add("CIR::SerDMCCommand(%d): Received \"%s\"",2,__LINE__,rsp);
    	DMCCommandStep = 0;
    }
	}
	// Query DMC for error code
	else if (DMCCommandStep == 4)
	{
		ClearSerialBuffer(IRPort);
		SerialBufPutString(IRPort,"TC1\r");
		Log.Add("CIR::SerDMCCommand(%d): Sent \"TC1\r\" to COM%d:",2,__LINE__,IRPort+1);
    ResponsesNeeded = 1;
    DMCCommandStep = 5;
	}
	// Wait for error code response
	else if (DMCCommandStep == 5)
	{
		e = SerDMCReceive(rsp,Timeout);
		if (e == 0)
		{
			Log.Add("!CIR::SerDMCCommand(%d): Received \"%s\"",2,__LINE__,rsp);
			DMCCommandStep = 0;
			e = 1;
		}
		else if (e == 1)
			DMCCommandStep = 0;
	}
	return(e);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::BSerDMCCommand - Send a DMC a command and wait for a response (blocking)
int CIR::BSerDMCCommand(char *Data,char *rsp,int Timeout)
{
	int temp;

	do
		temp = SerDMCCommand(Data,rsp,Timeout);
	while (temp == -1);
	return(temp);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayAll - Display all on-screen IR data
void CIR::DisplayAll()
{
	DisplayStatus();
  DisplaySelection();
	DisplayEncoder();
  DisplayCommand();
	DisplaySwitches();
	DisplayInputs();
  DisplayVelocity();
  DisplayVolts();
  if (Telescope == 2)
    M2Cass.DisplayPort();
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayStatus - Display enabled/communication status
void CIR::DisplayStatus()
{
	int color = BLACK;

  if ((ErrorCount > 0) && Enabled)
		color = SHECRED;
  else if (Enabled)
		color = SHECGREEN;
	PutChar16('0'+Address,12,ibty+26+Address*16,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplaySelection - Display if this IR is selected for graph/commands
void CIR::DisplaySelection()
{
	int color = BLACK;
  if (ActiveIR == Address)
		color = SHECGREEN;
	PutString16(IRNames[Address],28,60,ibty+26+Address*16,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayEncoder - Display currently if this IR is selected for graph/commands
void CIR::DisplayEncoder()
{
	char s[50];
	int temp,a,b,dcolor = BLACK,ecolor = BLACK;

	sprintf(s,"%+011d",Encoder);
  if (Enabled)
	{
    if ((MasterEncErr <= EncoderBad) && (SlaveEncErr <= EncoderBad))
		  ecolor = SHECRED;
	  else if (((MasterEncErr > EncoderBad) && (MasterEncErr < EncoderOK)) &&
             ((SlaveEncErr > EncoderBad) && (SlaveEncErr < EncoderOK)))
		  ecolor = SHECYELLOW;
  }
	PutString16(s,420,508,ibty+26+Address*16,ecolor,SHECMEDGRAY);

	// Convert encoder reading to degrees * 1000
	temp = round((float)(Encoder - CCWHome) * 360000.0 / (float)Revolution);
	if (temp > 999999) temp = 999999;
	if (temp < -999999) temp = -999999;
	// Record current position in deg * 1000 for EDS
	Val = temp;
	a = abs(temp / 1000);
	b = abs(temp % 1000);
	if (temp >= 0)
		sprintf(s,"+%03d.%03d",a,b);
	else
		sprintf(s,"-%03d.%03d",a,b);
	if (!Homed && Enabled)
		dcolor = SHECYELLOW;
	PutString16(s,12+16*8,12+24*8,ibty+26+Address*16,dcolor,SHECMEDGRAY);
  if (Enabled)
	{
    if ((MasterEncErr <= EncoderBad) && (SlaveEncErr <= EncoderBad) &&
        (prevEncoder != Encoder))
		{
      EDSLog.Add(34,"Rotator%d encoder error at %s",CurrentIR,s);
      prevEncoder = Encoder;
    }
  }
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayCommand - Display current target angle
void CIR::DisplayCommand()
{
	char s[50];
	int temp,a,b,color = BLACK;

	if (LastCommand == 2)	// A Jog command
	{
		// Convert command velocity to degrees per second
		temp = round((float)JogCommand * 360000.0 / (float)Revolution);
		if (temp > 9999) temp = 9999;
		if (temp < -9999) temp = -9999;
		// Store command in deg/sec * 1000 for EDS
		Req = temp;
		a = abs(temp / 1000);
		b = abs(temp % 1000);
		if (temp >= 0)
			sprintf(s,"  +%1d.%03d",a,b);
		else
			sprintf(s,"  -%1d.%03d",a,b);
	}
	else
	{
		// Convert command to degrees
		temp = round((float)(Command - CCWHome) * 360000.0 / (float)Revolution);
		if (temp > 999999) temp = 999999;
		if (temp < -999999) temp = -999999;
		// Store command in deg/sec * 1000 for EDS
		Req = temp;
		a = abs(temp / 1000);
		b = abs(temp % 1000);
		if (temp >= 0)
			sprintf(s,"+%03d.%03d",a,b);
		else
			sprintf(s,"-%03d.%03d",a,b);
	}
	if (!Homed && Enabled)
		color = SHECYELLOW;
	PutString16(s,12+7*8,12+15*8,ibty+26+Address*16,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplaySwitches - Display all switches from TS command
void CIR::DisplaySwitches()
{
	int color,c;

	// Error
	if (ErrorFlag && Enabled) color = SHECRED;
	else color = BLACK;
	PutChar16('þ',12+37*8,ibty+26+Address*16,color,SHECMEDGRAY);
	// Motion
	if (Motion == IRMCW) c = 16; 				// >
	else if (Motion == IRMCCW) c = 17;	// <
	else c = 29;												// <>
	// If it will hit a soft limit within the next three minutes,
	// and tracking or jogging, paint yellow.
	if (	Enabled &&
		Homed &&
		DMCMoving &&
		(((Encoder + Velocity * 180) >= (CWLimit - (SLDist * (Revolution / 360)))) ||
		((Encoder + Velocity * 180) <= (CCWLimit + (SLDist * (Revolution / 360)))))) {
			color = SHECYELLOW;
	}
	else if ((DMCMoving || MotorPower) && Enabled) color = SHECGREEN;
	else color = BLACK;
	PutChar16(c,12+39*8,ibty+26+Address*16,color,SHECMEDGRAY);
	// Limits
	if (Limit == IRLCW) c = 16; 				// >
	else if (Limit == IRLCCW) c = 17;		// <
	else c = 29;												// <>
	if ((Limit > 0) && Enabled) color = SHECRED;
	else color = BLACK;
	PutChar16(c,12+41*8,ibty+26+Address*16,color,SHECMEDGRAY);
	// Zone
	if (Zone == IRZCW) c = 16; 				// >
	else c = 17;											// <
	if (Enabled) color = SHECGREEN;
	else color = BLACK;
	PutChar16(c,12+49*8,ibty+26+Address*16,color,SHECMEDGRAY);
	// Homed
	if (Homed && Enabled) color = SHECGREEN;
	else if (Homing && Enabled) color = SHECYELLOW;
	else color = BLACK;
	PutChar16('þ',12+47*8,ibty+26+Address*16,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayInputs - Display all inputs from TI command
void CIR::DisplayInputs()
{
	int color,c;

  static int i;

	// CASS Clamp at Mag2
  if ((CurrentIR == IRCASS) && (Telescope == 2))
	{
    // Clamp engaged, dog deployed, normal secured condition
    if (Enabled && Brake && M2Cass.LatchEngaged && !M2Cass.LatchReleased)
      color = SHECRED;
    // Clamp released, dog retracted, normal free position
	  else if (Enabled && !Brake && !M2Cass.LatchEngaged && M2Cass.LatchReleased)
      color = SHECGREEN;
    // Clamp engaged, dog retracted, error condition
    else if (Enabled && Brake && !M2Cass.LatchEngaged && M2Cass.LatchReleased)
      color = SHECWHITE;
    // Clamp released, dog deployed, error condition
    else if (Enabled && !Brake && M2Cass.LatchEngaged && !M2Cass.LatchReleased)
      color = SHECWHITE;
    // Clamp engaged/released but dog midway
	  else if ((Enabled && Brake && !M2Cass.LatchEngaged && !M2Cass.LatchReleased) ||
            (Enabled && !Brake && !M2Cass.LatchEngaged && !M2Cass.LatchReleased))
      color = SHECYELLOW;
	  else
      color = BLACK;
  }
  // All other IRs do not have detent system
  else
    color = BLACK;
	PutChar16('þ',12+43*8,ibty+26+Address*16,color,SHECMEDGRAY);

	// LockPin
	if (LockPin && Enabled) color = SHECRED;
	else color = BLACK;
	PutChar16('þ',12+45*8,ibty+26+Address*16,color,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayVelocity - Display angular velocity
void CIR::DisplayVelocity()
{
	char s[50];
	int temp,a,b;

	// Display left or right motion arrows
	if (Enabled && DMCMoving && (Velocity > 0))
		Motion = IRMCW;
	else if (Enabled && DMCMoving && (Velocity < 0))
		Motion = IRMCCW;
	else
		Motion = IRMNone;
	DisplaySwitches();

	// Display angular velocity
	// Convert velocity to degrees per second
	temp = round((float)Velocity * 360000.0 / (float)Revolution);
	if (temp > 9999) temp = 9999;
	if (temp < -9999) temp = -9999;
	// Store velocity in deg/sec * 1000 for EDS
	Vel = temp;
	a = abs(temp / 1000);
	b = abs(temp % 1000);
	if (temp >= 0)
		sprintf(s,"+%1d.%03d",a,b);
	else
		sprintf(s,"-%1d.%03d",a,b);
	PutString16(s,12+25*8,12+31*8,ibty+26+Address*16,BLACK,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CIR::DisplayVolts - Display torque (in volts)
void CIR::DisplayVolts()
{
	char s[20];
	int a,b;

	a = abs(Volts/10);
	b = abs(Volts%10);

	if (abs(Volts) > 99)
	{
		a = 9;
		b = 9;
	}

	if (Volts >= 0)
		sprintf(s,"+%1d.%1d",a,b);
	else
		sprintf(s,"-%1d.%1d",a,b);

	PutString16(s,268,300,ibty+26+Address*16,BLACK,SHECMEDGRAY);
}

/////////////////////////////////////////////////////////////////////////////
// CM2Cass::EngageClamp - Locks CASS IR position at Mag2
void CM2Cass::EngageClamp()
{
  char tmp[255];

  // Only toggle clamps if the DMC is on, apparently working and LockPin is out
  if (!IRs[IRCASS].Enabled || (IRs[IRCASS].ErrorCount != 0))
		EDSLog.Add(100,"Clamps not toggled: IR not enabled/com error");
	else if (IRs[IRCASS].LockPin)
		EDSLog.Add(100,"Clamps not toggled: Lock Pin in (unlock manually)");
	else if (IRs[IRCASS].DMCMoving)
		EDSLog.Add(100,"Clamps not toggled: Move in progress (use STOP)");
  else if (!LatchEngaged && !LatchReleased)
    EDSLog.Add(100,"Clamp in the middle way");
  else if (LatchEngaged && LatchReleased)
    EDSLog.Add(100,"Error in clamp position sensors");
	else if (!IRs[IRCASS].AmplifierPower)
		EDSLog.Add(112,"Move ignored: Motor amplifier off (check lock pin)");
	else if (IRs[IRCASS].NewMove)
		EDSLog.Add(112,"Move ignored: Another move is being processed");
  else
	{
  	  // Let current operation finish
      PauseIR();
      IRs[IRCASS].NewMove = 7;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CM2Cass::ReleaseClamp - Frees CASS IR Position at Mag2
void CM2Cass::ReleaseClamp()
{
  char tmp[255];

  // Only toggle clamps if the DMC is on, apparently working and LockPin is out
  if (!IRs[IRCASS].Enabled || (IRs[IRCASS].ErrorCount != 0))
		EDSLog.Add(101,"Clamps not toggled: IR not enabled/com error");
	else if (IRs[IRCASS].LockPin)
		EDSLog.Add(101,"Clamps not toggled: Lock Pin in (unlock manually)");
	else if (IRs[IRCASS].DMCMoving)
		EDSLog.Add(101,"Clamps not toggled: Move in progress (use STOP)");
  else if (!LatchEngaged && !LatchReleased)
    EDSLog.Add(101,"Clamp in the middle way");
  else if (LatchEngaged && LatchReleased)
    EDSLog.Add(101,"Error in clamp position sensors");
	else if (!IRs[IRCASS].AmplifierPower)
		EDSLog.Add(112,"Move ignored: Motor amplifier off (check lock pin)");
	else if (IRs[IRCASS].NewMove)
		EDSLog.Add(112,"Move ignored: Another move is being processed");
  else {
    if (IRs[IRCASS].Brake) {
  	  // Let current operation finish
      PauseIR();
	  IRs[IRCASS].NewMove = 8;
	}
  }
}

/////////////////////////////////////////////////////////////////////////////
// CM2Cass::ChangePort - Moves Cass to any observing port (in Mag2)
void CM2Cass::ChangePort(int pos,int tp)
{
  char tmp[255];

  // Only move if the DMC is on, apparently working and the LockPin is out
  if (!IRs[IRCASS].Enabled || (IRs[IRCASS].ErrorCount != 0))
		EDSLog.Add(112,"Move ignored: IR not enabled/error");
  else if (IRs[IRCASS].LockPin)
		EDSLog.Add(112,"Move ignored: LockPin in");
	else if (IRs[IRCASS].DMCMoving)
		EDSLog.Add(112,"Move ignored: Move in progress (use STOP)");
  else if (!LatchEngaged && !LatchReleased)
    EDSLog.Add(112,"Move ignored: Clamp in the middle way");
  else if (LatchEngaged && LatchReleased)
    EDSLog.Add(112,"Move ignored: Error in clamp position sensors");
	else if (!IRs[IRCASS].AmplifierPower)
		EDSLog.Add(112,"Move ignored: Motor amplifier off (check lock pin)");
	else if (IRs[IRCASS].NewMove)
		EDSLog.Add(112,"Move ignored: Another move is being processed");
  else
	{
    IRs[IRCASS].NewMove = 6; // Flag that a move has been requested
    // Absolute move
		IRs[IRCASS].Command = pos;
    Target = tp;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CM2Cass::DisplayPort - Displays the port the teritary is pointing at (in Mag2)
void CM2Cass::DisplayPort()
{
  int x,y;
  int color;

  x = 520+12;
  y = cbty+8;

  if (LatchEngaged && !LatchReleased)
    color = SHECGREEN;
  else
    color = BLACK;
  switch (M3Position)
  {
    case M3NASW:
      PutString32("NASW",x,x+64,y+16,color,SHECMEDGRAY); break;
    case M3NASE:
      PutString32("NASE",x,x+64,y+16,color,SHECMEDGRAY); break;
    case M3AUX1:
      PutString32("AUX1",x,x+64,y+16,color,SHECMEDGRAY); break;
    case M3AUX2:
      PutString32("AUX2",x,x+64,y+16,color,SHECMEDGRAY); break;
    case M3AUX3:
      PutString32("AUX3",x,x+64,y+16,color,SHECMEDGRAY); break;
    default:
      PutString32("    ",x,x+64,y+16,color,SHECMEDGRAY); break;
  }
}

/////////////////////////////////////////////////////////////////////////////
// UpdateIRs: Update instrument rotator communication
void UpdateIRs()
{
	int e,i;

  e = IRs[CurrentIR].Update();
  if (e > 0)
  {
  	IRs[CurrentIR].CommandCount = 0;
  	IRs[CurrentIR].ErrorCount++;
    if (IRs[CurrentIR].ErrorCount < 3)
			EDSLog.Add(106,"%s IR com error",IRNames[CurrentIR]);
		else if (IRs[CurrentIR].ErrorCount == 3)
			EDSLog.Add(107,"%s IR com errors suspended",IRNames[CurrentIR]);
    IRs[CurrentIR].DisplayStatus();
	 	CurrentIR++;
    if (CurrentIR >= NumberIRs)
			CurrentIR = 0;
  }
  else if (e == 0)
	{
  	// Note that a command has been sent
  	IRs[CurrentIR].CommandCount++;
    if (IRs[CurrentIR].ErrorCount >= 3)
			EDSLog.Add(984,"%s IR com errors resumed",IRNames[CurrentIR]);
  	IRs[CurrentIR].ErrorCount = 0;
    IRs[CurrentIR].DisplayStatus();
    // If we've exceeded the number of commands for this IR,
    // go to the next IR
    if ( ((CurrentIR == ActiveIR) && (IRs[CurrentIR].CommandCount > ActiveCommandCount)) ||
     		 ((CurrentIR != ActiveIR) && (IRs[CurrentIR].CommandCount > InactiveCommandCount)) )
		{
			IRs[CurrentIR].CommandCount = 0;
		 	CurrentIR++;
      if (CurrentIR >= NumberIRs)
    	  CurrentIR = 0;
    }
  }
  // Loop through all 6 instrument rotators (skip disabled ones)
	i = 0;
  while ((i < NumberIRs) && !IRs[CurrentIR].Enabled)
  {
  	i++;
  	CurrentIR++;
	  if (CurrentIR >= NumberIRs)
	  	CurrentIR = 0;
  }
}

/////////////////////////////////////////////////////////////////////////////
// PauseIR: Pauses IR communication
void PauseIR()
{
	int e;

  do
  	e = IRs[CurrentIR].Update();
	while (e < 0);
}

/////////////////////////////////////////////////////////////////////////////
// IRTerminal: opens a simple terminal with the instrument rotators
void IRTerminal(int IRPORT)
// This routine acts as a simple terminal to the IRs.
// It displays received characters, and transmits typed characters.
// Hitting ESC ends the terminal session.
// IRPORT is the COM port the rotator is attached to.
{
	int c; // Holds received and typed characters
	char line[80]; // A buffer for received and transmitted characters

	IRPORT--;
	if ((IRPORT < 2) || (IRPORT > 5))
	{
		Log.Add("Error opening terminal with IRs",0);
		return;
	}
	CloseVideo();
	cprintf("IR Terminal on COM%d: (ESC to end)\r\n",IRPORT+1);
	do
	{
		// Don't let the watchdog go off during terminal mode
		Watchdog();
		// Check for incoming serial characters
		if (SerialCharReady(IRPORT))
		{
			c = SerialGetChar(IRPORT);
			cprintf("%c",c);
			if (c == '\r')
				cprintf("\n");
		}
		c = 0;
		// Check for outgoing characters
		if (kbhit())
		{
			c = toupper(getch());
			if (c != 27)
			{
				cprintf("%c",c);
				if (c == '\r')
					cprintf("\n");
				// Transmit the typed character
				SerialPutChar(IRPORT,c);
			}
		}
	// Quit on an <ESC>
	} while (c != 27);
	// Tell system we may have changed the selected IR
	IRSelect=-1;
	InitVideo();
	InitDisplay();
	Cell.DisplayAll();
}

/////////////////////////////////////////////////////////////////////////////
// ProgIR: Downloads IR servo code to an instrument rotator
void ProgIR(int IR)
{
	int i;
	char s[255];
	FILE* fin;

  Log.Add("Preparing to program instrument rotator #%d...",0,IR);
  Log.DisplayAll();

 	// Let current operation finish
  PauseIR();

  // Read IR servo code
  fin=fopen("IRSERVO.DMC","r");
  if (fin == NULL)
  {
		Log.Add("Error opening IRSERVO.DMC!",0);
    return;
  }

	// Switch to proper IR
  sprintf(s,"%%%d\r",IR);
	SerialBufPutString(IRs[IR].IRPort,s);

  // Begin download
  while (!feof(fin))
  {
  	if (fgets(s,255,fin) == NULL)
			break;
    i = strlen(s);
    // Remove CR's and LF's
    while ( (i >= 1) && ((s[i-1] == 13) || (s[i-1] == 10)) )
			i--;
    if (i==0)
    	break;
    // Add a single CR
		s[i] = 13;
    s[i+1] = 0;
		SerialBufPutString(IRs[IR].IRPort,s);
		Log.Add("Wrote \"%s\" to COM%d:",0,s,IRs[IR].IRPort+1);
    Log.DisplayAll();
  }

  // Wait for burn to complete
  Log.Add("Waiting 10 seconds for program burn...",0);
  Log.DisplayAll();
  Delay(10000);

  Log.Add("Burn complete.",0);
  Log.Add("It's recommended that you check the program",0);
  Log.Add("via the IR terminal (LS command), and reset",0);
  Log.Add("the IR to begin the program (RS command).",0);

  // Tell system we may have changed the selected IR
  IRSelect=-1;
}

Generated by GNU Enscript 1.6.5.2.
Document Actions