} // end-Prepare

        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        private bool Purge()
        {
            const string funcMsg = "Purge: ";

            bool     purgeDone      = false;
            int      purgeStepTime  = 2;       // 2 seconds is a guess; configure as necessary
            TimeSpan maxPurgeLength = new TimeSpan(0, 0, _purge1Seconds);
            TimeSpan elapsedTime    = new TimeSpan(0, 0, 0);
            DateTime purgeStartTime; // initialized immediately prior to each loop which this is used
            DateTime cylinderStartTime = DateTime.UtcNow;

            try
            {
                // Indicate that the purging process is now in progress
                ConsoleState consoleState;
                if (_purgeType == PurgeType.PreCalibration || _purgeType == PurgeType.PostCalibration)
                {
                    consoleState = ConsoleState.CalibratingInstrument;
                }
                else if (_purgeType == PurgeType.CylinderSwitch)
                {
                    consoleState = (_returnGasResponseEvent is InstrumentCalibrationEvent) ? ConsoleState.CalibratingInstrument : ConsoleState.BumpingInstrument;
                }
                else
                {
                    consoleState = ConsoleState.BumpingInstrument;
                }

                Master.Instance.ConsoleService.UpdateState(consoleState, ConsoleServiceResources.PURGING);

                _instrumentController.OpenGasEndPoint(_airEndPoint, _desiredFlow);

                switch (_purgeType)
                {
                // Constant purge only
                case PurgeType.PreCalibration:     // prior to zeroing, we do a fixed-time purge.
                    Log.Debug(string.Format("{0}Purging for {1} seconds.", funcMsg, _purge1Seconds));

                    purgeStartTime = DateTime.UtcNow;
                    while (elapsedTime < maxPurgeLength)
                    {
                        if (!Master.Instance.ControllerWrapper.IsDocked())
                        {
                            throw new InstrumentNotDockedException();
                        }

                        Thread.Sleep(1000);       // Wait for the purge.

                        // See if ResourceService determined that cylinder was empty while we slept.
                        CheckAir(_airEndPoint);       // throws if empty cylinder is detected

                        elapsedTime = DateTime.UtcNow - purgeStartTime;
                    }
                    purgeDone = true;
                    break;

                // Constant purge followed by a "smart purge"
                case PurgeType.PostCalibration:
                case PurgeType.PostBump:
                    // constant purge
                    if (_purge1Seconds > 0)
                    {
                        Log.Debug(string.Format("{0}Purging for {1} seconds.", funcMsg, _purge1Seconds));

                        purgeStartTime = DateTime.UtcNow;
                        while (elapsedTime < maxPurgeLength)
                        {
                            if (!Master.Instance.ControllerWrapper.IsDocked())
                            {
                                throw new InstrumentNotDockedException();
                            }

                            Thread.Sleep(1000);                                       // Wait for the purge.

                            // See if ResourceService determined that cylinder was empty while we slept.
                            CheckAir(_airEndPoint);                                       // throws if empty cylinder is detected

                            elapsedTime = DateTime.UtcNow - purgeStartTime;
                        }
                    }

                    // smart purge
                    if (_purge2Seconds > 0)
                    {
                        Log.Debug(string.Format("{0}Purging for a maximum of {1} seconds.", funcMsg, _purge2Seconds));

                        // reset as constant purge above may have used these
                        elapsedTime    = new TimeSpan(0, 0, 0);
                        maxPurgeLength = new TimeSpan(0, 0, _purge2Seconds);

                        purgeStartTime = DateTime.UtcNow;
                        while (elapsedTime < maxPurgeLength && purgeDone == false)
                        {
                            // See if the instrument is in alarm.  If it is not, the purge can end early.
                            if (IsInstrumentInAlarm(_returnGasResponseEvent.DockedInstrument, null) == false)
                            {
                                purgeDone = true;
                            }
                            else
                            {
                                CheckAir(_airEndPoint);                                           // throws if empty cylinder is detected
                                Log.Debug(string.Format("{0}Purging for {1} seconds.", funcMsg, purgeStepTime));
                                Thread.Sleep(purgeStepTime * 1000);
                            }
                            elapsedTime = DateTime.UtcNow - purgeStartTime;
                        }

                        if (purgeDone)
                        {
                            Log.Debug(string.Format("{0}Instrument is NOT in alarm after {1} seconds.", funcMsg, elapsedTime.TotalSeconds));
                        }
                        else
                        {
                            Log.Debug(string.Format("{0}Instrument is IN ALARM after {1} seconds.  MAXIMUM PURGE TIME EXCEEDED.", funcMsg, elapsedTime.TotalSeconds));
                        }

                        if (!purgeDone && _purgeType == PurgeType.PostBump)
                        {
                            // If we got here, that must mean that the PurgeAfterBump setting is enabled.
                            Log.Debug(string.Format("{0}PUTTING SENSORS INTO BUMP FAULT THAT ARE STILL IN ALARM.", funcMsg));
                            // See if the instrument is still in alarm.  Report a purgeDone of 'false' if the instrument is still in alarm.
                            // Put sensors into bump fault that are still in alarm.
                            purgeDone = !IsInstrumentInAlarm(_returnGasResponseEvent.DockedInstrument, _returnGasResponseEvent as InstrumentBumpTestEvent);

                            if (purgeDone)
                            {
                                Log.Debug(string.Format("{0}Instrument was NOT in alarm for the final check.", funcMsg));
                            }
                        }
                    }
                    else
                    {
                        // If PurgeAfterBump setting is disabled than we can report that the purge completed successfully.
                        purgeDone = true;
                    }

                    break;

                // Smart purge only
                case PurgeType.PreBump:
                    Log.Debug(string.Format("{0}Purging for a maximum of {1} seconds.", funcMsg, _purge1Seconds));

                    purgeStartTime = DateTime.UtcNow;
                    while (elapsedTime < maxPurgeLength && purgeDone == false)
                    {
                        // See if the instrument is in alarm.  If it is not, the purge can end early.
                        if (IsInstrumentInAlarm(_returnGasResponseEvent.DockedInstrument, null) == false)
                        {
                            purgeDone = true;
                        }
                        else
                        {
                            CheckAir(_airEndPoint);                                       // throws if empty cylinder is detected
                            Log.Debug(string.Format("{0}Purging for {1} seconds.", funcMsg, purgeStepTime));
                            Thread.Sleep(purgeStepTime * 1000);
                        }
                        elapsedTime = DateTime.UtcNow - purgeStartTime;
                    }

                    if (purgeDone)
                    {
                        Log.Debug(string.Format("{0}Instrument is NOT in alarm after {1} seconds.", funcMsg, elapsedTime.TotalSeconds));
                    }
                    else
                    {
                        Log.Debug(string.Format("{0}Instrument is IN ALARM after {1} seconds.  MAXIMUM PURGE TIME EXCEEDED.", funcMsg, elapsedTime.TotalSeconds));
                    }

                    break;

                case PurgeType.O2Recovery:
                    Log.Debug(string.Format("{0}Purging O2 sensors for recovery from depravation for a maximum of {1} seconds.", funcMsg, _purge1Seconds));

                    // From all the InstalledComponents, get a sub-list of just the O2 sensors.
                    // Then, from the list of O2 sensors, whittle it down to just O2 sensors that we have SGRs for.
                    // SGRs might be missing, for example, if we just did a bump test, but one or more sensors were
                    // already in a cal-fault state. (Which could happen with dualsense sensors where one in the pair
                    // is not working.) Those failed sensors would not have been bump tested, so there will be no SGR,
                    // and since the sensoris not "working", we don't have to worry about getting its reading.
                    List <InstalledComponent> o2Components = _returnGasResponseEvent.DockedInstrument.InstalledComponents.FindAll(c => c.Component.Enabled && c.Component.Type.Code == SensorCode.O2);
                    o2Components = o2Components.FindAll(o2 => _returnGasResponseEvent.GetSensorGasResponseByUid(o2.Component.Uid) != null);

                    purgeStartTime = DateTime.UtcNow;
                    while (elapsedTime < maxPurgeLength && purgeDone == false)
                    {
                        Thread.Sleep(1000);

                        CheckAir(_airEndPoint);       // throws if empty cylinder is detected

                        // SGF  24-Aug-2011  INS-2314 -- check O2 sensors for their current readings
                        int numO2SensorsPassed = 0;
                        foreach (InstalledComponent ic in o2Components)
                        {
                            Sensor            sensor = (Sensor)ic.Component;
                            SensorGasResponse sgr    = _returnGasResponseEvent.GetSensorGasResponseByUid(sensor.Uid);
                            sgr.Status = Status.O2RecoveryFailed;

                            sgr.O2HighReading = _instrumentController.GetSensorReading(ic.Position, sensor.Resolution);
                            sgr.Time          = DateTime.UtcNow;
                            Log.Debug(string.Format("{0}O2 sensor UID={1} O2HighReading={2}.", funcMsg, sensor.Uid, sgr.O2HighReading.ToString()));

                            // SGF  24-Aug-2011  INS-2314 -- getting rid of the test for the high threshold, since we don't use cylinders with higher than normal O2 levels
                            if (OXYGEN_FRESH_AIR_TEST_LOW_PASS_PCT <= sgr.O2HighReading)
                            {
                                Log.Debug(string.Format("{0}O2 sensor UID={1} reading is within normal range.", funcMsg, sensor.Uid));
                                // INETQA-4149 INS-7625 SSAM v7.6 IsO2HighBumpPassed flag is set to true if O2 sensor passes the recovery purge.
                                // Else if recovery fails, calibration is initiated to recover the O2 sensor.
                                sgr.IsO2HighBumpPassed = true;
                                sgr.Status             = Status.Passed;
                                numO2SensorsPassed++;
                            }
                        }

                        if (numO2SensorsPassed == o2Components.Count)
                        {
                            purgeDone = true;     // All O2 sensors pass the recovery test; time to short-circuit the purge
                        }
                        elapsedTime = DateTime.UtcNow - purgeStartTime;
                    }

                    // For any O2 sensors that failed to recover above, mark the SGR status as O2RecoveryFailed
                    foreach (InstalledComponent ic in o2Components)
                    {
                        SensorGasResponse sgr = _returnGasResponseEvent.GetSensorGasResponseByUid(ic.Component.Uid);
                        sgr.SpanCoef = _instrumentController.GetSensorSpanCoeff(ic.Position);
                        sgr.UsedGasEndPoints.Add(new UsedGasEndPoint(_airEndPoint, CylinderUsage.BumpHigh, elapsedTime));
                        if (sgr.Status == Status.O2RecoveryFailed)
                        {
                            Log.Warning(string.Format("{0} O2 SENSOR (UID={1}) FAILED TO RECOVER FROM DEPRAVATION.", funcMsg, ic.Component.Uid));
                        }
                    }

                    GasType gasType = GasType.Cache[GasCode.O2];
                    Master.Instance.ConsoleService.UpdateState(ConsoleState.BumpingInstrument, gasType.Symbol);

                    break;     // end-PurgeType.O2Recovery

                // Purge between use of different gas endpoints
                case PurgeType.CylinderSwitch:
                    Log.Debug(string.Format("{0}Purging for {1} seconds.", funcMsg, _purge1Seconds));

                    purgeStartTime = DateTime.UtcNow;
                    while (elapsedTime < maxPurgeLength && purgeDone == false)
                    {
                        // See if the sensor readings have met the purge complete criterion. If it has, the purge can end early.
                        // During a calibration, this is a Constant purge only
                        if (_returnGasResponseEvent is InstrumentBumpTestEvent)
                        {
                            purgeDone = IsInstrumentPurgeCriterionMet();
                        }

                        Thread.Sleep(1000);     // Wait for the purge.

                        // See if ResourceService determined that cylinder was empty while we slept.
                        CheckAir(_airEndPoint);     // throws if empty cylinder is detected
                        elapsedTime = DateTime.UtcNow - purgeStartTime;
                    }

                    if (_returnGasResponseEvent is InstrumentCalibrationEvent)
                    {
                        purgeDone = true;       // For Calibration, its fixed time purge
                    }
                    Log.Debug(string.Concat("CYLINDER-SWITCH purge ", purgeDone ? "PASSED" : "FAILED"));

                    break;
                } // end-switch
            }
            catch (CommunicationAbortedException cae)   // undocked?
            {
                throw new InstrumentNotDockedException(cae);
            }
            catch (InstrumentNotDockedException)
            {
                throw;
            }
            catch (FlowFailedException ffe)   // ran out of gas during the purge?
            {
                Log.Warning(Name + " throwing FlowFailedException for position " + ffe.GasEndPoint.Position);
                throw;
            }
            catch (Exception ex)
            {
                throw new UnableToPurgeException(ex);
            }
            finally
            {
                _instrumentController.CloseGasEndPoint(_airEndPoint);

                //Purge is alway run at max voltage which satisfies condition of bad kink tubing even
                //there is no issue with tubing.  So, explicitly set "Pump.IsBadPumpTubing" to false
                //once purge operation is complete.
                Pump.IsBadPumpTubing = false;

                if (_returnGasResponseEvent != null)
                {
                    // SGF  06-Jun-2011  INS-1735
                    // Add a new UsedGasEndPoint object to the return event if the duration is greater than 0.
                    TimeSpan durationInUse = DateTime.UtcNow - cylinderStartTime;
                    if (durationInUse.CompareTo(TimeSpan.MinValue) > 0)
                    {
                        _returnGasResponseEvent.UsedGasEndPoints.Add(new UsedGasEndPoint(_airEndPoint, CylinderUsage.Purge, durationInUse));
                    }
                }

                Log.Debug(string.Format("{0}Finished", funcMsg));
            }

            return(purgeDone);
        }
    /// Zeroes all sensors contained on the instrument.
    /// 
    /// NOTE: THIS ROUTINE LOOKS LIKE IT CAN ZERO A SPECIFIC SENSOR 
    /// BUT IN ACTUALITY, IT ALWAYS ZEROS ALL SENSORS.
    /// </summary>
    /// <param name="installedComponents">Contains InstalledComponents for the instrument being zeroed.</param>
    private void ZeroSensors( IEnumerable<InstalledComponent> installedComponents )
    {
        Log.Debug( "ZEROING: Preparing to zero" );

        // Indicate that the zeroing process is now in progress
        Master.Instance.ConsoleService.UpdateState( ConsoleState.CalibratingInstrument, ConsoleServiceResources.ZEROING );

        // See if we have a CO2 sensor.  If so, then we can only zero using zero air.
        // fresh air is NOT allowed.  If no CO2 sensor is installed, then fresh
        // air may be used as an alternative to zero air, if zero air is not found.
        bool useZeroAirOnly = false;
        foreach ( InstalledComponent installedComponent in installedComponents )
        {
            if ( installedComponent.Component.Type.Code == SensorCode.CO2 )
            {
                Sensor sensor = (Sensor)installedComponent.Component;

                if ( !sensor.Enabled ) // if it's disabled, we won't be zeroing it.
                    continue;

                Log.Debug( "ZEROING: Found CO2 sensor.  Will not use fresh air to zero." );
                useZeroAirOnly = true;
                break;
            }
        }

		GasEndPoint zeroEndPoint = null;

        DateTime startTime = DateTime.UtcNow;

        try
        {
			_zeroingUsedGasEndPoint = null;  // Reset any previous setting (if any).

            zeroEndPoint = GetSensorZeroAir( null, useZeroAirOnly, false );  // Get the zeroing gas end point for this gas.

            _instrumentController.OpenGasEndPoint( zeroEndPoint, Pump.StandardFlowRate );

            // ZeroSensor will return false if IDS times out before instrument finishes zeroing.
            // Return value does NOT indicate if zeroing was successful or not!
            if ( !_instrumentController.ZeroSensors( zeroEndPoint ) )
                throw new UnableToZeroInstrumentSensorsException();

            // SGF  14-Jun-2011  INS-1732 -- get sensor readings after zeroing has taken place
            foreach ( InstalledComponent ic in _returnEvent.DockedInstrument.InstalledComponents )
            {
                if ( !( ic.Component is Sensor ) )  // Skip non-sensors.
                    continue;

                Sensor sensor = (Sensor)ic.Component;
                if ( !sensor.Enabled ) // Skip disabled sensors
                    continue;

                SensorGasResponse sgr = _returnEvent.GetSensorGasResponseByUid( sensor.Uid );
                if ( sgr != null )
                {
                    sgr.ReadingAfterZeroing = _instrumentController.GetSensorReading( ic.Position, sensor.Resolution );
                    sgr.TimeAfterZeroing = DateTime.UtcNow;
				}
            }
        }
        catch ( Exception e )
        {
            Log.Error( "Zeroing Sensor", e );
            throw;
        }
        finally
        {
            Log.Debug( "ZEROING: Finished" );

            _instrumentController.CloseGasEndPoint( zeroEndPoint );

            if ( zeroEndPoint != null )  // how could this ever be null?
                _zeroingUsedGasEndPoint = new UsedGasEndPoint( zeroEndPoint, CylinderUsage.Zero, DateTime.UtcNow - startTime, 0 );
        }
    }