/// <summary> /// This method takes one reading for all enabled O2 sensors without flowing any gas /// which saves at least 4 seconds. /// <para>True - Run an O2 recovery purge.</para> /// <para>False - No O2 recovery purge needed.</para> /// </summary> /// <returns></returns> private bool IsFullO2RecoveryPurgeNeeded() { string funcMsg = "IsFullO2RecoveryPurgeNeeded: "; // Take an initial reading for all O2 sensors and only kick off a purge if one of them is not reading above 20%. List <InstalledComponent> o2Components = _returnEvent.DockedInstrument.InstalledComponents.FindAll(c => c.Component.Enabled && c.Component.Type.Code == SensorCode.O2); o2Components = o2Components.FindAll(o2 => _returnEvent.GetSensorGasResponseByUid(o2.Component.Uid) != null); int numO2SensorsPassed = 0; foreach (InstalledComponent ic in o2Components) { Sensor sensor = (Sensor)ic.Component; SensorGasResponse sgr = _returnEvent.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 // INS-7625 SSAM v7.6 Adding test for high threshold to be max 22% VOL // O2 high bump test: Update O2 pass criteria to be between 20% and 22%. if (InstrumentPurgeOperation.OXYGEN_FRESH_AIR_TEST_LOW_PASS_PCT <= sgr.O2HighReading && sgr.O2HighReading <= OXYGEN_FRESH_AIR_TEST_HIGH_PASS_PCT) { Log.Debug(string.Format("{0}O2 sensor UID={1} reading is within normal range.", funcMsg, sensor.Uid)); sgr.Status = Status.Passed; sgr.IsO2HighBumpPassed = true; numO2SensorsPassed++; } } if (numO2SensorsPassed == o2Components.Count) { return(false); // All O2 sensors pass the recovery test; time to short-circuit the purge } return(true); }
} // 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 ); } }