using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Media; using DataPROWin7.Common; using DataPROWin7.DataModel.Classes.Hardware; using DTS.Common.SharedResource.Strings; using DTS.Common.Base; using DTS.Common.DAS.Concepts; using DTS.Common.Enums; using DTS.Common.Enums.Hardware; using DTS.Common.Enums.Sensors; using DTS.Common.Interface.DASFactory; using DTS.Common.Interface.Channels; using DTS.Common.Interface.DataRecorders; using DTS.Common.Interface.DASFactory.Diagnostics; using DTS.Common.Interface.Sensors; using DTS.Common.Utilities.Logging; using DTS.DASLib.Service; using DTS.SensorDB; using DTS.Common.DataModel; using DTS.Common.Interface.Sensors.AnalogDiagnostics; // ReSharper disable ParameterTypeCanBeEnumerable.Local // ReSharper disable UnusedVariable // ReSharper disable RedundantDefaultMemberInitializer // ReSharper disable InconsistentNaming // ReSharper disable CheckNamespace namespace DataPROWin7.DataModel { public class HardwareChannel : BasePropertyChanged, IComparable, IHardwareChannel { private readonly DTS.Common.ISO.HardwareChannel _isoChannel; public DTS.Common.ISO.HardwareChannel GetISOChannel() { return _isoChannel; } /// /// /// gets the DAS this channel belongs to /// /// public IDASHardware GetParentDAS() { return Hardware; } public bool IsG5() { return ModuleSerialNumber.StartsWith("5M"); } public string ModuleSerialNumber { get => null != _isoChannel ? _isoChannel.ModuleSerialNumber : ""; set { if (null == _isoChannel) { return; } _isoChannel.ModuleSerialNumber = value; OnPropertyChanged("ModuleSerialNumber"); } } public bool DisplayModuleSerialNumber => Hardware.CareAboutModules; private DiagnosticStatus _diagnosticStatus = DiagnosticStatus.Untested; public DiagnosticStatus DiagnosticStatus { get => _diagnosticStatus; set { if (DiagnosticStatus.Untested == value) { _diagnostics = null; AutoZeroPercentDeviationStatus = DiagnosticStatus.Untested; ActualRangeStatus = DiagnosticStatus.Untested; OffsetStatus = DiagnosticStatus.Untested; OffsetEUStatus = DiagnosticStatus.Untested; ShuntStatus = DiagnosticStatus.Untested; NoiseStatus = DiagnosticStatus.Untested; ExcitationStatus = DiagnosticStatus.Untested; _24VPowerStatus = DiagnosticStatus.Untested; CalSignalStatus = DiagnosticStatus.Untested; GainStatus = DiagnosticStatus.Untested; } SetProperty(ref _diagnosticStatus, value, "DiagnosticStatus"); } } public int CompareTo(HardwareChannel right) { if (this == right) { return 0; } if (null == right) { return 0; } var order = GetISOChannel().DASDisplayOrder.CompareTo(right.GetISOChannel().DASDisplayOrder); return 0 != order ? order : GetISOChannel().ChannelIdx.CompareTo(right.GetISOChannel().ChannelIdx); } #region Properties /// /// /// returns true if the channel supports analog sensors /// public bool IsAnalog => IsSupportedBridgeType(SensorConstants.BridgeType.FullBridge); /// /// /// returns true if the channel supports squibs /// public bool IsSquib => IsSupportedBridgeType(SensorConstants.BridgeType.SQUIB); /// /// /// returns true if the channel supports digital outputs /// public bool IsDigitalOut => IsSupportedBridgeType(SensorConstants.BridgeType.TOMDigital); /// /// /// returns true if the channel supports digital inputs /// public bool IsDigitalIn => IsSupportedBridgeType(SensorConstants.BridgeType.DigitalInput); /// /// /// returns true if the channel supports clocks /// public bool IsClock => IsSupportedBridgeType(SensorConstants.BridgeType.RTC); /// /// /// returns true if the channel supports uart i/o /// public bool IsUart => IsSupportedBridgeType(SensorConstants.BridgeType.UART); /// /// /// returns true if the channel supports stream output /// public bool IsStreamOut => IsSupportedBridgeType(SensorConstants.BridgeType.StreamOut); /// /// /// returns true if the channel supports stream input /// public bool IsStreamIn => IsSupportedBridgeType(SensorConstants.BridgeType.StreamIn); /// /// Returns true if the channel supports a thermocoupler /// public bool IsThermocoupler => IsSupportedBridgeType(SensorConstants.BridgeType.Thermocoupler); public bool IsCan => IsSupportedBridgeType(SensorConstants.BridgeType.CAN); public bool IsTSRAIR { get { return Hardware.DASTypeEnum == HardwareTypes.DKR || Hardware.DASTypeEnum == HardwareTypes.DIR || Hardware.DASTypeEnum == HardwareTypes.TSR_AIR || Hardware.DASTypeEnum == HardwareTypes.TSR_AIR_RevB; } } public bool IsTSRAIRModule { get { return Hardware.DASTypeEnum == HardwareTypes.EMB_ANG_ACC || Hardware.DASTypeEnum == HardwareTypes.EMB_ANG_ARS || Hardware.DASTypeEnum == HardwareTypes.EMB_ATM || Hardware.DASTypeEnum == HardwareTypes.EMB_LIN_ACC_HI || Hardware.DASTypeEnum == HardwareTypes.EMB_LIN_ACC_LO || Hardware.DASTypeEnum == HardwareTypes.EMB_MAG || Hardware.DASTypeEnum == HardwareTypes.EMB_MAG_SWITCH || Hardware.DASTypeEnum == HardwareTypes.EMB_MIC || Hardware.DASTypeEnum == HardwareTypes.EMB_OPT || Hardware.DASTypeEnum == HardwareTypes.EMB_RTC_S_MARK || Hardware.DASTypeEnum == HardwareTypes.EMB_RTC_NS_PAD; ; } } private bool _excludedFromTSRAIR = false; public bool IsExcludedFromTSRAIR { get => _excludedFromTSRAIR; set => SetProperty(ref _excludedFromTSRAIR, value, "IsExcludedFromTSRAIR"); } /// /// Returns true if the DAS is a SLICE6 AIR-TC /// public bool IsSLICETC { get { return Hardware.DASTypeEnum == HardwareTypes.SLICE6_AIR_TC; } } private DiagnosticStatus _offsetStatus = DiagnosticStatus.Untested; public DiagnosticStatus OffsetStatus { get => _offsetStatus; set => SetProperty(ref _offsetStatus, value, "OffsetStatus"); } private DiagnosticStatus _offsetEUStatus = DiagnosticStatus.Untested; /// /// the current status of the Initial Offset in EU /// public DiagnosticStatus OffsetEUStatus { get => _offsetEUStatus; set => SetProperty(ref _offsetEUStatus, value, "OffsetEUStatus"); } private DiagnosticStatus _autoZeroPercentDeviationStatus = DiagnosticStatus.Untested; public DiagnosticStatus AutoZeroPercentDeviationStatus { get => _autoZeroPercentDeviationStatus; set => SetProperty(ref _autoZeroPercentDeviationStatus, value, "AutoZeroPercentDeviationStatus"); } private DiagnosticStatus __24VPowerStatus = DiagnosticStatus.Untested; public DiagnosticStatus _24VPowerStatus { get => __24VPowerStatus; set => SetProperty(ref __24VPowerStatus, value, "_24VPowerStatus"); } private DiagnosticStatus _excitationStatus = DiagnosticStatus.Untested; public DiagnosticStatus ExcitationStatus { get => _excitationStatus; set => SetProperty(ref _excitationStatus, value, "ExcitationStatus"); } private DiagnosticStatus _calSignalStatus = DiagnosticStatus.Untested; public DiagnosticStatus CalSignalStatus { get => _calSignalStatus; set => SetProperty(ref _calSignalStatus, value, "CalSignalStatus"); } private DiagnosticStatus _shuntStatus = DiagnosticStatus.Untested; public DiagnosticStatus ShuntStatus { get => _shuntStatus; set => SetProperty(ref _shuntStatus, value, "ShuntStatus"); } private DiagnosticStatus _delayStatus = DiagnosticStatus.Untested; public DiagnosticStatus DelayStatus { get => _delayStatus; set => SetProperty(ref _delayStatus, value, "DelayStatus"); } private DiagnosticStatus _durationStatus = DiagnosticStatus.Untested; public DiagnosticStatus DurationStatus { get => _durationStatus; set => SetProperty(ref _durationStatus, value, "DurationStatus"); } private DiagnosticStatus _actualRangeStatus = DiagnosticStatus.Untested; public DiagnosticStatus ActualRangeStatus { get => _actualRangeStatus; set => SetProperty(ref _actualRangeStatus, value, "ActualRangeStatus"); } private DiagnosticStatus _gainStatus = DiagnosticStatus.Untested; public DiagnosticStatus GainStatus { get => _gainStatus; set => SetProperty(ref _gainStatus, value, "GainStatus"); } private DiagnosticStatus _noiseStatus = DiagnosticStatus.Untested; public DiagnosticStatus NoiseStatus { get => _noiseStatus; set => SetProperty(ref _noiseStatus, value, "NoiseStatus"); } #endregion Properties /// /// returns the high/low acceptable values for 24V /// /// /// public double Get24VPowerValue(bool bHigh) { return bHigh ? SerializedSettings._24VPowerHigh : SerializedSettings._24VPowerLow; } /// /// 14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics /// returns the threshold according to allowed excitation error percent /// /// whether to return low or high threshold /// public double GetExcitationPercent(bool bHigh) { return DataModelSettings.AllowedExcitationErrorPercent * (bHigh ? 1D : -1D); } /// /// returns excitation in mV /// /// public double GetMeasuredExcitationMV() { if (null == _diagnostics) { return double.NaN; } if (null == _diagnostics.MeasuredExcitationMilliVolts) { return double.NaN; } return (double)_diagnostics.MeasuredExcitationMilliVolts; } /// /// 14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics /// returns the measured excitation as calculated by MeasX -ExpX/ ExpX /// /// public double GetExcitationPercentValue() { if (null == _diagnostics) { return double.NaN; } if (null == _diagnostics.MeasuredExcitationMilliVolts) { return double.NaN; } var exc = (double)_diagnostics.MeasuredExcitationMilliVolts; //since measured is absolute, we check if we read a negative when it was read //and reapply it here //14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics if (_diagnostics.NegativeExcitation) { exc *= -1D; } return 100D * (exc - _diagnostics.ExpectedExcitationMilliVolts) / _diagnostics.ExpectedExcitationMilliVolts; } public double GetAllowedExcitationValue(bool bHigh) { if (null == _diagnostics) { return double.NaN; } return bHigh ? (1.0D + DataModelSettings.AllowedExcitationErrorPercent / 100.0D) * _diagnostics.ExpectedExcitationMilliVolts : (1.0D - DataModelSettings.AllowedExcitationErrorPercent / 100.0D) * _diagnostics.ExpectedExcitationMilliVolts; } /// /// indicates eu is inverted, this is needed to calculate from mv to eu or vice versa /// private bool InvertEU { get => DataModel.HardwareChannel.IsInvertedEU(Sensor, Sensor.Calibration); } /// /// retrieves the tolerance value (high/low, mv/EU) /// public double GetOffsetToleranceValue(bool bHigh, bool bEU) { if (bEU) { if (InvertEU) { bHigh = !bHigh; } if (null == Sensor.Calibration) { return 0D; } if (!Sensor.Calibration.NonLinear || Sensor.Calibration.LinearAdded) { var mV = bHigh ? Sensor.OffsetToleranceHigh : Sensor.OffsetToleranceLow; var scaler = GetDataScaler(Sensor, Sensor.Calibration, true, true, this, GroupChannel); return scaler.GetEUFromMv(mV); } else { //for now non linear is just expressed as eu return bHigh ? Sensor.OffsetToleranceHigh : Sensor.OffsetToleranceLow; } } else { return bHigh ? Sensor.OffsetToleranceHigh : Sensor.OffsetToleranceLow; } } public double GetAutoZeroPercentDeviationValue() { if (_diagnostics?.AutoZeroPercentDeviation == null) { return double.NaN; } return (double)_diagnostics.AutoZeroPercentDeviation; } public double GetAllowedVoltageInsertionError(bool bHigh) { return bHigh ? DataModelSettings.AllowedVoltageInsertionErrorPercent : -1D * DataModelSettings.AllowedVoltageInsertionErrorPercent; } public double GetDelayMS() { if (_diagnostics?.MeasuredDelayMS == null) { return double.NaN; } return (double)_diagnostics.MeasuredDelayMS; } public double GetDurationMS() { if (_diagnostics?.MeasuredDurationMS == null) { return double.NaN; } return (double)_diagnostics.MeasuredDurationMS; } public double GetBridgeResistance() { if (_diagnostics?.BridgeResistance == null) { return double.NaN; } return (double)_diagnostics.BridgeResistance; } public double[] GetCurrentData() { return null == _diagnostics ? new double[0] : _diagnostics.SquibFireCurrentData; } public double[] GetVoltageData() { return null == _diagnostics ? new double[0] : _diagnostics.SquibFireVoltageData; } public double GetMeasuredExcitation() { if (_diagnostics?.MeasuredExcitationMilliVolts == null) { return double.NaN; } return (double)_diagnostics.MeasuredExcitationMilliVolts; } /// /// 14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics /// this returns true if measured excitation was negative when it was read /// it seems only TDAS code is currently absoluting excitation though /// /// public bool HasNegativeExcitationReading() { return _diagnostics?.NegativeExcitation ?? false; } public double GetInitialOffset(bool bEU) { if (null == _diagnostics) { return double.NaN; } if (_diagnostics.MeasuredOffsetMilliVolts == null && _diagnostics.MeasuredOffsetEngineeringUnits == null) { return double.NaN; } if (null == _diagnostics.MeasuredInternalOffsetMilliVolts || double.IsNaN((double)_diagnostics.MeasuredInternalOffsetMilliVolts)) { return bEU ? GetEU((double)_diagnostics.MeasuredOffsetMilliVolts) : (double)_diagnostics.MeasuredOffsetMilliVolts; } if (null != _diagnostics.MeasuredOffsetEngineeringUnits) { return (double)_diagnostics.MeasuredOffsetEngineeringUnits; } var mv = (double)_diagnostics.MeasuredOffsetMilliVolts - (double)_diagnostics.MeasuredInternalOffsetMilliVolts; return bEU ? GetEU(mv) : mv; } /// /// retrieves EU for a given mV /// private double GetEU(double mV) { if (null == Sensor.Calibration) { return 0D; } var scaler = GetDataScaler(Sensor, Sensor.Calibration, true, true, this, GroupChannel); return scaler.GetEUFromMv(mV); } public double GetFinalOffset() { if (_diagnostics?.FinalOffsetADC == null) { return double.NaN; } return (double)(_diagnostics.FinalOffsetADC - _diagnostics.ZeroMVInADC) * _diagnostics.ScalefactorMilliVoltsPerADC; } #region Static things to abstract out to common area? /// /// returns the first valid sensitivity according to proportionality and supported excitations /// returns 1 if no valid excitations are found and sensor is proportional... /// /// /// /// public static double GetSensitivity(ISensorBase sensorBase, ISensorCalibration sc, bool useAddedLinear = false, IHardwareChannel hardwareChannel = null) { var records = DataModel.HardwareChannel.GetCalibrationRecords(sc, useAddedLinear); if (!sc.IsProportional) { return records[0].Sensitivity; } foreach (var se in sensorBase.SupportedExcitation) { if (null != hardwareChannel) { if (!hardwareChannel.IsSupportedExcitation(se)) { continue; } } foreach (var record in records) { if (record.Excitation == se) { return record.Sensitivity; } } } //no valid excitation found, we could throw an exception, lets just return 1 for now return 1; } /// /// returns the calibration records given a sensor calibration /// optionally strips off the non linear cal if requested for dual linear/non linear sensors /// /// /// /// public static ICalibrationRecord[] GetCalibrationRecords(ISensorCalibration sc, bool useAddedLinear) { return sc.LinearAdded && useAddedLinear ? sc.Records.Records.Skip(1).ToArray() : sc.Records.Records; } /// /// returns whether eu is inverted given a sensor and a calibration /// [temporary place for this] /// @TODO abstract out /// /// /// /// public static bool IsInvertedEU(ISensorBase sensorBase, ISensorCalibration sc) { if (null == sensorBase) { return false; } var invert = sensorBase.Invert; var sens = GetSensitivity(sensorBase, sc, true); if (sens < 0) { invert = !invert; } return invert; } /// /// returns a data scaler for the current hardware channel /// /// /// whether to clear user and diagnostic set parameters like /// FinalOffsetADC, UserOffset, user set InitialOffset, etc, parameters which affect EU /// values. This allows a consumer to get an EU value without consideration of adjustments /// this is used to get an offset in EU without being padded /// /// public static DataScaler GetDataScaler(ISensorBase sensor, ISensorCalibration sc, bool useAddedLinear = false, bool clearEUAdjustmentParams = false, IHardwareChannel hardwareChannel = null, IGroupChannel groupChannel = null) { var ds = new DataScaler(); var records = GetCalibrationRecords(sc, useAddedLinear); if (sc.NonLinear && !(sc.LinearAdded && useAddedLinear)) //18276 don't set a linearization formula if the cal we want to use is the linear of a dual-cal { ds.SetLinearizationFormula(records[0].Poly); } if (null != hardwareChannel && null != hardwareChannel.Diagnostics) { //hardware with diagnostics stuff if (sc != null && (sc.LinearAdded && useAddedLinear ? sc.ZeroMethods.Methods[sc.ZeroMethods.Methods.Length - 1] : sc.ZeroMethods.Methods[0]).Method == ZeroMethodType.None) { //if ZeroMethod is "None" don't apply ANY zeroing ds.SetDataZeroLevelADC(0); } else { if (null == hardwareChannel.Diagnostics.FinalOffsetADC) { ds.SetDataZeroLevelADC(hardwareChannel.Diagnostics.ZeroMVInADC); } else { ds.SetDataZeroLevelADC((short)hardwareChannel.Diagnostics.FinalOffsetADC); } } if (null != hardwareChannel.Diagnostics.RemovedOffsetADC) { ds.SetRemovedADC((int)hardwareChannel.Diagnostics.RemovedOffsetADC); } if (null != hardwareChannel.Diagnostics.RemovedInternalOffsetADC) { ds.SetRemovedInternalADC((int)hardwareChannel.Diagnostics.RemovedInternalOffsetADC); } if (hardwareChannel.IsTSRAIR) { // EU only ds.SetScaleFactorEU(hardwareChannel.Diagnostics.ScalefactorEngineeringUnitsPerADC); ds.SetUseEUScaleFactors(true); // i.e. use EU rather than Mv } else { ds.SetScaleFactorMv(hardwareChannel.Diagnostics.ScalefactorMilliVoltsPerADC); ds.SetZeroMvInADC(hardwareChannel.Diagnostics.ZeroMVInADC); } ds.MeasuredExcitationVoltage = null == hardwareChannel.Diagnostics.MeasuredExcitationMilliVolts ? 0D : (double)hardwareChannel.Diagnostics.MeasuredExcitationMilliVolts / 1000D; ds.FactoryExcitationVoltage = hardwareChannel.Diagnostics.ExpectedExcitationMilliVolts / 1000D; } else { //no hardware channel specific stuff if (sc.IsProportional) { bool bFound = false; foreach (var se in sensor.SupportedExcitation) { if (bFound) { break; } if (null != hardwareChannel && !hardwareChannel.IsSupportedExcitation(se)) { continue; } foreach (var record in records) { if (record.Excitation == se) { ds.FactoryExcitationVoltage = Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(se); ds.MeasuredExcitationVoltage = ds.FactoryExcitationVoltage; bFound = true; break; } } } if (!bFound) { ds.FactoryExcitationVoltage = 5D; //couldn't find a valid excitation, throw exception? ds.MeasuredExcitationVoltage = 5D; } } } //common initialization stuff var zeromethod = sc.LinearAdded && useAddedLinear ? sc.ZeroMethods.Methods[sc.ZeroMethods.Methods.Length - 1] : sc.ZeroMethods.Methods[0]; var initialoffset = sc.InitialOffsets.DefaultOffset; if (null != groupChannel) { initialoffset = groupChannel.InitialOffset; } ds.ZeroMethodType = zeromethod.Method; ds.UnitConversion = MeasurementUnitList.GetMeasurementUnit(records[0].EngineeringUnits).GetScalerConversion(sensor.DisplayUnit); ds.SensitivityUnits = records[0].SensitivityUnits; ds.SetInitialOffset(initialoffset); ds.SetMvPerEu(GetSensitivity(sensor, sc, useAddedLinear)); //data scaler needs to get inverted or not only from polarity as negative sensitivity //shows up through scale factor ds.IsInverted = sensor.Invert; ds.ProportionalToExcitation = sc.IsProportional; ds.BasedOnOutputAtCapacity = records[0].AtCapacity; ds.CapacityOutputIsBasedOn = records[0].CapacityOutputIsBasedOn; ds.SensitivityUnits = records[0].SensitivityUnits; ds.IEPE = sensor.Bridge == SensorConstants.BridgeType.IEPE; ds.Digital = sensor.IsDigitalInput(); ds.DigitalOutput = sensor.IsDigitalOutput(); if (sensor is SensorData sd) { ds.DigitalMode = sd.InputMode; ds.SetDigitalMultiplier(sd.ScaleMultiplier); } if (clearEUAdjustmentParams) { ds.SetInitialOffset(new DTS.Common.Classes.Sensors.InitialOffset(0)); ds.UserOffsetEU = 0D; ds.SetDataZeroLevelADC(0); } return ds; } #endregion public double GetCapacityOutputIsBasedOn(ISensorCalibration sc) { return sc.Records.Records[0].AtCapacity ? sc.Records.Records[0].CapacityOutputIsBasedOn : 1.0; } /// /// returns preferred excitation given hardware channel and sensor /// /// /// public double GetExcitation(ISensorCalibration sc) { var preferredExcitation = ExcitationVoltageOptions.ExcitationVoltageOption.Undefined; foreach (var se in Sensor.SupportedExcitation) { if (!IsSupportedExcitation(se)) continue; preferredExcitation = se; break; } if (preferredExcitation == ExcitationVoltageOptions.ExcitationVoltageOption.Undefined) { throw new NotSupportedException(string.Format(StringResources.HardwareChannel_SensorVoltageNotSupported, GetId(), ChannelName, Sensor.SerialNumber)); } return Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(preferredExcitation); } public double GetNoisePercentage() { if (_diagnostics?.NoisePercentFullScale == null) { return double.NaN; } return (double)_diagnostics.NoisePercentFullScale; } public double GetDesiredRange() { return Sensor?.Capacity ?? 0; } public double GetActualRangeEu(bool useLinearAdded = false) { if (null == Sensor) return 0; if (_diagnostics?.ScalefactorMilliVoltsPerADC == null && _diagnostics?.ScalefactorEngineeringUnitsPerADC == null) { return double.NaN; } SensorCalibration sc = null; foreach (var se in Sensor.SupportedExcitation) { //for now just accept any excitation for thermocouples if (!IsSupportedExcitation(se) && !Sensor.IsThermocoupler()) continue; sc = SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(Sensor, se); if (null != sc) { break; } } if (null == sc) { throw new NotSupportedException(string.Format(StringResources.HardwareChannel_CalibrationNotFound, GetId(), Sensor.SerialNumber, ChannelName)); } var uniPolarMultiplier = Sensor.UniPolar ? 2D : 1D; //14055 Range failure in diagnostics when using a dual-sensitivity sensor // non linear sensors just return capacity if (sc.LinearAdded) { if (useLinearAdded) { if (sc.IsProportional && sc.Records.Records.Last().SensitivityUnits != SensorConstants.SensUnits.mVperEU) { var dSens = GetSensitivity(Sensor, sc, useLinearAdded, this); var dExc = GetExcitation(sc); var dEU = GetCapacityOutputIsBasedOn(sc); return Math.Abs(ActualRangeMv / (dSens * dExc / dEU)) * uniPolarMultiplier; } else { return (ActualRangeMv / Math.Abs(GetSensitivity(Sensor, sc, useLinearAdded, this) / GetCapacityOutputIsBasedOn(sc))) * uniPolarMultiplier; } } else { return Sensor.Capacity; } } if (sc.IsProportional && sc.Records.Records[0].SensitivityUnits != SensorConstants.SensUnits.mVperEU) { var dSens = GetSensitivity(Sensor, sc, useLinearAdded, this); var dExc = GetExcitation(sc); var dEU = GetCapacityOutputIsBasedOn(sc); return Math.Abs(ActualRangeMv / (dSens * dExc / dEU)) * uniPolarMultiplier; } if (sc.NonLinear) { return Sensor.Capacity; } if (IsTSRAIR && null != _diagnostics?.ScalefactorEngineeringUnitsPerADC) { //we're an embedded analog sensor: just multiply scale * maxvalue similar to Mv var scalefactor = _diagnostics.ScalefactorEngineeringUnitsPerADC; return Math.Abs(scalefactor * short.MaxValue); } return (ActualRangeMv / Math.Abs(GetSensitivity(Sensor, sc, useLinearAdded, this) / GetCapacityOutputIsBasedOn(sc))) * uniPolarMultiplier; } public double ActualRangeMv { get { try { if (null != _diagnostics) { var scalefactor = _diagnostics.ScalefactorMilliVoltsPerADC; return Math.Abs(scalefactor * short.MaxValue); } } catch (Exception ex) { APILogger.Log(ex); } return 2400D; } } /// /// returns the High/Low thresholds for gain based on measured gain /// and allowed gain percent error /// public double GetGainThreshold(bool low) { if (_diagnostics?.TargetGain == null) { return double.NaN; } return (double)_diagnostics?.TargetGain * (100D + GetAllowedGainPercentError(low)) / 100D; } /// /// returns the actual measured gain /// public double GetGainValue() { if (_diagnostics?.MeasuredGain == null) { return double.NaN; } return (double)_diagnostics.MeasuredGain; } public double GetGainPercentError() { if (_diagnostics?.MeasuredGain == null || null == _diagnostics.TargetGain) { return double.NaN; } return (double)(100D * (_diagnostics.MeasuredGain - _diagnostics.TargetGain) / _diagnostics.TargetGain); } public double GetAllowedGainPercentError(bool bHigh) { var gainThreshold = DataModelSettings.AllowedGainErrorPercent; // https://dtsweb.zendesk.com/agent/tickets/7982 // http://manuscript.dts.local/f/cases/16473 // Add additional AllowedGainErrorPercent for SLICE6 SLICE6A to system settings at 5% if (Hardware.IsSLICE6OrSLICE6A) { gainThreshold = DataModelSettings.AllowedGainErrorPercent_SLICE6andSLICE6A; } return bHigh ? gainThreshold : -1D * gainThreshold; } public double GetAllowedNoisePercentError(bool bHigh) { var percent = SerializedSettings.AllowedNoisePercentage; return bHigh ? percent : -1D * percent; } public double GetAllowedShuntPercentError(bool bHigh) { if (null == _diagnostics) { return double.NaN; } return bHigh ? DataModelSettings.AllowedShuntErrorPercent : -1 * DataModelSettings.AllowedShuntErrorPercent; } public double GetAllowedShuntHighOhmPercentError(bool bHigh) { //FB 28086 Read high ohm allowed shunt percent error if (null == _diagnostics) { return double.NaN; } return bHigh ? DataModelSettings.ShuntToleranceHighOhmPercent : -1 * DataModelSettings.ShuntToleranceHighOhmPercent; } public double GetShuntPercentError() { if (_diagnostics?.TargetShuntDeflectionMv == null || null == _diagnostics.MeasuredShuntDeflectionMv) { return double.NaN; } return (double)(100D * (_diagnostics.MeasuredShuntDeflectionMv - _diagnostics.TargetShuntDeflectionMv) / _diagnostics.TargetShuntDeflectionMv); } public string GetOverallStatus() { switch (DiagnosticStatus) { case DiagnosticStatus.Failed: return "Fail"; case DiagnosticStatus.Passed: return "Pass"; default: return "---"; } } public IDiagnosticResult _diagnostics = null; public IDiagnosticResult Diagnostics { get => _diagnostics; } public void UpdateDiagnostics(IDiagnosticResult squibChannelDiagnostics, bool bPostTest) { if (bPostTest) { DiagnosticStatus = DiagnosticStatus.Untested; return; } _diagnostics = squibChannelDiagnostics; if (null == _diagnostics) { DiagnosticStatus = DiagnosticStatus.Untested; } else if (null != _diagnostics.SquibFirePassed) { if (null != _diagnostics.SquibDelayPassed) { DelayStatus = (bool)_diagnostics.SquibDelayPassed ? DiagnosticStatus.Passed : DiagnosticStatus.Failed; } else { DelayStatus = DiagnosticStatus.Untested; } if (null != _diagnostics.SquibDurationPassed) { DurationStatus = ((bool)_diagnostics.SquibDurationPassed) ? DiagnosticStatus.Passed : DiagnosticStatus.Failed; } else { DurationStatus = DiagnosticStatus.Untested; } DiagnosticStatus = ((bool)_diagnostics.SquibFirePassed) ? DiagnosticStatus.Passed : DiagnosticStatus.Failed; } else { DelayStatus = DiagnosticStatus.Failed; DurationStatus = DiagnosticStatus.Failed; DiagnosticStatus = DiagnosticStatus.Failed; } } public void UpdateDiagnostics(OutputTOMDigitalChannel channel) { DiagnosticStatus = DiagnosticStatus.Passed; } public void UpdateDiagnostics(IDiagnosticResult result) { try { _diagnostics = result; if (null == result && null != Sensor) { OffsetStatus = DiagnosticStatus.Untested; OffsetEUStatus = DiagnosticStatus.Untested; ExcitationStatus = DiagnosticStatus.Untested; __24VPowerStatus = DiagnosticStatus.Untested; } else if (null != Sensor) { var bPassed = true; if (result?.MeasuredOffsetMilliVolts != null && Sensor.CheckOffset) { var mV = GetInitialOffset(false); //we only want to set EU status if display offset eu is true (and it has a linear cal, etc) //otherwise show and calculate mV var setEUStatus = SerializedSettings.DisplayEUOffset && null != Sensor.Calibration && (!Sensor.Calibration.NonLinear || Sensor.Calibration.LinearAdded); if (!setEUStatus) { if (mV > Sensor.OffsetToleranceHigh) { bPassed = false; OffsetStatus = DiagnosticStatus.Failed; } else if (mV < Sensor.OffsetToleranceLow) { bPassed = false; OffsetStatus = DiagnosticStatus.Failed; } else { OffsetStatus = DiagnosticStatus.Passed; } } else { OffsetStatus = DiagnosticStatus.Untested; } if (setEUStatus) { var eu = GetEU(mV); var euLow = GetOffsetToleranceValue(false, true); var euHigh = GetOffsetToleranceValue(true, true); if (eu < euLow || eu > euHigh) { bPassed = false; OffsetEUStatus = DiagnosticStatus.Failed; } else { OffsetEUStatus = DiagnosticStatus.Passed; } } else { OffsetEUStatus = DiagnosticStatus.Untested; } } else if (result?.MeasuredOffsetEngineeringUnits != null && Sensor.CheckOffset) { // TODO: REMOVE THIS HACK when offset tolerance of embedded sensors figured out var eu = GetInitialOffset(true); var euLow = short.MinValue; var euHigh = short.MaxValue; if (eu < euLow || eu > euHigh) { bPassed = false; OffsetEUStatus = DiagnosticStatus.Failed; } else { OffsetEUStatus = DiagnosticStatus.Passed; } } else { OffsetStatus = DiagnosticStatus.Untested; } if (result?.MeasuredExcitationMilliVolts != null) { if (Sensor.IsDigitalInput()) { //12476 SLICE PRO DIM failed diagnostics in Run Test tile //Excitation is not valid on SLICE PRO DIM (I'm guessing also TDAS?) ExcitationStatus = DiagnosticStatus.Untested; _24VPowerStatus = DiagnosticStatus.Untested; } else { if (Sensor.Bridge == SensorConstants.BridgeType.IEPE) { var eV = (double)result.MeasuredExcitationMilliVolts / 1000D; if (eV < Get24VPowerValue(false) || eV > Get24VPowerValue(true)) { bPassed = false; _24VPowerStatus = DiagnosticStatus.Failed; } else { _24VPowerStatus = DiagnosticStatus.Passed; } } else { var eV = (double)result.MeasuredExcitationMilliVolts; if (eV < GetAllowedExcitationValue(false) || eV > GetAllowedExcitationValue(true)) { bPassed = false; ExcitationStatus = DiagnosticStatus.Failed; } else { if (result.NegativeExcitation) { ExcitationStatus = DiagnosticStatus.Failed; bPassed = false; } else { ExcitationStatus = DiagnosticStatus.Passed; } } } } } if (result?.FinalOffsetADC != null) { SensorCalibration sc2 = null; foreach (var se in Sensor.SupportedExcitation) { if (!IsSupportedExcitation(se)) continue; sc2 = SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(Sensor, se); if (null != sc2) { break; } } if (null != sc2 && sc2.RemoveOffset) { var finalOffsetADC = (short)result.FinalOffsetADC; var percent = Math.Abs(100D * finalOffsetADC * result.ScalefactorMilliVoltsPerADC / ActualRangeMv); if (DataModelSettings.AutoZeroPercentDeviationAllowed <= GetAutoZeroPercentDeviationValue()) { AutoZeroPercentDeviationStatus = DiagnosticStatus.Failed; bPassed = false; } else { AutoZeroPercentDeviationStatus = DiagnosticStatus.Passed; } } else { AutoZeroPercentDeviationStatus = DiagnosticStatus.Untested; } } else { AutoZeroPercentDeviationStatus = DiagnosticStatus.Untested; } if (result?.MeasuredCalSignalMv != null && null != result.TargetCalSignalMv) { var mVActual = (double)result.MeasuredCalSignalMv; var mVExpected = (double)result.TargetCalSignalMv; var percent = Math.Abs((mVActual - mVExpected) / mVExpected * 100.0D); if (percent > GetAllowedVoltageInsertionError(true)) { CalSignalStatus = DiagnosticStatus.Failed; bPassed = false; } else { CalSignalStatus = DiagnosticStatus.Passed; } } else { CalSignalStatus = DiagnosticStatus.Untested; } if (result?.MeasuredShuntDeflectionMv != null && null != result.TargetShuntDeflectionMv) { //FB 28086 Adjust shunt tolerance for high ohm sensors var sensorResistance = Sensor.BridgeResistance; var allowedShuntPercentError = sensorResistance > DataModelSettings.ShuntToleranceHighOhmResistance ? GetAllowedShuntHighOhmPercentError(true) : GetAllowedShuntPercentError(true); var actualShuntPercentError = Math.Abs(GetShuntPercentError()); if (actualShuntPercentError > allowedShuntPercentError) { bPassed = false; ShuntStatus = DiagnosticStatus.Failed; } else { ShuntStatus = DiagnosticStatus.Passed; } } else { ShuntStatus = DiagnosticStatus.Untested; } if (result?.SquibDurationPassed != null) { DurationStatus = ((bool)result.SquibDurationPassed) ? DiagnosticStatus.Passed : DiagnosticStatus.Failed; } if (result?.SquibDelayPassed != null) { DelayStatus = ((bool)result.SquibDelayPassed) ? DiagnosticStatus.Passed : DiagnosticStatus.Failed; } if (result?.MeasuredGain != null && null != result.TargetGain) { if (Math.Abs(GetGainPercentError()) > GetAllowedGainPercentError(true)) { bPassed = false; GainStatus = DiagnosticStatus.Failed; } else { GainStatus = DiagnosticStatus.Passed; } } else { GainStatus = DiagnosticStatus.Untested; } if (result != null) { var actualRangemV = result.ScalefactorMilliVoltsPerADC * short.MaxValue; } foreach (var se in Sensor.SupportedExcitation) { if (!IsSupportedExcitation(se)) continue; var sc = SensorCalibration.GetLatestCalibrationBySerialNumberAndExcitation(Sensor, se); if (null != sc) { break; } } //14055 Range failure in diagnostics when using a dual-sensitivity sensor // actual range can be complicated as there could be both a non linear and a linear actual range for the channel var actualRangeEu = GetActualRangeEu(); if (!Sensor.IsDigitalInput()) // 7113 - Diagnostics failed on Slice PRO DIM { if (Sensor.Bridge == SensorConstants.BridgeType.IEPE) { var low = SerializedSettings.IEPERangeLowLimitScalar * Sensor.Capacity; var high = SerializedSettings.IEPERangeHighLimitScalar * Sensor.Capacity; if (actualRangeEu < low || actualRangeEu > high) { ActualRangeStatus = DiagnosticStatus.Failed; bPassed = false; } else { ActualRangeStatus = DiagnosticStatus.Passed; } } else if (!Sensor.IsTestSpecificEmbedded && !Sensor.IsTestSpecificThermo) { if (actualRangeEu < (Sensor.Capacity * SerializedSettings.ActualRangeLowerLimit) || actualRangeEu > (Sensor.Capacity * SerializedSettings.ActualRangeUpperLimit * SerializedSettings.DesiredRangeOverheadPercent)) { ActualRangeStatus = DiagnosticStatus.Failed; bPassed = false; } else { ActualRangeStatus = DiagnosticStatus.Passed; } } } else { //10342 Diagnostic results not consistent with TDC for TDAS DIM channels. //9912 Fix for diagnostics using digital inputs on a TDAS G5. switch (Hardware.GetHardwareTypeEnum()) { case HardwareTypes.TDAS_Pro_Rack: case HardwareTypes.G5VDS: case HardwareTypes.TDAS_LabRack: bPassed = true; break; default: if (result != null && result.DigitalInputActiveState) { bPassed = false; } break; } } if (null != result.NoisePercentFullScale) { if (Math.Abs((double)result.NoisePercentFullScale) > GetAllowedNoisePercentError(true)) { bPassed = false; NoiseStatus = DiagnosticStatus.Failed; } else { NoiseStatus = DiagnosticStatus.Passed; } } if (Sensor.IsThermocoupler()) { //use the status already set???? } else { DiagnosticStatus = bPassed ? DiagnosticStatus.Passed : DiagnosticStatus.Failed; } } else { DiagnosticStatus = DiagnosticStatus.Passed; } } catch (Exception ex) { APILogger.Log("failed to updatediagnostics ", ex); DiagnosticStatus = DiagnosticStatus.Failed; } } public override string ToString() { var parent = GetParentDAS(); if (null != parent) { return ToString(new[] { parent }); } return ToString(null); } public string ToString(IDASHardware[] hardwares) { if (null == GetISOChannel()) { return string.Empty; // should never get here } var index = GetISOChannel().DASDisplayOrder; if (index < 0) { index = ChannelNumber; } if (null != Hardware && Hardware.IsModule() && Hardware.IsPseudoRackModule()) { var channelRepresentation = new ChannelRepresentation(this, 1 + index, hardwares); if (!DataModelSettings.ShowCompactHardware) { return Hardware.IsTSRAIR() ? $"{channelRepresentation.SerialNumber} {channelRepresentation.ChannelNumberString}" : $"[{Hardware.SerialNumber}] {channelRepresentation.ChannelNumberString}"; } return $"[{Hardware}] {channelRepresentation.ChannelNumberString}"; } else { var channelRepresentation = new ChannelRepresentation(this, 1 + index, hardwares); if (null == Hardware) return $"[{channelRepresentation.SerialNumber}] {channelRepresentation.ChannelNumberString}"; if (Hardware.CareAboutModules) { if (Hardware.IsTSRAIR()) { return $"{channelRepresentation.SerialNumber} {channelRepresentation.ChannelNumberString}"; } else { return $"[{channelRepresentation.DASSerialNumber}:{channelRepresentation.SerialNumber}] {channelRepresentation.ChannelNumberString}"; } } //16083 DAS channel # not incremented correctly for generic DAS //return a special formatted string if standin if (Hardware.GetHardware().StandIn) { return GetStandInToString(channelRepresentation); } return $"[{channelRepresentation.SerialNumber}] {channelRepresentation.ChannelNumberString}"; } } /// /// gets a formatted string for the channel for stand in hardware /// this is specific to stacks and labeling them channels 1-n ... for now /// 16083 DAS channel # not incremented correctly for generic DAS /// /// /// private string GetStandInToString(ChannelRepresentation ch) { switch (Hardware.GetHardwareTypeEnum()) { case HardwareTypes.SLICE1_5_Micro_Base: case HardwareTypes.SLICE1_5_Nano_Base: case HardwareTypes.SLICE_Base: case HardwareTypes.SLICE_NANO_Base: case HardwareTypes.SLICE_Micro_Base: return $"[{ch.SerialNumber}] {1 + ChannelNumber}"; case HardwareTypes.TDAS_Pro_Rack: case HardwareTypes.TDAS_LabRack: return $"[{ch.DASSerialNumber}:{ch.SerialNumber}] {ch.ChannelNumberString}"; } return $"[{ch.SerialNumber}] {ch.ChannelNumberString}"; } public string GetId() { return $"{Hardware.GetHardware().GetId()}x{1 + ChannelNumber}"; } public string GetIdSimple(DASHardware[] hardwares = null) { var index = GetISOChannel().DASDisplayOrder; if (index < 0) { index = ChannelNumber; } var channelRepresentation = new ChannelRepresentation(this, 1 + index, hardwares); if (null != Hardware && Hardware.IsModule() && Hardware.IsPseudoRackModule()) { return $"[{Hardware}] {channelRepresentation.ChannelNumberString}"; } return $"[{channelRepresentation.SerialNumber}] {channelRepresentation.ChannelNumberString}"; } public static string GetId(IDASCommunication das, DASChannel channel) { var h = DASHardwareList.GetList().GetHardware(das.SerialNumber, ((ICommunication)das).ConnectString) ?? new DASHardware(das); return $"{h.GetHardware().GetId()}x{1 + channel.Number}"; } public static string GetId(DASHardware hardware, DASChannel channel) { return $"{hardware.GetHardware().GetId()}x{1 + channel.Number}"; } public static ChannelRepresentation GetIdSimple(IDASCommunication das, DASChannel channel) { var h = DASHardwareList.GetList().GetHardware(das.SerialNumber, ((ICommunication)das).ConnectString) ?? new DASHardware(das); var channelRepresentation = new ChannelRepresentation(h, channel, 1 + channel.Number); return channelRepresentation; } public static ChannelRepresentation GetIdSimple(IDASCommunication das, DASChannel channel, int channelNumber) { var h = DASHardwareList.GetList().GetHardware(das.SerialNumber, ((ICommunication)das).ConnectString) ?? new DASHardware(das); ChannelRepresentation channelRepresentation; if (h.DASTypeEnum == HardwareTypes.SLICE1_G5Stack) { channelNumber = channel.AbsoluteDisplayOrder; if (channel is AnalogInputDASChannel) { var channelOrder = das.GetChannelDisplayOrder(); channelNumber = channelOrder[channel.Number]; } channelRepresentation = new ChannelRepresentation(h, channel, 1 + channelNumber); } else { channelRepresentation = new ChannelRepresentation(h, channel, channelNumber); } return channelRepresentation; } //private TestObjectChannel _testObjectChannel; //public TestObjectChannel TestObjectChannel //{ // get => _testObjectChannel; // set { SetProperty(ref _testObjectChannel, value, "TestObjectChannel"); OnPropertyChanged("ChannelName"); } //} private IGroupChannel _channel; public IGroupChannel GroupChannel { get => _channel; set { SetProperty(ref _channel, value, "GroupChannel"); OnPropertyChanged("ChannelName"); } } public string IsoChannelName => null == GroupChannel ? "N/A" : GroupChannel.IsoChannelName; public string UserCode => null == GroupChannel ? "N/A" : GroupChannel.UserCode; public string UserChannelName => null == GroupChannel ? "N/A" : GroupChannel.UserChannelName; public string ChannelName => null == GroupChannel ? "N/A" : GroupChannel.GetChannelName(SerializedSettings.ISOViewMode); public string DisplayName => null == GroupChannel ? "N/A" : GroupChannel.GetChannelName(SerializedSettings.ISOViewMode); public Visibility UnassignVisibility => null == _sensor ? Visibility.Collapsed : Visibility.Visible; public HardwareChannel(HardwareChannel copy) { ChannelNumber = copy.ChannelNumber; if (null != copy.Sensor) { _sensor = new SensorData(copy._sensor); } _channel = copy._channel; Hardware = copy.Hardware; _isoChannel = copy._isoChannel; } public HardwareChannel(DTS.Common.ISO.HardwareChannel channel, DASHardware hardware) { ChannelNumber = channel.ChannelIdx; Hardware = hardware; _isoChannel = channel; } public int ModuleArrayIndex { get => _isoChannel.ModuleArrayIndex; set => _isoChannel.ModuleArrayIndex = value; } public DASHardware Hardware { get; } public HardwareChannel(int channelNumber, DASHardware hardware, List supportedBridges, List supportedExcitation, int displayOrder, List digitalInputModes, List squibFireModes, List digitalOutputModes, string moduleSerialNumber, int moduleArrayIndex) { ChannelNumber = channelNumber; Hardware = hardware; var bridge = supportedBridges.Sum(b => (int)b); var excitation = supportedExcitation.Sum(e => (int)e); var diModes = digitalInputModes.Sum(di => (int)di); var squibs = squibFireModes.Sum(m => (int)m); var doModes = digitalOutputModes.Sum(dout => (int)dout); var isoHardware = hardware.GetHardware(); _isoChannel = new DTS.Common.ISO.HardwareChannel { ChannelIdx = channelNumber, DASDisplayOrder = displayOrder, SupportedExcitations = excitation, SupportedBridges = bridge, LocalOnly = false, ParentDAS = isoHardware, SupportedDigitalInputModes = diModes, SupportedSquibFireModes = squibs, SupportedDigitalOutputModes = doModes, ModuleSerialNumber = moduleSerialNumber, ModuleArrayIndex = moduleArrayIndex }; isoHardware.SetChannel(_isoChannel); } private bool _bSelected = false; public bool Selected { get => _bSelected; set { SetProperty(ref _bSelected, value, "Selected"); OnPropertyChanged("BackgroundColor"); } } public int ChannelNumber { get; } = 0; public string ChannelNumberText { get { var channelRepresentation = new ChannelRepresentation(this, ChannelNumber + 1); return $"Ch. {channelRepresentation.ChannelNumberString}"; } } public Color BackgroundColor { get { if (Selected) { return Colors.Yellow; } return null == _sensor ? Colors.LightPink : Colors.LightGreen; } } public double ScaleFactorMv => _diagnostics?.ScalefactorMilliVoltsPerADC ?? .2D; private SensorData _sensor = null; public SensorData Sensor { get => _sensor; set { SetProperty(ref _sensor, value, "Sensor"); OnPropertyChanged("BackgroundColor"); OnPropertyChanged("SensorName"); OnPropertyChanged("UnassignVisibility"); } } /// /// essentially the same as SensorName, except some places use SensorName as serialnumber, and this won't be the case with axis /// with multi axis sensors we just show the original sensor serial and Axis x where x is the axis number /// public string SensorNameWithAxis => null == _sensor ? "Empty" : _sensor.GetSerialNumberWithAxis(StringResources.SensorDatabase_SerialNumberWithAxis); public string SensorName => null == _sensor ? "Empty" : _sensor.SerialNumber; public bool IsSupportedBridgeType(SensorConstants.BridgeType bridgeType) { return (GetISOChannel().SupportedBridges & (int)bridgeType) == (int)bridgeType; } private static List _configSquibFireModes = null; public bool IsSupportedSquibFireMode(SquibFireMode mode) { var isHardwareSupported = (GetISOChannel().SupportedSquibFireModes & (int)mode) == (int)mode; if (!isHardwareSupported) return false; if (mode == SquibFireMode.NONE) { return true; } if (null == _configSquibFireModes) { PopulateConfigSquibFireModes(); } return _configSquibFireModes != null && _configSquibFireModes.Contains(mode); } private void PopulateConfigSquibFireModes() { var modes = new List(); var sModes = DataModelSettings.SupportedSquibFireModes.Split(','); foreach (var s in sModes) { if (Enum.TryParse(s, out SquibFireMode mode)) { modes.Add(mode); } } _configSquibFireModes = modes; } public bool IsSupportedExcitation(ExcitationVoltageOptions.ExcitationVoltageOption excitation) { return (GetISOChannel().SupportedExcitations & (int)excitation) == (int)excitation; } public bool IsSupportedDigitalInputMode(DigitalInputModes mode) { return (GetISOChannel().SupportedDigitalInputModes & (int)mode) == (int)mode; } public bool IsSupportedDigitalOutputMode(DigitalOutputModes mode) { return (GetISOChannel().SupportedDigitalOutputModes & (int)mode) == (int)mode; } public bool IsSupportedExcitation(ExcitationVoltageOptions.ExcitationVoltageOption[] excitations) { return Array.Exists(excitations, e => (GetISOChannel().SupportedExcitations & (int)e) == (int)e); } } }