using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; using DTS.Common.Constant; using DTS.Common.Utilities; using DTS.DASLib.Command.SLICE; using DTS.DASLib.Command; using DTS.Common.Enums; using DTS.Common.Interface.DASFactory; using DTS.Common.Utilities.Logging; using DTS.DASLib.Command.SLICE.RealtimeCommands; using DTS.Common.Interface.Connection; using DTS.Common.Interface.DASFactory.Diagnostics; using DTS.Common.Enums.DASFactory; using DTS.Common.ICommunication; namespace DTS.DASLib.Service { public partial class Slice : Communication, IDASCommunication, IConfigurationActions, IDiagnosticsActions, ITriggerCheckActions, IRealTimeActions, IArmActions, IDownloadActions where T : IConnection, new() { protected DateTime GetCurrentSystemTime() { var query = new QueryBaseSystemTime(this); query.SyncExecute(); return query.SystemTime; } protected void DoRTCInUTCCheck() { try { var dt = GetCurrentSystemTime(); if (DateTime.UtcNow.Subtract(dt).TotalMinutes > 1) { ClockSyncInUTC = false; } else { ClockSyncInUTC = true; } } catch( Exception ex) { APILogger.Log(ex); } } private const short ZeroTargetValue = 0; private const ushort TargetToleranceValue = 50; private const float BridgeResistanceMeasurementRangeMV = 250.0f; private const int SmartChargeResistorSettingDisabledValue = 128; private const double FirstInputVoltage = 9.0; private const double SecondInputVoltage = 11.0; protected virtual bool SupportsIEPECalSignal => true; private class DiagnosticsAsyncPacket { public SliceServiceAsyncInfo info { get; set; } public uint DiagnosticsSampleRateHz { get; set; } public float DiagnosticsAAFilterFrequencyHz { get; set; } } private class SquibFireCheckArmPacket : DiagnosticsAsyncPacket { public double MaxDelay { get; set; } public double MaxDuration { get; set; } } private class StatusIndicatorPacket { public SliceServiceAsyncInfo info { get; set; } public DiagnosticsStatusIndicatorState state { get; set; } } #region PrepareForDiagnostics void IDiagnosticsActions.PerformArmChecks(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.PerformArmChecks", new WaitCallback(AsyncPerformArmChecks), info); } /// /// returns true if should check for battery /// right now this is just a check against SLICE6 /// since SLICE6 does not have batteries, /// /// private bool ShouldCheckForBattery() { return this is EthernetSlice6 || this is EthernetSlice6AirBridge; } private void AsyncPerformArmChecks(object o) { if (!(o is SliceServiceAsyncInfo info)) { return; } //14684 DataPRO crashed while running arm checklist in check trigger //this is operating on it's own thread, so don't throw any exception, return an error instead try { var dasResults = new ArmCheckResults(); var sensorIds = new Dictionary(); if (null != ArmCheckActions) { if (ArmCheckActions.PerformBatteryVoltageCheck) { var checkBatteryVoltage = false; if (ShouldCheckForBatteryIds()) { var batteryid = new QueryOneWireID(this, QueryOneWireID.Default_IO_Timeout); batteryid.StackChannel = 0xFF; batteryid.SyncExecute(); if (batteryid.IDs.Count > 0) { if (!string.Equals(new EID(HexEncoding.ToString(batteryid.IDs[0])).ID, EIDReader.BLANK_ID)) { checkBatteryVoltage = true; } } } else { //tsrair has a battery, but no one-wire id. otherwise skip if (this is EthernetTsrAir) { checkBatteryVoltage = true; } } if (checkBatteryVoltage) { GetBaseInputs(); if (BaseInput.BatteryMilliVolts > 3000D) { dasResults.BatteryVoltage = new double?[1]; dasResults.BatteryVoltage[0] = BaseInput.BatteryMilliVolts / 1000D; } } info.Progress(25); } if (ArmCheckActions.PerformInputVoltageCheck) { GetBaseInputs(); dasResults.InputVoltage = BaseInput.InputMilliVolts / 1000D; } if (ArmCheckActions.PerformEventLineCheck) { ((ITriggerCheckActions)this).DoTriggerCheckSync(); } info.Progress(50); if (ArmCheckActions.PerformSensorIdCheck) { try { PerformSensorIdCheck(ref sensorIds); } catch (Exception ex) { APILogger.Log(ex); info.Error(ex.Message); return; } } if (ArmCheckActions.PerformTiltSensorCheck) { GetTiltResults(dasResults); } if (ArmCheckActions.PerformClockSyncCheck && IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.QueryClockSyncStatus)) { //SLICE 6 AIR var query = new QueryClockSyncStatus(this); try { query.SyncExecute(); dasResults.InputClockLocks = query.InputSyncStatus; } catch (Exception ex) { info.Error(ex.Message, ex); } } else if (ArmCheckActions.PerformClockSyncCheck && IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.PTPSyncStatus)) { //SLICE 6, S6DB, etc. var query = new Ptp1588GetSyncStatus(this); try { query.SyncExecute(); var syncStatus = new Dictionary(); List sources = Enum.GetValues(typeof(DTS.Common.InputClockSource)).Cast().ToList(); for (int i = 1; i < sources.Count; i++) { // init all sources not synced syncStatus.Add(sources[i], false); } syncStatus[DTS.Common.InputClockSource.PTP] = (Ptp1588Commands.PtpSyncStatus.Synced == query.SyncStatus); dasResults.InputClockLocks = syncStatus; } catch (Exception ex) { info.Error(ex.Message, ex); } } } info.Progress(75); dasResults.SensorIds = sensorIds; if (IsTOM() && ArmCheckActions.PerformSquibResistanceCheck) { dasResults.SquibResistances = new Dictionary(); var measureSquibs = new MeasureSquibChannelResistances(this); measureSquibs.SyncExecute(); for (int i = 0; i < 4 && i < measureSquibs.MeasuredResistanceOhms.Length; i++) { double d = measureSquibs.MeasuredResistanceOhms[i]; dasResults.SquibResistances.Add(i * 2, d); dasResults.SquibResistances.Add(1 + (i * 2), d); } } else { dasResults.SquibResistances = null; //squibResistances; } info.Progress(100); ArmCheckResults = dasResults; info.Success(); } catch (Exception ex) { info.Error($"Arm checklist failed {SerialNumber} - {ex.Message}"); } } protected virtual void GetTiltResults(ArmCheckResults dasResults) { return; } void IDiagnosticsActions.SaveTiltSensorDataPre(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.SaveTiltSensorDataPre", new WaitCallback(AsyncSaveTiltSensorDataPre), info); } private void AsyncSaveTiltSensorDataPre(object o) { if (!(o is SliceServiceAsyncInfo info)) { return; } if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.InSliceTiltSensorADCPre)) { var setTS = new SetArmAttribute(this); setTS.SetValue(AttributeTypes.ArmAndEventAttributes.tiltSensorPreEventADC, ArmCheckResults.TiltSensorDataPre, true); setTS.SyncExecute(); } info.Success(); } void IDiagnosticsActions.SaveTemperaturesPre(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.SaveTemperaturesPre", new WaitCallback(AsyncSaveTemperaturesPre), info); } private void AsyncSaveTemperaturesPre(object o) { var info = (SliceServiceAsyncInfo)o; info.Success(); } protected virtual void PerformSensorIdCheck(ref Dictionary sensorIds) { PerformSensorIdCheck_SingleChannelMethod(ref sensorIds); } protected void PerformSensorIdCheck_SingleChannelMethod(ref Dictionary sensorIds) { foreach (var module in ConfigData.Modules) { foreach (var channel in module.Channels) { var dasChannelNumber = DASInfo.MapModuleArrayIndexAndChannelNum2DASChannel(module.ModuleArrayIndex, channel.ModuleChannelNumber); channel.IDs = EIDReader.RetriveEIDs(this, dasChannelNumber, channel.IdType); if (null != channel.IDs) { if (IsTOM()) { sensorIds[channel.Number * 2] = new[] { channel.IDs[0].ID }; sensorIds[1 + (channel.Number * 2)] = new[] { channel.IDs[0].ID }; } else { var ids = new List(); foreach (var id in channel.IDs) { ids.Add(id.ID); } sensorIds[channel.Number] = ids.ToArray(); } } } } } /// /// Perform diagnostics based on the property ChannelDiagnostics and stuff the /// result in ChannelDiagnosticsResults /// /// the diagnostics sample rate /// the AA filter frequency /// The function to call with information /// Whatever you want to pass along void IDiagnosticsActions.PrepareForDiagnostics(UInt32 DiagnosticsSampleRateHz, float DiagnosticsAAFilterFrequencyHz, DTS.DASLib.Service.PrePostResults WhichResult, ServiceCallback callback, object userData) { var packet = new DiagnosticsAsyncPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); packet.DiagnosticsSampleRateHz = DiagnosticsSampleRateHz; packet.DiagnosticsAAFilterFrequencyHz = DiagnosticsAAFilterFrequencyHz; LaunchAsyncWorker("Slice.PrepareForDiagnostics", new WaitCallback(AsyncPrepareForDiagnostics), packet); } /// /// the working guts of PrepareForDiagnostics /// /// our async data private void AsyncPrepareForDiagnostics(object asyncInfo) { var packet = asyncInfo as DiagnosticsAsyncPacket; try { // set diagnostics AAfilter and samplerate var setSR = new SetArmAttribute(this); setSR.SetValue(AttributeTypes.ArmAndEventAttributes.DiagnosticsSampleRateHz, packet.DiagnosticsSampleRateHz, true); setSR.SyncExecute(); // set diagnostics AAfilter and samplerate var setFF = new SetArmAttribute(this); setFF.SetValue(AttributeTypes.ArmAndEventAttributes.DiagnosticsAAFilterFrequencyHz, packet.DiagnosticsAAFilterFrequencyHz, true); setFF.SyncExecute(); // prepare for diagnostics var prep = new PrepareForDiagnostics(this); prep.SyncExecute(); // we're done packet.info.Success(); } catch (CanceledException) { // like most tv shows, we have been canceled packet.info.Cancel(); } catch (Exception ex) { packet.info.Error(ex.Message, ex); } } #endregion #region PrepareForBridgeResistanceMeasurement /// /// Perform diagnostics based on the property ChannelDiagnostics and stuff the /// result in ChannelDiagnosticsResults /// /// the sample rate for diagnostics /// the AA filter frequency /// The function to call with information /// Whatever you want to pass along void IDiagnosticsActions.PrepareForBridgeResistanceMeasurement(UInt32 DiagnosticsSampleRateHz, float DiagnosticsAAFilterFrequencyHz, ServiceCallback callback, object userData) { var packet = new DiagnosticsAsyncPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); packet.DiagnosticsSampleRateHz = DiagnosticsSampleRateHz; packet.DiagnosticsAAFilterFrequencyHz = DiagnosticsAAFilterFrequencyHz; LaunchAsyncWorker("Slice.PrepareForBridgeResistanceMeasurement", new WaitCallback(AsyncPrepareForBridgeResistanceMeasurement), packet); } /// /// the working guts of PrepareForBridgeResistanceMeasurement /// /// our async data private void AsyncPrepareForBridgeResistanceMeasurement(object asyncInfo) { var packet = asyncInfo as DiagnosticsAsyncPacket; try { // Turn on excitations explicitly so that sensor will warm up as much as possible // set diagnostics AAfilter and samplerate // Should only be one channel, but just in case for (uint CurrentChannel = 0; CurrentChannel < ChannelDiagnostics.Length; CurrentChannel++) { var ExcitationSwitch = new SetSwitchImmediate(this); ExcitationSwitch.Setting = 1; ExcitationSwitch.DeviceID = DASInfo.MapDASChannelNumber2ModuleDeviceID(ChannelDiagnostics[0].DASChannelNumber); var module = DASInfo.Modules[ExcitationSwitch.DeviceID - 1]; if (module.TypeOfModule == DFConstantsAndEnums.ModuleType.SLICEIEPE) { ExcitationSwitch.Switch = (byte)Switches.EIPESwitches.IS_EnableAnalogPowerSupply; ExcitationSwitch.SwitchText = Switches.EIPESwitches.IS_EnableAnalogPowerSupply.ToString(); } else { ExcitationSwitch.Switch = (byte)Switches.BridgeSwitches.EnableAnalogPowerSupply; ExcitationSwitch.SwitchText = Switches.BridgeSwitches.EnableAnalogPowerSupply.ToString(); } ExcitationSwitch.SyncExecute(); if (module.TypeOfModule == DFConstantsAndEnums.ModuleType.SLICEIEPE) { ExcitationSwitch.Switch = (byte)Switches.EIPESwitches.IS_Enable24Volt; ExcitationSwitch.SwitchText = Switches.EIPESwitches.IS_Enable24Volt.ToString(); } else { ExcitationSwitch.Switch = (byte)Switches.BridgeSwitches.EnableAmps; ExcitationSwitch.SwitchText = Switches.BridgeSwitches.EnableAmps.ToString(); } ExcitationSwitch.SyncExecute(); if (module.TypeOfModule != DFConstantsAndEnums.ModuleType.SLICEIEPE) { ExcitationSwitch.Switch = (byte)Switches.BridgeSwitches.EnableExcitation; } ExcitationSwitch.SyncExecute(); } // set calibration AAfilter and samplerate var setSR = new SetArmAttribute(this); setSR.SetValue(AttributeTypes.ArmAndEventAttributes.DiagnosticsSampleRateHz, packet.DiagnosticsSampleRateHz, true); setSR.SyncExecute(); // set diagnostics AAfilter and samplerate var setFF = new SetArmAttribute(this); setFF.SetValue(AttributeTypes.ArmAndEventAttributes.DiagnosticsAAFilterFrequencyHz, packet.DiagnosticsAAFilterFrequencyHz, true); setFF.SyncExecute(); // Dummy set the requested range--we are relying on this being overriden later when the // software sets the configuration on the module. The channel may not have a valid requested // range stored on it, so we need to at least dummy set it here so we can do proper data // collection to compute the bridge resistance. We use the target range set in // BridgeResistanceMeasurementRangeMV, which should give us about a gain of 10. // note - we also reset the configure has been run as we are relying on re-configuring again. ConfigureHasBeenRun = false; var getRange = new QueryArmAttribute(this); getRange.Key = AttributeTypes.ArmAndEventAttributes.StackChannelRangesMillivolts; getRange.SyncExecute(); var rangeArray = getRange.Value as float[]; for (var i = 0; i < rangeArray.Length; i++) { rangeArray[i] = BridgeResistanceMeasurementRangeMV; } var config = new ConfigAttributes(this); try { config.AAFilter = ConfigData.Modules[0].AAFilterRateHz; } catch (Exception ex) { APILogger.Log("Failed to set AAF min", ex); } var setRange = new SetArmAttribute(this); setRange.SetValue(AttributeTypes.ArmAndEventAttributes.StackChannelRangesMillivolts, rangeArray, true); setRange.SyncExecute(); // prepare for diagnostics var prep = new PrepareForDiagnostics(this); prep.SyncExecute(); // Since we're not allowing the user to let the sensor warm up, we do it here Thread.Sleep(100); // we're done packet.info.Success(); } catch (CanceledException) { // like most tv shows, we have been canceled packet.info.Cancel(); } catch (Exception ex) { packet.info.Error(ex.Message, ex); } } #endregion #region GetBridgeMeasurement void IDiagnosticsActions.GetBridgeMeasurement(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.GetBridgeMeasurement", new WaitCallback(AsyncDiagnosAndGetResults), info); } #endregion #region MeasureTransferSpeed void IDiagnosticsActions.MeasureTransferSpeed(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.MeasureTransferSpeed", new WaitCallback(AsyncMeasureTransferSpeedResults), info); } #endregion #region CalibrateAndGetResults public void SetStatusIndicator(DiagnosticsStatusIndicatorState state, ServiceCallback callback, object userData) { var packet = new StatusIndicatorPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("SLICE.SetStatusIndicator", new WaitCallback(AsyncSetStatusIndicator), packet); } private void AsyncSetStatusIndicator(object asyncInfo) { var packet = asyncInfo as StatusIndicatorPacket; packet.info.Success(); } public void TurnOffT0Lights(ServiceCallback callback, object userData) { var packet = new SliceServiceAsyncInfo(callback, userData); packet.Success(); } /// /// Perform diagnostics based on the property ChannelDiagnostics and stuff the /// result in ChannelDiagnosticsResults /// /// The function to call with information /// Whatever you want to pass along void IDiagnosticsActions.DiagnosAndGetResults(int EventNumber, PrePostResults WhichResult, ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); info.PreOrPost = WhichResult; LaunchAsyncWorker("Slice.DiagnosAndGetResults", new WaitCallback(AsyncDiagnosAndGetResults), info); } protected void ReconfigureAccordingToConfig() { try { var cmd = new SetStackChannelTypeConfiguration(this); var channels = new List(); foreach (var iDASModule in ConfigData.Modules) { var module = (DASModule)iDASModule; var moduleType = module.OwningDAS.DASInfo.Modules[module.ModuleArrayIndex].TypeOfModule; foreach (var c in module.Channels) { if (c is AnalogInputDASChannel) { if ((c as AnalogInputDASChannel).IEPEChannel) { switch (moduleType) { case DFConstantsAndEnums.ModuleType.SliceBridge2Low: case DFConstantsAndEnums.ModuleType.SliceIEPE2Low: channels.Add(SetStackChannelTypeConfiguration.ChannelTypes.FORCE_IEPE_LOW); break; default: channels.Add(SetStackChannelTypeConfiguration.ChannelTypes.FORCE_IEPE); break; } } else { switch (moduleType) { case DFConstantsAndEnums.ModuleType.SliceBridge2Low: case DFConstantsAndEnums.ModuleType.SliceIEPE2Low: channels.Add(SetStackChannelTypeConfiguration.ChannelTypes.FORCE_BRIDGE_LOW); break; default: channels.Add(SetStackChannelTypeConfiguration.ChannelTypes.FORCE_BRIDGE); break; } } } else { channels.Add(SetStackChannelTypeConfiguration.ChannelTypes.AUTO_DETECT); } } } cmd.SetValue(channels.ToArray()); } catch (Exception ex) { APILogger.Log(ex); } } public bool IsTOM() { return SerialNumber.StartsWith("SPT") || SerialNumber.StartsWith("SLT"); } //FB 6416 Measure the transfer speed based on writing /reading a particular data size to/from DAS private float MeasureTransferSpeed(int bufferSize, ICommunication comm) { Stopwatch stopwatch = new Stopwatch(); var sfd = new SetFileData(comm, 600000); if (bufferSize > sfd.MaximumFileStreamBytes) { throw new ArgumentOutOfRangeException($"buffer Size: {bufferSize} > {sfd.MaximumFileStreamBytes}"); } var bytes = new byte[bufferSize]; for (int i = 0; i < bufferSize; i++) { bytes[i] = 0x2A; } sfd.StartByteCount = 0; sfd.FileID = (ushort)ConfigAttributes.FileStore.SpeedTest; sfd.Data = bytes; stopwatch.Reset(); stopwatch.Start(); sfd.SyncExecute(); stopwatch.Stop(); var gfd = new QueryFileData(comm, 600000); gfd.FileID = (ushort)ConfigAttributes.FileStore.SpeedTest; gfd.StartByteCount = 0; gfd.EndByteCount = (uint)bufferSize - 1; stopwatch.Start(); gfd.SyncExecute(); stopwatch.Stop(); float speed = (float)gfd.Data.Length * 8 / stopwatch.ElapsedMilliseconds; return speed; } //FB 6416 Calculate the average transfer speed based on 3 tries private void AsyncMeasureTransferSpeedResults(object asyncInfo) { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } try { float totalSpeed = 0; int tries = 3; for (int i = 0; i < tries; i++) { totalSpeed += MeasureTransferSpeed(400, this); } float averageSpeed = 0; averageSpeed = totalSpeed / tries; OptimizationValues = new Classes.Diagnostics.OptimizationValues { TransferSpeed = averageSpeed }; info.Success(); } catch (Exception ex) { info.Error("Measuring transfer speed threw exception", ex); } } private void AsyncDiagnosAndGetResultsTOM(object asyncInfo) { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } try { // prepare the result array var channelToResult = new Dictionary(); if (ChannelDiagnostics != null && ChannelDiagnostics.Length > 0) { for (int idx = 0; idx < ChannelDiagnostics.Length; idx++) { var result = Array.Find(ChannelDiagnosticsResults, ch => ch.DASChannelNumber == ChannelDiagnostics[idx].DASChannelNumber); if (null != result) { channelToResult[ChannelDiagnostics[idx].DASChannelNumber] = (DiagnosticsResult)result; } } } var ProgressSteps = 12D; var ProgressCounter = 1; var mtbdc = new MeasureTomBaseDiagnosticChannel(this); mtbdc.Channel = MeasureTomBaseDiagnosticChannel.TomBaseDiagnosticChannelList.Power17V; mtbdc.SyncExecute(); var queryScaleFactors = new QueryArmAttribute(this); queryScaleFactors.Key = AttributeTypes.ArmAndEventAttributes.StackChannelScaleFactorsMillivoltsPerADC; queryScaleFactors.SyncExecute(); var scaleFactors = (float[])queryScaleFactors.Value; var mscr = new MeasureSquibChannelResistances(this); mscr.SyncExecute(); var queryFinalOffsetADC = new QueryArmAttribute(this); queryFinalOffsetADC.Key = AttributeTypes.ArmAndEventAttributes.StackChannelFinalOffsetCounts; queryFinalOffsetADC.SyncExecute(); var finalOffsetADC = queryFinalOffsetADC.Value as short[]; for (var i = 0; i < 8; i += 2) { var squibVoltage = ConfigData.Modules[0].Channels[i] as OutputSquibChannel; var squibCurrent = ConfigData.Modules[0].Channels[i + 1] as OutputSquibChannel; if (squibVoltage.ConfigurationMode == DFConstantsAndEnums.ConfigMode.Normal) { if (channelToResult.ContainsKey(i)) { channelToResult[i].NoisePercentFullScale = 0; channelToResult[i].FinalOffsetADC = 0; channelToResult[i].AutoZeroPercentDeviation = null; channelToResult[i].ScalefactorMilliVoltsPerADC = scaleFactors[i]; channelToResult[i].MeasuredExcitationMilliVolts = mtbdc.Measurement * 1000D; //14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics //SLICE excitation is not absoluted, but carrying through the values here for completeness channelToResult[i].NegativeExcitation = mtbdc.Measurement < 0; squibVoltage.ScaleFactorMv = scaleFactors[i]; squibVoltage.PreTestDataZeroLevelADC = finalOffsetADC[i]; channelToResult[i].BridgeResistance = mscr.MeasuredResistanceOhms[i / 2]; } if (channelToResult.ContainsKey(i + 1)) { channelToResult[i + 1].NoisePercentFullScale = 0; channelToResult[i + 1].AutoZeroPercentDeviation = null; channelToResult[i + 1].FinalOffsetADC = 0; channelToResult[i + 1].ScalefactorMilliVoltsPerADC = scaleFactors[i + 1]; channelToResult[i + 1].BridgeResistance = mscr.MeasuredResistanceOhms[i / 2]; squibCurrent.ScaleFactorMv = scaleFactors[i + 1]; squibCurrent.PreTestDataZeroLevelADC = finalOffsetADC[i + 1]; ; } } } // measure inputs GetBaseInputs(); info.Progress((int)(ProgressCounter++ / ProgressSteps * 100D)); info.Success(); } catch (CanceledException) { // like most tv shows, we have been canceled info.Cancel(); } catch (Exception ex) { info.Error(ex.Message, ex); } } /// /// the working guts of DiagnosAndGetResults /// /// our async data protected virtual void AsyncDiagnosAndGetResults(object asyncInfo) { if (IsTOM()) { AsyncDiagnosAndGetResultsTOM(asyncInfo); } else { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } try { //this was part of a resharper irmprovement //I'm choosing to assert here as I'm not sure what the //behavior should be if info is null, and this will preserve //the existing behavior, but a little cleaner Debug.Assert(info != null, "info != null"); var oldResults = new Dictionary(); if (null != ChannelDiagnosticsResults && ChannelDiagnosticsResults.Length > 0) { foreach (var r in ChannelDiagnosticsResults) { oldResults[r.DASChannelNumber] = r; } } // prepare the result array IDiagnosticResult[] results; if (ChannelDiagnostics != null && ChannelDiagnostics.Length > 0) { results = new DiagnosticsResult[ChannelDiagnostics.Length]; for (var idx = 0; idx < ChannelDiagnostics.Length; idx++) { results[idx] = new DiagnosticsResult { DASChannelNumber = ChannelDiagnostics[idx].DASChannelNumber }; if (oldResults.ContainsKey(results[idx].DASChannelNumber)) { oldResults.Remove(results[idx].DASChannelNumber);//we'll have a new value, don't hold to old one } results[idx].EventNumber = info.PreOrPost == PrePostResults.PreEventDiagnosticsResult ? DFConstantsAndEnums.EVENT_NUMBER_PRETEST_DIAG : DFConstantsAndEnums.EVENT_NUMBER_POSTTEST_DIAG; } } else { // this case will only generate the base input values results = null; } const double progressSteps = 14.0; var progressCounter = 1; if (results != null) { // Need to map IEPE channel AC coupling from Configuration so we can restore later. #region MAP_IEPE_CH_COUPLING var numChannels = DASInfo.Modules.Sum(mod => mod.NumberOfChannels); var IsACCoupledArray = new bool[numChannels]; for (int moduleIdx = 0; moduleIdx < ConfigData.Modules.Length; moduleIdx++) { var module = ConfigData.Modules[moduleIdx]; for (var channelIdx = 0; channelIdx < module.Channels.Length; channelIdx++) { var channel = module.Channels[channelIdx]; var dasChannelNumber = DASInfo.MapModuleArrayIndexAndChannelNum2DASChannel(moduleIdx, channelIdx); var analog = channel as AnalogInputDASChannel; if (analog?.IEPEChannel ?? false) { IsACCoupledArray[dasChannelNumber] = analog.CouplingMode == Common.Enums.Sensors.SensorConstants.CouplingModes.AC; } else { IsACCoupledArray[dasChannelNumber] = false; } } } var acCouplingExists = Array.Exists(IsACCoupledArray, x => x == true); var config = GetConfigAttributes(this); TurnOffACCoupling(acCouplingExists, config, numChannels); #endregion MAP_IEPE_CH_COUPLING // first get the scale factors GetScaleFactorMVForChannel(info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); QueryGain(info, ref results); // query factory excitation FactoryExcitation(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure excitation MeasureExcitation(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure offset MeasureOffset(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure internal offset (signal chain) MeasureInternalOffset(ChannelDiagnostics, info, ref results, false); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); var dasChannelNumberToZeromVinAdc = new Dictionary(); foreach (var res in results) { dasChannelNumberToZeromVinAdc[res.DASChannelNumber] = res.ZeroMVInADC; } foreach (var module in ConfigData.Modules) { foreach (var ch in module.Channels) { if (!(ch is AnalogInputDASChannel aic)) continue; if (dasChannelNumberToZeromVinAdc.ContainsKey(aic.Number)) { aic.ZeromVInADC = dasChannelNumberToZeromVinAdc[aic.Number]; } } } CheckDigitalValues(ChannelDiagnostics, info, ref results); //FB 29510 if (IsTSRAIR()) { MarkOffsetForRemoval(); } RestoreIEPE(acCouplingExists, config, IsACCoupledArray, info); // remove offset RemoveOffset(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure final offset MeasureFinalOffset(ChannelDiagnostics, info, ref results); if (this is EthernetTsrAir) { foreach (var module in ConfigData.Modules) { if (module.ModuleType() != DFConstantsAndEnums.ModuleType.EmbeddedAtmospheric) continue; //don't record offsets for the non-accel foreach (var ch in module.Channels) { if (!(ch is AnalogInputDASChannel aic)) continue; results.First(res => res.DASChannelNumber == aic.Number).FinalOffsetADC = 0; } } } info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure final internal offset (signal chain) again since RemoveOffset was called MeasureInternalOffset(ChannelDiagnostics, info, ref results, true); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure noise MeasureNoise(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // perform shunt check PerformShuntCheck(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); // measure the bridge resistance PerformBridgeResistanceMeasurement(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100.0)); PerformVoltageInsertionCheck(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100D)); // perform cal signal check PerformCalSignalCheck(ChannelDiagnostics, info, ref results); info.Progress((int)(progressCounter++ / progressSteps * 100D)); } var newResults = new List(oldResults.Values.ToArray()); // measure inputs GetBaseInputs(); if (results != null) { info.Progress((int)(progressCounter / progressSteps * 100.0)); newResults.AddRange(results); } SetChannelDiagnosticsResults(newResults.ToArray(), true); info.Success(); } catch (CanceledException) { // like most tv shows, we have been canceled info.Cancel(); } catch (Exception ex) { if (ex.Data.Contains("Status")) { if (ex.Data["Status"] is DFConstantsAndEnums.CommandStatus cs) { if (cs != DFConstantsAndEnums.CommandStatus.StatusSetupShuntDACOutputExceeded) { if (cs == DFConstantsAndEnums.CommandStatus.StatusSetupCALDACOutputExceeded) { info.Error( "Cal DAC output exceeded while running diagnostics on channel.\n Calibration stopped."); return; } } else { info.Error( "Shunt DAC output exceeded while running diagnostics on channel.\n Calibration stopped."); return; } } } info.Error(ex.Message, ex); } } } /// /// turns off AC coupling on ALL channels this was original done for /// http://manuscript.dts.local/f/cases/16524/diagnostic-result-should-include-the-resting-voltage-of-an-IEPE-sensor /// I split it off here for beter readability /// /// /// /// private void TurnOffACCoupling(bool acCouplingExists, ConfigAttributes config, long numChannels) { if (acCouplingExists) { config.ConfigureCoupling(new bool[numChannels]); // Set all to false // prepare for diagnostics var prep = new PrepareForDiagnostics(this); prep.SyncExecute(); // Since we're not allowing the user to let the sensor warm up, we do it here Thread.Sleep(100); } } private void MarkOffsetForRemoval() { //FB 29510 Set remove offset to true only for High G channels index 3.4.5 foreach (var channelDiagnostic in ChannelDiagnostics) { channelDiagnostic.RemoveOffset = channelDiagnostic.DASChannelNumber == DFConstantsAndEnums.High_g_Linear_1_Index || channelDiagnostic.DASChannelNumber == DFConstantsAndEnums.High_g_Linear_2_Index || channelDiagnostic.DASChannelNumber == DFConstantsAndEnums.High_g_Linear_3_Index; } } /// /// restores AC coupling for channels that have it selected; /// http://manuscript.dts.local/f/cases/16524/diagnostic-result-should-include-the-resting-voltage-of-an-IEPE-sensor /// /// /// /// private void RestoreIEPE(bool acCouplingExists, ConfigAttributes config, bool[] IsACCoupledArray, SliceServiceAsyncInfo info) { // Need to restore AC couping if there were any channels configured this way // Only Restore coupling mode if we adjusted in the first place if (acCouplingExists) { config.ConfigureCoupling(IsACCoupledArray); // Restore to channel config settings // prepare for diagnostics var prep = new PrepareForDiagnostics(this); prep.SyncExecute(); var warmup = RunTestVariables.IEPEExcitationWamupTimeMS; for (var i = 0; i < warmup; i += 200) { var percentDone = 100 * i / warmup; APILogger.Log($"IEPE warmup {percentDone:00}%"); Thread.Sleep(200); } } } /// /// Combine nominal gain, gain calibrated adjustment and millivolts per count /// to produce a scale factor to convert counts to millivolts /// /// The channel to query /// object that /// will have the scale factor mV per ADC assigned if all goes right /// /// scale factor to convert counts to millivolts private void GetScaleFactorMVForChannel(SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { var query = new QueryArmAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelScaleFactorsMillivoltsPerADC; query.SyncExecute(); if (query.DataType != AttributeTypes.AttributeDataTypes.Float32Star) throw new Exception("DiagnosticsService.GetScaleFactorMVForChannel: Found type " + query.DataType); var resultArray = (float[])query.Value; foreach (var diagResult in results) { //TODO: REMOVE THIS HACK //when scale factor works //or we've cut it from tsr-air if (this is WinUSBTsrAir || this is EthernetTsrAir) { diagResult.ScalefactorMilliVoltsPerADC = 0; diagResult.ScalefactorEngineeringUnitsPerADC = resultArray[diagResult.DASChannelNumber]; } else { diagResult.ScalefactorMilliVoltsPerADC = resultArray[diagResult.DASChannelNumber]; diagResult.ScalefactorEngineeringUnitsPerADC = 0; } info.NewData(new ServiceCallbackData.DiagnosticNewData() { DasChannelNumber = diagResult.DASChannelNumber, Action = ServiceCallbackData.DiagnosticNewData.Actions.ScaleFactorMv, Result = diagResult.ScalefactorMilliVoltsPerADC }); } } /// /// Query the channels' gain codes, convert to gain values, and write to the log /// /// /// private void QueryGain(SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { const string StackChannelGainValues = "Stack Channel Gain Values"; var query = new QueryArmAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelGainCodes; query.SyncExecute(); var resultCodesArray = (ushort[])query.Value; var resultValuesArray = new List(); foreach (var diagResult in results) { diagResult.QueriedGain = GainCodeToGainValue(resultCodesArray[diagResult.DASChannelNumber]); resultValuesArray.Add(diagResult.QueriedGain); } var sb = new StringBuilder(StackChannelGainValues); sb.Append(" [" + SerialNumber + "] "); sb.Append(ArrayToString.ArrayObjectToString(resultValuesArray.ToArray())); APILogger.LogString(sb.ToString()); } /// /// gets the expected excitation in mV for a given channel /// returns 0 if excitation could not be retrieved, otherwise excitation in mV /// /// /// /// protected virtual double GetExpectedExcitationMV(int moduleIndex, int channelOnModule) { if (ConfigData?.Modules == null || ConfigData.Modules.Length <= moduleIndex) { APILogger.Log("unable to get excitation, no ConfigData to base excitation on"); return 0D; } var aic = ConfigData.Modules[moduleIndex].Channels[channelOnModule] as AnalogInputDASChannel; if (null == aic) { //only have excitation to consider on analog channels APILogger.Log("unable to get excitation, channel has no excitation (is not analog)"); return 0D; } var excitation = Common.DAS.Concepts.Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(aic.Excitation) * 1000D; try { var qsa = new QuerySystemAttribute_Bridge(this); switch (channelOnModule) { case 0: qsa.Key = AttributeTypes.SystemAttributes_Bridge.FactoryCalibratedExcitationAVolts; break; case 1: qsa.Key = AttributeTypes.SystemAttributes_Bridge.FactoryCalibratedExcitationBVolts; break; default: qsa.Key = AttributeTypes.SystemAttributes_Bridge.FactoryCalibratedExcitationCVolts; break; } //note device 0 is the base, the first module starts at 1, so we have to start at an offset of 1 qsa.DeviceID = Convert.ToByte(1 + moduleIndex); qsa.SyncExecute(); var bridgeExcitation = Convert.ToDouble(qsa.Value) * 1000D;//convert from V to mV var delta = Math.Abs(excitation - bridgeExcitation); if (delta < 500) { excitation = bridgeExcitation; } } catch (Exception ex) { APILogger.Log(ex); } return excitation; } /// /// Query the factory excitation for all channels. /// /// An array of actions. One per channel /// Our async data /// An array of results. One entry per channel private void FactoryExcitation(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { if (!SupportsDiagnosticsFactoryExcitation) { foreach (var t in results) { t.ExpectedExcitationMilliVolts = 0.0; } return; } // pickup factory excitation var query = new QueryArmAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.DiagnosticsFactoryExcitation; try { query.SyncExecute(); if (!(query.Value is float[] factoryExcitations)) { throw new ApplicationException("no factory excitations"); } else { //when we are only doing a single channel diagnostics, this code will cause a problem, we only have // one result, not the full set // 6/8/2010 - dtm var bNeedToSet = false; for (var idx = 0; idx < results.Length; idx++) { if (idx >= ChannelActions.Length) { throw new ApplicationException("no corresponding channel action for this result"); } int actualIdx = ChannelActions[idx].DASChannelNumber; if (actualIdx < 0 || actualIdx >= factoryExcitations.Length) { throw new ApplicationException("result doesn't match a channel action or a factory excitation index"); } //https://support.dtsweb.com/hc/en-us/community/posts/115000905934-SLICE6-Fails-Diagnostics-across-all-6-channels-Excitation-Tolerances-are-Zero //http://fogbugz/fogbugz/default.asp?10853 //we look for a clearly bogus value, if we find it we try to recover //recovered value is either the bridge factory excitation or the nominal value given the channel setting if (factoryExcitations[actualIdx] < 1D) { var moduleIdx = DASInfo.MapDASChannelNumber2ModuleArrayIndex(ChannelActions[idx].DASChannelNumber); var channelInModule = DASInfo.MapDASChannelNumber2ModuleChannelNumber(ChannelActions[idx].DASChannelNumber); var qsa = new QuerySystemAttribute_Bridge(this); qsa.DeviceID = Convert.ToByte(1 + moduleIdx); var expectedValue = GetExpectedExcitationMV(moduleIdx, channelInModule); //if we have 0 or less, it's not a valid value, we don't want to change the value in the existing arm attribute... APILogger.Log($"unexpected factory excitation:{factoryExcitations[actualIdx]}, should have been {expectedValue}"); if (expectedValue > 0D) { factoryExcitations[actualIdx] = Convert.ToSingle(expectedValue); bNeedToSet = true; } results[idx].ExpectedExcitationMilliVolts = expectedValue; } else { results[idx].ExpectedExcitationMilliVolts = factoryExcitations[actualIdx]; } info.NewData(new ServiceCallbackData.DiagnosticNewData() { Action = ServiceCallbackData.DiagnosticNewData.Actions.FactoryExcitation, Result = factoryExcitations[actualIdx], DasChannelNumber = ChannelActions[idx].DASChannelNumber }); } if (!bNeedToSet) return; //set it for future posterity and retrieval //http://fogbugz/fogbugz/default.asp?10853 try { if (SupportsDiagnosticsFactoryExcitation) { var set = new SetArmAttribute(this); set.SetValue(AttributeTypes.ArmAndEventAttributes.DiagnosticsFactoryExcitation, factoryExcitations.ToArray(), AttributeTypes.AttributeDataTypes.Float32Star, true); set.SyncExecute(); } else { foreach (var t in results) { t.ExpectedExcitationMilliVolts = 0.0; } } } catch (Exception ex) { APILogger.Log("failed to set factory excitations: ", ex); } } } catch (Exception ex) { APILogger.Log(ex); foreach (var t in results) { t.ExpectedExcitationMilliVolts = 0.0; } } } /// /// Measure the excitation on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel private void MeasureExcitation(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.MeasureExcitation); if (NumToMeasure == 0) return; var measureExcitation = new MeasureBridgeDiagnosticChannel(this); var measure24V = new MeasureIEPEDiagnosticChannel(this); for (var idx = 0; idx < ChannelActions.Length; idx++) { var module = Convert.ToInt32(DASInfo.MapDASChannelNumber2ModuleArrayIndex(ChannelActions[idx].DASChannelNumber)); if (DASInfo.Modules[module].TypeOfModule == DFConstantsAndEnums.ModuleType.SLICEIEPE) { measure24V.DeviceID = DASInfo.MapDASChannelNumber2ModuleDeviceID(ChannelActions[idx].DASChannelNumber); measure24V.DiagnosticField = MeasureIEPEDiagnosticChannel.IEPEDiagnosticChannelList.IM_24VPowerSupply; measure24V.SyncExecute(); results[idx].MeasuredExcitationMilliVolts = measure24V.Measurement * 1000D; results[idx].ExpectedExcitationMilliVolts = 24000D; } else { if (ChannelActions[idx].MeasureExcitation) { switch (DASInfo.MapDASChannelNumber2ModuleChannelNumber(ChannelActions[idx].DASChannelNumber)) { case 0: measureExcitation.Channel = MeasureBridgeDiagnosticChannel.BridgeDiagnosticChannelList.ExcitationA; break; case 1: measureExcitation.Channel = MeasureBridgeDiagnosticChannel.BridgeDiagnosticChannelList.ExcitationB; break; case 2: measureExcitation.Channel = MeasureBridgeDiagnosticChannel.BridgeDiagnosticChannelList.ExcitationC; break; default: info.Error("MeasureExcitation: Unknown channel number: " + DASInfo.MapDASChannelNumber2ModuleChannelNumber(ChannelActions[idx].DASChannelNumber).ToString()); break; } for (var retryCount = 0; retryCount < 2; retryCount++) { measureExcitation.DeviceID = DASInfo.MapDASChannelNumber2ModuleDeviceID(ChannelActions[idx].DASChannelNumber); measureExcitation.SyncExecute(); results[idx].MeasuredExcitationMilliVolts = measureExcitation.Measurement * 1000.0; //14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics //SLICE excitation is not absoluted, but carrying through the values h results[idx].NegativeExcitation = measureExcitation.Measurement < 0; info.NewData(new ServiceCallbackData.DiagnosticNewData() { DasChannelNumber = ChannelActions[idx].DASChannelNumber, Result = results[idx].MeasuredExcitationMilliVolts, Action = ServiceCallbackData.DiagnosticNewData.Actions.MeasureExcitation }); // first figure out what our target is const float VoltageMultiplier = 0.98F; var targetVoltage = 0F; var moduleIdx = DASInfo.MapDASChannelNumber2ModuleArrayIndex(ChannelActions[idx].DASChannelNumber); var channelIdx = DASInfo.MapDASChannelNumber2ModuleChannelNumber(ChannelActions[idx].DASChannelNumber); if (ConfigData != null && ConfigData.Modules != null && ConfigData.Modules.Length > moduleIdx && ConfigData.Modules[moduleIdx].Channels != null && ConfigData.Modules[moduleIdx].Channels.Length > channelIdx) { if (ConfigData.Modules[moduleIdx].Channels[channelIdx] is AnalogInputDASChannel analog) { var excitation = Common.DAS.Concepts.Test.Module.Channel.Sensor.GetExcitationVoltageMagnitudeFromEnum(analog.Excitation); targetVoltage = (float)(excitation * VoltageMultiplier); } else { // it's not an AnalogInputDASChannel throw new NotSupportedException("Slice::MeasureExcitation Only analog channels implemented!"); } } else { // no config data, assume 5V targetVoltage = 5.0F * VoltageMultiplier; } if (measureExcitation.Measurement >= targetVoltage) { // it's OK, no retry break; } } } else { results[idx].MeasuredExcitationMilliVolts = 0; results[idx].NegativeExcitation = false; } } } } /// /// Measure the offset on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel private void MeasureOffset(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.MeasureOffset); if (NumToMeasure == 0) return; var measureOffset = new MeasureStackChannelOffset(this); measureOffset.NumberOfChannels = (byte)NumToMeasure; measureOffset.DeviceID = 0; // send to base var measureChannels = new byte[NumToMeasure]; var channelCounter = 0; for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureOffset) { measureChannels[channelCounter++] = (byte)ChannelActions[idx].DASChannelNumber; } } measureOffset.ChannelList = measureChannels; measureOffset.SyncExecute(); var measureOffset2 = new RetrieveSampleAverage(this); measureOffset2.DeviceID = 0; // send to base var avgTimeSeconds = 1.0 / 60.0 * 2.0; // 2 * 60Hz cycles measureOffset2.Samples = (ushort)(10000 * avgTimeSeconds); if (measureOffset2.Samples < 1) { measureOffset2.Samples = 1; } measureOffset2.SyncExecute(); // calculate result channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureOffset) { var result = (double)measureOffset.MeasuredOffset[channelCounter]; if (this is EthernetTsrAir) { results[idx].MeasuredOffsetEngineeringUnits = result; } else { results[idx].MeasuredOffsetMilliVolts = result; } info.NewData(new ServiceCallbackData.DiagnosticNewData() { Action = ServiceCallbackData.DiagnosticNewData.Actions.MeasureOffset, Result = result, DasChannelNumber = ChannelActions[idx].DASChannelNumber }); channelCounter++; } } } /// /// Remove the offset on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel private void RemoveOffset(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // first count how many we need to do it on var NumToRemove = ChannelActions.Count(a => a.RemoveOffset); if (NumToRemove == 0) return; var removeOffset = new OffsetChannel(this, 1000 * 60 * 10); removeOffset.DeviceID = 0; // send to base var removeOffsetChannels = new byte[NumToRemove]; var ZeroTargets = new short[NumToRemove]; var TargetTolerances = new ushort[NumToRemove]; var channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].RemoveOffset) { removeOffsetChannels[channelCounter] = (byte)ChannelActions[idx].DASChannelNumber; ZeroTargets[channelCounter] = ZeroTargetValue; TargetTolerances[channelCounter] = TargetToleranceValue; channelCounter++; } } removeOffset.StackChannelList = removeOffsetChannels; removeOffset.ZeroTarget = ZeroTargets; removeOffset.TargetTolerance = TargetTolerances; removeOffset.SyncExecute(); } private void CheckDigitalValues(IDiagnosticActions[] channelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { var onesToDo = from ca in channelActions where ca.CheckDigitalState select ca; if (!onesToDo.Any()) return; var measureOffset = new RetrieveSampleAverage(this) { DeviceID = 0 }; // send to base const double avgTimeSeconds = 1.0 / 60.0 * 2.0; // 2 * 60Hz cycles measureOffset.Samples = (ushort)(10000 * avgTimeSeconds); if (measureOffset.Samples < 1) { measureOffset.Samples = 1; } measureOffset.SyncExecute(); var inputModes = new Dictionary(); var idx = 0; foreach (var module in ConfigData.Modules) { foreach (var ch in module.Channels) { if (ch.IsConfigured()) { inputModes[idx] = ((AnalogInputDASChannel)ch).DigitalMode; } idx++; } } for (idx = 0; idx < results.Length; idx++) { if (!inputModes.ContainsKey(idx)) { continue; } //9955 adjusting these to match what SPD00036 is doing //9955 reported that CCNO and CCNC values were good //but H2L and L2H transitions were backwards //I tested each setting using ch1 on SPD00036 switch (inputModes[idx]) { case DigitalInputModes.CCNO: results[idx].DigitalInputActiveState = measureOffset._data[idx] <= DigitalInputs.ConstantCurrentBreakPoint; break; case DigitalInputModes.THL: results[idx].DigitalInputActiveState = measureOffset._data[idx] <= DigitalInputs.VoltageInputBreakPoint; break; case DigitalInputModes.TLH: results[idx].DigitalInputActiveState = measureOffset._data[idx] > DigitalInputs.VoltageInputBreakPoint; break; case DigitalInputModes.CCNC: results[idx].DigitalInputActiveState = measureOffset._data[idx] > DigitalInputs.ConstantCurrentBreakPoint; break; } } } /// /// Measure the offset on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel protected virtual void MeasureFinalOffset(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // verify the sample rate var srQuery = new QueryArmAttribute(this); srQuery.Key = AttributeTypes.ArmAndEventAttributes.DiagnosticsSampleRateHz; srQuery.SyncExecute(); var samplerate = (double)(uint)srQuery.Value; if (samplerate == 0) { throw new Exception("MeasureFinalOffset: Samplerate is 0"); } // get the average offset var measureOffset = new RetrieveSampleAverage(this); measureOffset.DeviceID = 0; // send to base var avgTimeSeconds = 1.0 / 60.0 * 2.0; // 2 * 60Hz cycles measureOffset.Samples = (ushort)(samplerate * avgTimeSeconds); if (measureOffset.Samples < 1) { measureOffset.Samples = 1; } measureOffset.SyncExecute(); // store the values in arm attributes var setSR = new SetArmAttribute(this); setSR.SetValue(AttributeTypes.ArmAndEventAttributes.StackChannelMeasuredOffsetADC, measureOffset._data, true); setSR.SyncExecute(); // set the calibration result for (var idx = 0; idx < results.Length; idx++) { results[idx].FinalOffsetADC = measureOffset.GetChannelData(results[idx].DASChannelNumber); if (ChannelActions[idx].RemoveOffset) { results[idx].AutoZeroPercentDeviation = 100D * (double)results[idx].FinalOffsetADC / short.MaxValue; } else { results[idx].AutoZeroPercentDeviation = null; } info.NewData(new ServiceCallbackData.DiagnosticNewData() { Result = results[idx].FinalOffsetADC, DasChannelNumber = results[idx].DASChannelNumber, Action = ServiceCallbackData.DiagnosticNewData.Actions.FinalOffset }); //attempt to resolve the removed adc by using the before and after info try { if (null != results[idx].MeasuredOffsetMilliVolts && ChannelActions[idx].RemoveOffset) { results[idx].RemovedOffsetADC = Convert.ToInt32((double)results[idx].MeasuredOffsetMilliVolts / results[idx].ScalefactorMilliVoltsPerADC - (double)results[idx].FinalOffsetADC); } else { results[idx].RemovedOffsetADC = 0; } } catch (Exception) { } } } /// /// Measure the internal offset on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel protected virtual void MeasureInternalOffset(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results, bool bFinalOffset) { // first count how many we need to do it on var numToMeasure = ChannelActions.Count(a => a.MeasureInternalOffset); if (numToMeasure == 0) return; foreach (var r in results) { r.RemovedInternalOffsetADC = 0; r.MeasuredInternalOffsetMilliVolts = 0; } } /// /// Convert gain code to value based on Slice 1 conversion table /// /// /// protected virtual double GainCodeToGainValue(ushort gainCode) { var gainValueString = ((GainCodes)gainCode).ToString(); if (!double.TryParse(gainValueString.ToString().TrimStart('G'), out double gainValue)) { gainValue = 1.0D; } return gainValue; } public enum GainCodes //Used by Slice 1 and 1.5 (Base +) { G1 = 0, G2 = 1, G4 = 2, G8 = 3, G16 = 4, G32 = 5, G64 = 6, G128 = 7, G10 = 8, G20 = 9, G40 = 10, G80 = 11, G160 = 12, G320 = 13, G640 = 14, G1280 = 15 } /// /// Measure the noise on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel private void MeasureNoise(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.MeasureNoise); if (NumToMeasure == 0) return; var measureChannelNoiseStat = new MeasureChannelNoiseStatistic(this); measureChannelNoiseStat.DeviceID = 0; // send to base var noiseChannelList = new byte[NumToMeasure]; var noiseUseGainOfOne = new bool[NumToMeasure]; var channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureNoise) { noiseChannelList[channelCounter] = (byte)ChannelActions[idx].DASChannelNumber; noiseUseGainOfOne[channelCounter] = false; channelCounter++; } } measureChannelNoiseStat.ChannelList = noiseChannelList; measureChannelNoiseStat.UseGainOfOne = noiseUseGainOfOne; measureChannelNoiseStat.SyncExecute(); channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureNoise) { // convert standard deviation to % of full scale var stddev = measureChannelNoiseStat.StandardDeviation[channelCounter]; var percent = 100.0f * stddev / 32768.0f; results[idx].NoisePercentFullScale = percent; info.NewData(new ServiceCallbackData.DiagnosticNewData() { Action = ServiceCallbackData.DiagnosticNewData.Actions.MeasureNoise, DasChannelNumber = results[idx].DASChannelNumber, Result = results[idx].NoisePercentFullScale }); channelCounter++; } } } /// /// Check the shunt on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel private void PerformCalSignalCheck(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.PerformCalSignalCheck); if (NumToMeasure == 0) return; //var queryChannelShuntResults = new QueryChannelShuntResults(this); var queryChannelShuntResults = new QueryChannelCalSignalResults(this); queryChannelShuntResults.DeviceID = 0; // send to base var shuntChannelList = new byte[NumToMeasure]; var channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformCalSignalCheck) { shuntChannelList[channelCounter] = (byte)ChannelActions[idx].DASChannelNumber; channelCounter++; } } queryChannelShuntResults.StackChannelList = shuntChannelList; try { queryChannelShuntResults.SyncExecute(); channelCounter = 0; for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformCalSignalCheck) { results[idx].CalSignalCheckFailed = false; results[idx].MeasuredCalSignalMv = queryChannelShuntResults.ActualMV[channelCounter]; results[idx].TargetCalSignalMv = queryChannelShuntResults.TargetMV[channelCounter]; info.NewData(new ServiceCallbackData.DiagnosticNewData() { Result = results[idx].MeasuredCalSignalMv, DasChannelNumber = results[idx].DASChannelNumber, Action = ServiceCallbackData.DiagnosticNewData.Actions.CalSignalCheck }); channelCounter++; } } } catch (NotSupportedException) { // If the functionality doesn't exist, just make sure the results are set to null // and the app will do the right thing. // for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformCalSignalCheck) { results[idx].MeasuredCalSignalMv = null; results[idx].TargetCalSignalMv = null; } } } catch (NotImplementedException) { // If the functionality doesn't exist, just make sure the results are set to null // and the app will do the right thing. // for (var idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformCalSignalCheck) { results[idx].MeasuredCalSignalMv = null; results[idx].TargetCalSignalMv = null; } } } catch (Exception) { if (queryChannelShuntResults.ResponseStatus == DFConstantsAndEnums.CommandStatus.StatusSetupCALDACOutputExceeded) { // we couldn't do a shunt check for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformCalSignalCheck) { results[idx].CalSignalCheckFailed = true; results[idx].MeasuredCalSignalMv = null; results[idx].TargetCalSignalMv = null; } } } else { throw; } } } /// /// Check the shunt on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel private void PerformShuntCheck(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.DiangosShuntDAC)) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.PerformShuntCheck); if (NumToMeasure == 0) return; var queryChannelShuntResults = new QueryChannelShuntResults(this); queryChannelShuntResults.DeviceID = 0; // send to base byte[] shuntChannelList = new byte[NumToMeasure]; int channelCounter = 0; for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformShuntCheck) { shuntChannelList[channelCounter] = (byte)ChannelActions[idx].DASChannelNumber; channelCounter++; } } queryChannelShuntResults.StackChannelList = shuntChannelList; #region tryCatch try { queryChannelShuntResults.SyncExecute(); channelCounter = 0; for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformShuntCheck) { results[idx].ShuntDeflectionFailed = false; results[idx].MeasuredShuntDeflectionMv = queryChannelShuntResults.ActualDeflectionMV[channelCounter]; results[idx].TargetShuntDeflectionMv = queryChannelShuntResults.TargetDeflectionMV[channelCounter]; info.NewData(new ServiceCallbackData.DiagnosticNewData() { Action = ServiceCallbackData.DiagnosticNewData.Actions.ShuntCheck, DasChannelNumber = results[idx].DASChannelNumber, Result = results[idx].MeasuredShuntDeflectionMv }); channelCounter++; } } } catch (System.NotSupportedException) { // If the functionality doesn't exist, just make sure the results are set to null // and the app will do the right thing. // for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformShuntCheck) { results[idx].MeasuredShuntDeflectionMv = null; results[idx].TargetShuntDeflectionMv = null; } } } catch (System.NotImplementedException) { // If the functionality doesn't exist, just make sure the results are set to null // and the app will do the right thing. // for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformShuntCheck) { results[idx].MeasuredShuntDeflectionMv = null; results[idx].TargetShuntDeflectionMv = null; } } } catch (System.Exception) { if (queryChannelShuntResults.ResponseStatus == DFConstantsAndEnums.CommandStatus.StatusSetupShuntDACOutputExceeded) { // we couldn't do a shunt check for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformShuntCheck) { results[idx].ShuntDeflectionFailed = true; results[idx].MeasuredShuntDeflectionMv = null; results[idx].TargetShuntDeflectionMv = null; } } } else { throw; } } #endregion } else { for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformShuntCheck) { results[idx].ShuntDeflectionFailed = true; results[idx].MeasuredShuntDeflectionMv = null; results[idx].TargetShuntDeflectionMv = null; } } } } /// /// Check the VoltageInsertionCheck on the channels that have it flagged /// /// An array of actions. One entry per channel /// Our async data /// An array of results. One entry per channel protected virtual void PerformVoltageInsertionCheck(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.PerformVoltageInsertCheck); if (NumToMeasure == 0) return; //slice 1 can not handle voltage insertion ... for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].PerformVoltageInsertCheck) { //results[idx].ShuntDeflectionFailed = true; results[idx].MeasuredGain = null; results[idx].TargetGain = null; } } } protected virtual void PerformBridgeResistanceMeasurement(IDiagnosticActions[] ChannelActions, SliceServiceAsyncInfo info, ref IDiagnosticResult[] results) { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.DiangosShuntDAC)) { // first count how many we need to do it on var NumToMeasure = ChannelActions.Count(a => a.MeasureBridgeResistance); if (NumToMeasure == 0) return; var measureChannelBridgeResistance = new MeasureStackChannelBridgeResistance(this); measureChannelBridgeResistance.DeviceID = 0; // send to base byte[] channelList = new byte[NumToMeasure]; int channelCounter = 0; for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureBridgeResistance) { channelList[channelCounter] = (byte)ChannelActions[idx].DASChannelNumber; channelCounter++; } } measureChannelBridgeResistance.StackChannelList = channelList; measureChannelBridgeResistance.SyncExecute(); channelCounter = 0; for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureBridgeResistance) { // Store results results[idx].BridgeResistance = measureChannelBridgeResistance.MeasuredBridgeResistanceOhms[channelCounter]; channelCounter++; } } } else { for (int idx = 0; idx < ChannelActions.Length; idx++) { if (ChannelActions[idx].MeasureBridgeResistance) { // Store results results[idx].BridgeResistance = null; } } } } public DFConstantsAndEnums.VoltageStatusColor ConvertInputVoltage2Color(double voltage) { if (voltage > MaximumValidInputVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Off; } if (voltage > InputHighVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Red; } if (voltage > InputMediumVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Green; } if (voltage >= InputLowVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Yellow; } if (voltage >= MinimumValidInputVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Red; } return DFConstantsAndEnums.VoltageStatusColor.Off; } public DFConstantsAndEnums.VoltageStatusColor ConvertBatteryVoltage2Color(double batteryVoltage) { if (batteryVoltage > MaximumValidBatteryVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Off; } if (batteryVoltage > BatteryHighVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Red; } if (batteryVoltage > BatteryMediumVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Green; } if (batteryVoltage >= BatteryLowVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Yellow; } if (batteryVoltage >= MinimumValidBatteryVoltage) { return DFConstantsAndEnums.VoltageStatusColor.Red; } return DFConstantsAndEnums.VoltageStatusColor.Off; } public DFConstantsAndEnums.VoltageStatusColor ConvertBatteryCapacity2Color(double voltage, double capacity) { if (voltage >= 5.0) { if (capacity > 90) { return DFConstantsAndEnums.VoltageStatusColor.Green; } else if (capacity <= 90 && capacity >= 40) { return DFConstantsAndEnums.VoltageStatusColor.Yellow; } else if (capacity < 40) { return DFConstantsAndEnums.VoltageStatusColor.Red; } } return DFConstantsAndEnums.VoltageStatusColor.Off; } public string ConvertInputStatusColor2Status(DFConstantsAndEnums.VoltageStatusColor color, double inputVoltage) { switch (color) { case DFConstantsAndEnums.VoltageStatusColor.Green: case DFConstantsAndEnums.VoltageStatusColor.Yellow: case DFConstantsAndEnums.VoltageStatusColor.Red: return inputVoltage.ToString(); default: return Command.TDAS.TestAll.VoltageStatus.None.ToString(); } } /// /// created for 11282, this just clears the diagnostics mode for das /// this should be part of a generic services /// protected virtual void TurnOffDiagnosticsMode() { } public string ConvertBatteryStatusColor2Status(DFConstantsAndEnums.VoltageStatusColor color, double batteryVoltage) { switch (color) { case DFConstantsAndEnums.VoltageStatusColor.Green: case DFConstantsAndEnums.VoltageStatusColor.Yellow: case DFConstantsAndEnums.VoltageStatusColor.Red: return batteryVoltage.ToString(); default: return Command.TDAS.TestAll.VoltageStatus.None.ToString(); } } /// /// FB 15815 /// Return true if the SmartChargeResistorSetting is not 128 otherwise return ture. /// return null if there is any exception getting the value or the command is not supported in this protocol /// public virtual bool? ChargingEnabled { get { try { //http://manuscript.dts.local/f/cases/39462/SPS-unnecessarily-queries-SmartChargeResistorSetting-while-armed if (null != DASArmStatus && DASArmStatus.IsArmed) { return null; } if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.QueryBatterySOC)) { var qsa = new QuerySystemAttributeSLICE2(this); qsa.Key = AttributeTypes.SystemAttributesSLICE2.SmartChargeResistorSetting; qsa.SyncExecute(); //Charging status enabled/disabled return Convert.ToInt32(qsa.Value) != SmartChargeResistorSettingDisabledValue; } else { //Command is not supported , charging status is unknown return null; } } catch (Exception ex) { //Command is not supported , charging status is unknown APILogger.Log(ex); return null; } } } /// /// returns whether the device supports the smart charging resistor setting. /// it's possible only SLICE2 supports, this, but the existing code assumes they all do while /// TSR AIR does not /// I'm not using it currently as maybe for now return "(UNKNOWN)" is the right thing to do /// as we don't explicitly know what the Voltage values should be for charging, discharging /// I leave it here as a future clue when the issue comes up again. /// /// private bool HasSmartChargingResistorSetting() { switch (this) { case EthernetTsrAir _: return false; } return true; } public virtual string ConvertInputVoltage2BatteryCharging(double inputVoltage) { var chargingEnabled = ChargingEnabled; if (!chargingEnabled.HasValue) { //FB 15815 return unknown if there is any error in charging status return Resources.Unknown; } //FB 15815 charging is disabled if (!chargingEnabled.Value) { return Resources.NotCharging; } if (inputVoltage <= SecondInputVoltage && inputVoltage >= FirstInputVoltage) { return Resources.NotCharging; } else if (inputVoltage > SecondInputVoltage) { return Resources.Charging; } else { return Resources.Discharging; } } /// /// Measure the input and battery voltages /// /// Our async data protected void GetBaseInputs(bool powerPro = false) { var inputValues = new BaseInputValues(); var inputs = powerPro ? new SLICEPowerProInputReader(this) : new SLICEBaseInputReader(this); try { var batteryMilliVolts = powerPro ? inputs.DirectBackupMilliVolts : inputs.BackupMilliVolts; var batteryVoltage = Math.Round(batteryMilliVolts / 1000D, 1); if ((batteryVoltage < MinimumValidBatteryVoltage) || (batteryVoltage > MaximumValidBatteryVoltage)) { batteryVoltage = 0D; batteryMilliVolts = 0D; } var inputVoltage = Math.Round(inputs.InputMilliVolts / 1000D, 1); var batteryVoltageStatusColor = DFConstantsAndEnums.VoltageStatusColor.Off; if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.QueryBatterySOC) && inputValues.ChargeCapacityValid) { batteryVoltageStatusColor = ConvertBatteryCapacity2Color(batteryVoltage, inputValues.ChargeCapacity); } else { batteryVoltageStatusColor = ConvertBatteryVoltage2Color(batteryVoltage); } var batteryVoltageStatus = ConvertBatteryStatusColor2Status(batteryVoltageStatusColor, batteryVoltage); var batteryChargingStatus = string.Empty; if (batteryVoltage >= MinimumValidBatteryVoltage) { batteryChargingStatus = ConvertInputVoltage2BatteryCharging(inputVoltage); } var statusDisplayBattery = ((batteryVoltage < MinimumValidBatteryVoltage) || (batteryVoltage > MaximumValidBatteryVoltage)) ? "---" : batteryVoltage.ToString(System.Globalization.CultureInfo.InvariantCulture) + " V " + batteryChargingStatus; inputValues.InputVoltage = inputVoltage; inputValues.InputMilliVolts = inputVoltage * 1000D; var inputVoltageStatusColor = ConvertInputVoltage2Color(inputVoltage); var inputVoltageStatus = ConvertInputStatusColor2Status(inputVoltageStatusColor, inputVoltage); inputValues.InputVoltageStatus = inputVoltageStatus; inputValues.InputVoltageStatusColor = inputVoltageStatusColor; inputValues.StatusDisplayInput = ((inputVoltage < MinimumValidInputVoltage) || (inputVoltage > MaximumValidInputVoltage)) ? "---" : inputVoltage.ToString(System.Globalization.CultureInfo.InvariantCulture) + " V"; inputValues.MinimumValidInputVoltage = MinimumValidInputVoltage; inputValues.MaximumValidInputVoltage = MaximumValidInputVoltage; // measure the temperature inputValues.TemperatureC = inputs.TemperatureC; // measure the battery power inputValues.BatteryVoltage = batteryVoltage; inputValues.BatteryMilliVolts = batteryMilliVolts; inputValues.BatteryVoltageStatus = batteryVoltageStatus; inputValues.BatteryVoltageStatusColor = batteryVoltageStatusColor; inputValues.MinimumValidBatteryVoltage = MinimumValidBatteryVoltage; inputValues.MaximumValidBatteryVoltage = MaximumValidBatteryVoltage; inputValues.StatusDisplayBattery = statusDisplayBattery; } catch (Exception ex) { APILogger.Log(ex); } // store the result BaseInput = inputValues; } private void SetBaseInputs(QueryArmAndTriggerStatus status) { var inputValues = new BaseInputValues(); if (null == status) { BaseInput = inputValues; return; } var batteryMilliVolts = status.BackupVoltage * 1000D; var batteryVoltage = Math.Round(status.BackupVoltage, 1); if ((batteryVoltage < MinimumValidBatteryVoltage) || (batteryVoltage > MaximumValidBatteryVoltage)) { batteryVoltage = 0D; batteryMilliVolts = 0D; } var inputVoltage = Math.Round(status.InputVoltage, 1); DFConstantsAndEnums.VoltageStatusColor batteryVoltageStatusColor; if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.QueryBatterySOC) && status.BatterySoc > 0.0 && status.BatterySoc <= 100) { //Use battery charge capacity (state of charge (SOC)) instead of voltage for status color batteryVoltageStatusColor = ConvertBatteryCapacity2Color(status.BackupVoltage, status.BatterySoc); } else { batteryVoltageStatusColor = ConvertBatteryVoltage2Color(batteryVoltage); } var batteryVoltageStatus = ConvertBatteryStatusColor2Status(batteryVoltageStatusColor, batteryVoltage); var batteryChargingStatus = string.Empty; if (batteryVoltage >= MinimumValidBatteryVoltage) { batteryChargingStatus = ConvertInputVoltage2BatteryCharging(inputVoltage); } var statusDisplayBattery = batteryVoltage == 0.0 ? "---" : batteryVoltage.ToString(System.Globalization.CultureInfo.InvariantCulture) + " V " + batteryChargingStatus; inputValues.InputVoltage = inputVoltage; inputValues.InputMilliVolts = inputVoltage * 1000D; inputValues.MinimumValidInputVoltage = MinimumValidInputVoltage; inputValues.MaximumValidInputVoltage = MaximumValidInputVoltage; var inputVoltageStatusColor = ConvertInputVoltage2Color(inputVoltage); var inputVoltageStatus = ConvertInputStatusColor2Status(inputVoltageStatusColor, inputVoltage); inputValues.InputVoltageStatus = inputVoltageStatus; inputValues.InputVoltageStatusColor = inputVoltageStatusColor; inputValues.StatusDisplayInput = ((inputVoltage < MinimumValidInputVoltage) || (inputVoltage > MaximumValidInputVoltage)) ? "---" : inputVoltage.ToString(System.Globalization.CultureInfo.InvariantCulture) + " V"; inputValues.BatteryVoltage = batteryVoltage; inputValues.BatteryMilliVolts = batteryMilliVolts; inputValues.MinimumValidBatteryVoltage = MinimumValidBatteryVoltage; inputValues.MaximumValidBatteryVoltage = MaximumValidBatteryVoltage; inputValues.BatteryVoltageStatus = batteryVoltageStatus; inputValues.BatteryVoltageStatusColor = batteryVoltageStatusColor; inputValues.StatusDisplayBattery = statusDisplayBattery; BaseInput = inputValues; //if we have QATS information and if SoC is populated, set it for baseinput //http://manuscript.dts.local/f/cases/39490/Refine-Battery-SoC-column-definition-and-implement if (status.BatterySoc > 0 && status.BatterySoc <= 100) { BaseInput.BatterySoC = status.BatterySoc; } } /// /// returns true if the unit should check/enable/disable /// backup power /// originally written as a performance improvement for S6 /// since it has no battery [reduce # of unnecessary commands] /// /// private bool ShouldUseBackupPower() { if (this is EthernetTsrAir) { return true; } return !(this is EthernetSlice6 || this is EthernetSlice6AirBridge); } protected virtual double MeasureInputMilliVolts() { var measure = new MeasureBaseDiagnosticChannel(this); measure.Channel = MeasureBaseDiagnosticChannel.BaseDiagnosticChannelList.InputVoltage; measure.DeviceGroup = 0; measure.DeviceID = 0; measure.SyncExecute(); return measure.Measurement * 1000D; } private double MeasureTemperatureC() { var measure = new MeasureBaseDiagnosticChannel(this); measure.Channel = MeasureBaseDiagnosticChannel.BaseDiagnosticChannelList.TemperatureC; measure.DeviceGroup = 0; measure.DeviceID = 0; measure.SyncExecute(); return measure.Measurement; } protected virtual double MeasureBackupMilliVolts() { if (!ShouldUseBackupPower()) { return 0D; } var measure = new MeasureBaseDiagnosticChannel(this); measure.Channel = MeasureBaseDiagnosticChannel.BaseDiagnosticChannelList.BackupVoltage; measure.DeviceGroup = 0; measure.DeviceID = 0; measure.SyncExecute(); return measure.Measurement * 1000.0; } private void EnableBackupPower() { if (!ShouldUseBackupPower()) { return; } var setSwitch = new SetSwitchImmediate(this); setSwitch.Switch = (byte)Switches.BaseSwitches.BackupPower; setSwitch.SwitchText = Switches.BaseSwitches.BackupPower.ToString(); setSwitch.Setting = 1; setSwitch.SyncExecute(); } private void DisableBackupPower() { if (!ShouldUseBackupPower()) { return; } var setSwitch = new SetSwitchImmediate(this); setSwitch.Switch = (byte)Switches.BaseSwitches.BackupPower; setSwitch.SwitchText = Switches.BaseSwitches.BackupPower.ToString(); setSwitch.Setting = 0; setSwitch.SyncExecute(); } #endregion #region GetEventDiagnosticsResults private class EventDiagnosticsAsyncPacket { public SliceServiceAsyncInfo info { get; set; } public int EventNumber { get; set; } public PrePostResults WhichResult { get; set; } } /// /// Retrieve the results from the implicit pre and post event diagnostics /// /// Which event number to Retrieve from /// The pre or post test results? /// The function to call with information /// Whatever you want to pass along void IDiagnosticsActions.GetEventDiagnosticsResults(int EventNumber, PrePostResults WhichResult, ServiceCallback callback, object userData) { Debug.Assert(WhichResult == PrePostResults.PreEventDiagnosticsResult); var packet = new EventDiagnosticsAsyncPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); packet.EventNumber = EventNumber; packet.WhichResult = WhichResult; LaunchAsyncWorker("Slice.GetEventDiagnosticsResults", new WaitCallback(AsyncGetEventDiagnosticsResults), packet); } private void GetEventDiagnosticFactoryExcitation(EventDiagnosticsAsyncPacket packet, ref DiagnosticsResult[] resultArray, ref string AttributeName) { var query = new QueryArmAttribute(this); if (SupportsDiagnosticsFactoryExcitation) { query.Key = AttributeTypes.ArmAndEventAttributes.DiagnosticsFactoryExcitation; AttributeName = AttributeTypes.ArmAndEventAttributes.DiagnosticsFactoryExcitation.ToString(); try { query.SyncExecute(); var factoryExcitations = query.Value as float[]; if (factoryExcitations == null || factoryExcitations.Length != resultArray.Length) { throw new ApplicationException(); // use the default values below } else { if (factoryExcitations.Length != resultArray.Length) { packet.info.Error("Slice.GetEventDiagnosticsResults: factoryExcitations result is different length than scale factors"); return; } for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].ExpectedExcitationMilliVolts = factoryExcitations[idx]; } } } catch { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].ExpectedExcitationMilliVolts = 0.0; } } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].ExpectedExcitationMilliVolts = 0.0; } } } private void SetFinalOffsetADC(short[] measuredOffsetADC, DiagnosticsResult[] resultArray) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].FinalOffsetADC = null; resultArray[idx].AutoZeroPercentDeviation = null; } var removeOffsetArray = new bool[resultArray.Length]; var index = 0; foreach (var module in ConfigData.Modules) { foreach (var channel in module.Channels) { if (!(channel is AnalogInputDASChannel)) { continue; } if (index >= removeOffsetArray.Length) { break; } removeOffsetArray[index] = (channel as AnalogInputDASChannel)?.RemoveOffset ?? false; index++; } } for (var idx = 0; idx < resultArray.Length; idx++) { if (null != measuredOffsetADC && idx < measuredOffsetADC.Length) { resultArray[idx].FinalOffsetADC = measuredOffsetADC[idx]; if (0 == resultArray[idx].FinalOffsetADC && idx < measuredOffsetADC.Length) { resultArray[idx].FinalOffsetADC = measuredOffsetADC[idx]; } if (removeOffsetArray[idx]) { resultArray[idx].AutoZeroPercentDeviation = 100D * measuredOffsetADC[idx] / short.MaxValue; } else { resultArray[idx].AutoZeroPercentDeviation = null; } try { if (0 == resultArray[idx].ScalefactorMilliVoltsPerADC) { resultArray[idx].RemovedOffsetADC = 0; } else { if (resultArray[idx].MeasuredOffsetMilliVolts == null || !removeOffsetArray[idx]) { resultArray[idx].RemovedOffsetADC = 0; } else { resultArray[idx].RemovedOffsetADC = Convert.ToInt32((double)resultArray[idx].MeasuredOffsetMilliVolts / resultArray[idx].ScalefactorMilliVoltsPerADC - (double)resultArray[idx].FinalOffsetADC); } } } catch (Exception ex) { APILogger.Log(ex); } } } } private static void ClearMeasuredGainAllEvents(IDiagnosticResult[] resultArray) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredGain = null; } } protected virtual void GetStackChannelVInsertActualGain(IDiagnosticResult[] resultArray ) { try { if ( IsTSRAIR()) { ClearMeasuredGainAllEvents(resultArray); return; } var query = new QueryEventAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelVInsertActualGain; query.SyncExecute(); if (!(query.Value is float[] actualGain) || actualGain.Length != resultArray.Length) { ClearMeasuredGainAllEvents(resultArray); } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredGain = actualGain[idx]; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredGain = null; } } } private static void ClearMeasuredExcitation(IDiagnosticResult[] resultArray) { try { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredExcitationMilliVolts = null; resultArray[idx].NegativeExcitation = false; } } catch(Exception ex) { APILogger.Log(ex); } } private void GetMeasuredExcitation(IDiagnosticResult[] resultArray) { try { if(IsTSRAIR()) { ClearMeasuredExcitation(resultArray); return; } var query = new QueryEventAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.DiagnosticsExcitationReading; query.SyncExecute(); if (!(query.Value is float[] excitationReading) || excitationReading.Length != resultArray.Length) { ClearMeasuredExcitation(resultArray); } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredExcitationMilliVolts = excitationReading[idx] * 1000D; //14233 Negative Excitation Reported by TDAS hardware not showing in Diagnostics //SLICE excitation is not absoluted, but carrying through the values h resultArray[idx].NegativeExcitation = excitationReading[idx] < 0; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredExcitationMilliVolts = null; resultArray[idx].NegativeExcitation = false; } } } private static void ClearStackChannelNoiseStatistic(IDiagnosticResult[] resultArray) { try { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].NoisePercentFullScale = null; } } catch( Exception ex) { APILogger.Log(ex); } } private void GetStackChannelNoiseStatistic(IDiagnosticResult[] resultArray) { try { if (IsTSRAIR()) { ClearStackChannelNoiseStatistic(resultArray); return; } var query = new QueryEventAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelNoiseStatistic; query.SyncExecute(); if (!(query.Value is float[] noiseStatisticStdDev) || noiseStatisticStdDev.Length != resultArray.Length) { ClearStackChannelNoiseStatistic(resultArray); } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].NoisePercentFullScale = 100.0 * noiseStatisticStdDev[idx] / 32768.0; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].NoisePercentFullScale = null; } } } /// /// returns true if the DAS supports DiagnosticFactoryExcitation attribute, /// false otherwise /// protected virtual bool SupportsDiagnosticsFactoryExcitation => true; private void AsyncGetEventDiagnosticsResults(object asyncInfo) { //performance improvement, don't check for existing event data [optional] //part of reducing unnecessary commands var packet = asyncInfo as EventDiagnosticsAsyncPacket; if (RunTestVariables.InRunTest && RunTestVariables.OptimizeQueryEventData // http://manuscript.dts.local/f/cases/18876/Voltage-and-current-data-incorrect-for-squibs-when-downloading-in-Run-Test // FIXME; HACK // CPB 2021-08-12 // Optimaztion seems to work fine for all channels but SLICE PRO TOM. I suspect that the real issue // is in the population of the ChannelDiagnosticResults because that is the object that appears to // be holding the needed cached values for analog channels. At the time of writing this, the result // of this optimization on SPT data is missing datazero and incorrect EU scalefactor && !IsTOM()) { if (null != ChannelDiagnosticsResults && ChannelDiagnosticsResults.Any()) { packet.info.Success(); return; } } if (IsTOM()) { AsyncGetEventDiagnosticsResultsTOM(asyncInfo); } else { var AttributeName = ""; try { DiagnosticsResult[] resultArray = null; var query = new QueryEventAttribute(this); // first get the scale factors query.EventNumber = (ushort)packet.EventNumber; query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelScaleFactorsMillivoltsPerADC; AttributeName = AttributeTypes.ArmAndEventAttributes.StackChannelScaleFactorsMillivoltsPerADC.ToString(); query.SyncExecute(); if (!(query.Value is float[] scalefactors) || scalefactors.Length == 0) { packet.info.Error("Slice.GetEventDiagnosticsResults: no scale factors found"); return; } else { //TSR AIR currently returns 24 scalefactors, but we only want the first 12 if (this is WinUSBTsrAir || this is EthernetTsrAir) { resultArray = new DiagnosticsResult[scalefactors.Length<12? scalefactors.Length:12]; } else { resultArray = new DiagnosticsResult[scalefactors.Length]; } for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx] = new DiagnosticsResult(); resultArray[idx].DASChannelNumber = idx; resultArray[idx].EventNumber = packet.EventNumber; if (this is WinUSBTsrAir || this is EthernetTsrAir) { resultArray[idx].ScalefactorEngineeringUnitsPerADC = scalefactors[idx]; } else { resultArray[idx].ScalefactorMilliVoltsPerADC = scalefactors[idx]; } } } packet.info.Progress(100 / 9 * 1); #region factory excitation GetEventDiagnosticFactoryExcitation(packet, ref resultArray, ref AttributeName); packet.info.Progress(100 / 9 * 2); #endregion #region measured excitation (optional) GetMeasuredExcitation(resultArray); packet.info.Progress(100 / 9 * 3); #endregion #region StackChannelTargetShuntDeflectionMV try { float[] targetShuntDeflectionMv = null; if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.DiangosShuntDAC)) { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelTargetShuntDeflectionMV; query.SyncExecute(); targetShuntDeflectionMv = query.Value as float[]; } if (targetShuntDeflectionMv == null || targetShuntDeflectionMv.Length != resultArray.Length) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetShuntDeflectionMv = null; } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetShuntDeflectionMv = targetShuntDeflectionMv[idx]; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetShuntDeflectionMv = null; } } packet.info.Progress(100 / 9 * 4); #endregion #region StackChannelActualShuntDeflectionMV try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelActualShuntDeflectionMV; query.SyncExecute(); if (!(query.Value is float[] actualShuntDeflectionMv) || actualShuntDeflectionMv.Length != resultArray.Length) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredShuntDeflectionMv = null; } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredShuntDeflectionMv = actualShuntDeflectionMv[idx]; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredShuntDeflectionMv = null; } } packet.info.Progress(100 / 9 * 5); #endregion GetStackChannelVInsertActualGain(resultArray); #region StackChannelTargetCalSignalMV if (SupportsIEPECalSignal) { try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelTargetCalSignalMV; query.SyncExecute(); if (!(query.Value is float[] targetCalSignalMv) || targetCalSignalMv.Length != resultArray.Length) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetCalSignalMv = null; } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetCalSignalMv = targetCalSignalMv[idx]; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetCalSignalMv = null; } } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].TargetCalSignalMv = null; } } #endregion #region StackChannelActualCalSignalMV if (true == SupportsIEPECalSignal) { try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelActualCalSignalMV; query.SyncExecute(); if (!(query.Value is float[] actualCalSignalMv) || actualCalSignalMv.Length != resultArray.Length) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredCalSignalMv = null; } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredCalSignalMv = actualCalSignalMv[idx]; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredCalSignalMv = null; } } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredCalSignalMv = null; } } #endregion #region measured offset (optional) try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelMeasuredOffsetMV; query.SyncExecute(); if (!(query.Value is float[] measuredOffsetMV) || measuredOffsetMV.Length != resultArray.Length) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredOffsetMilliVolts = null; } } else { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredOffsetMilliVolts = measuredOffsetMV[idx]; } } } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredOffsetMilliVolts = null; } } packet.info.Progress(100 / 9 * 6); #endregion #region final offset (optional) try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelFinalOffsetCounts; query.SyncExecute(); var query2 = new QueryEventAttribute(this); // first get the scale factors query2.EventNumber = (ushort)packet.EventNumber; query2.Key = AttributeTypes.ArmAndEventAttributes.StackChannelMeasuredOffsetADC; query2.SyncExecute(); var measuredOffsetADC = query2.Value as short[]; SetFinalOffsetADC(measuredOffsetADC, resultArray); } catch (Exception) { for (var idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].FinalOffsetADC = null; resultArray[idx].AutoZeroPercentDeviation = null; } } packet.info.Progress(100 / 9 * 7); #endregion #region noise (optional) GetStackChannelNoiseStatistic(resultArray); packet.info.Progress(100 / 9 * 8); #endregion // pickup shunt check (optional) packet.info.Progress(100); //apparently we can't requerying them as it's borked now var dasChannelNumberToZeromVADC = new Dictionary(); foreach (var module in ConfigData.Modules) { foreach (var ch in module.Channels) { if (ch is AnalogInputDASChannel aic) { dasChannelNumberToZeromVADC[aic.Number] = aic.ZeromVInADC; } } } foreach (var result in resultArray) { if (dasChannelNumberToZeromVADC.ContainsKey(result.DASChannelNumber)) { result.ZeroMVInADC = dasChannelNumberToZeromVADC[result.DASChannelNumber]; } } SetChannelDiagnosticsResults(resultArray, false); packet.info.Success(); } catch (CanceledException) { packet.info.Cancel(); } catch (Exception ex) { packet.info.Error(ex.Message + " Attribute: " + AttributeName, ex); } } } protected virtual void QueryInternalOffsets(ref DiagnosticsResult[] resultsArray) { return; } private void AsyncGetEventDiagnosticsResultsTOM(object asyncInfo) { var packet = asyncInfo as EventDiagnosticsAsyncPacket; var AttributeName = ""; try { DiagnosticsResult[] resultArray = null; var query = new QueryEventAttribute(this); // first get the scale factors query.EventNumber = (ushort)packet.EventNumber; query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelScaleFactorsMillivoltsPerADC; AttributeName = AttributeTypes.ArmAndEventAttributes.StackChannelScaleFactorsMillivoltsPerADC.ToString(); query.SyncExecute(); if (!(query.Value is float[] scalefactors) || scalefactors.Length == 0) { packet.info.Error("Slice.GetEventDiagnosticsResults: no scale factors found"); return; } else { resultArray = new DiagnosticsResult[16];//save space for 8 digital outs for (var idx = 0; idx < scalefactors.Length; idx++) { resultArray[idx] = new DiagnosticsResult(); resultArray[idx].DASChannelNumber = idx; resultArray[idx].EventNumber = packet.EventNumber; resultArray[idx].ScalefactorMilliVoltsPerADC = scalefactors[idx] / 1000D; } for (var i = 8; i < 16; i++) { resultArray[i] = new DiagnosticsResult(); resultArray[i].DASChannelNumber = i; resultArray[i].EventNumber = packet.EventNumber; resultArray[i].ScalefactorMilliVoltsPerADC = 1; } } packet.info.Progress(100 / 9 * 1); #region factory excitation #endregion #region measured excitation (optional) #endregion #region StackChannelTargetShuntDeflectionMV packet.info.Progress(100 / 9 * 4); #endregion #region StackChannelActualShuntDeflectionMV packet.info.Progress(100 / 9 * 5); #endregion #region StackChannelTargetCalSignalMV #endregion #region StackChannelActualCalSignalMV #endregion #region measured offset (optional) /* try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelMeasuredOffsetMV; query.SyncExecute(); var measuredOffsetMV = query.Value as float[]; if (measuredOffsetMV == null || measuredOffsetMV.Length != resultArray.Length) { for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredOffsetMilliVolts = null; } } else { for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredOffsetMilliVolts = measuredOffsetMV[idx]; } } } catch (System.Exception) { for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].MeasuredOffsetMilliVolts = null; } } packet.info.Progress(100 / 9 * 6); #endregion */ //#region final offset (optional) query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelFinalOffsetCounts; query.SyncExecute(); var finalOffsetsADC = query.Value as short[]; for (int idx = 0; idx < resultArray.Length && idx < finalOffsetsADC.Length; idx++) { resultArray[idx].FinalOffsetADC = finalOffsetsADC[idx]; resultArray[idx].AutoZeroPercentDeviation = null; } /* try { query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelMeasuredOffsetADC; query.SyncExecute(); var finalOffsetADC = query.Value as Int16[]; if (finalOffsetADC == null || finalOffsetADC.Length != resultArray.Length) { for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].FinalOffsetADC = null; } } else { for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].FinalOffsetADC = finalOffsetADC[idx]; try { if (0 == resultArray[idx].ScalefactorMilliVoltsPerADC) { resultArray[idx].RemovedOffsetADC = 0; } else { if (resultArray[idx].MeasuredOffsetMilliVolts == null) { resultArray[idx].RemovedOffsetADC = 0; } else { resultArray[idx].RemovedOffsetADC = Convert.ToInt32((double)resultArray[idx].MeasuredOffsetMilliVolts / resultArray[idx].ScalefactorMilliVoltsPerADC - (double)resultArray[idx].FinalOffsetADC); } } } catch (System.Exception) { } } } } catch (System.Exception) { for (int idx = 0; idx < resultArray.Length; idx++) { resultArray[idx].FinalOffsetADC = null; } }*/ packet.info.Progress(100 / 9 * 7); #endregion #region noise (optional) packet.info.Progress(100 / 9 * 8); #endregion #region InternallOffsetADC QueryInternalOffsets(ref resultArray); #endregion // pickup shunt check (optional) packet.info.Progress(100); SetChannelDiagnosticsResults(resultArray, false); packet.info.Success(); } catch (CanceledException) { packet.info.Cancel(); } catch (Exception ex) { packet.info.Error(ex.Message + " Attribute: " + AttributeName, ex); } } #endregion void IDiagnosticsActions.SquibFireCheckArm(double delay, double duration, ServiceCallback callback, object userData) { var packet = new SquibFireCheckArmPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); packet.MaxDelay = delay; packet.MaxDuration = duration; packet.DiagnosticsSampleRateHz = 10000; packet.DiagnosticsAAFilterFrequencyHz = 2000; LaunchAsyncWorker("SLICE.SquibFireCheckArm", new WaitCallback(AsyncSquibFireCheckArm), packet); } /// /// a flag to indicate that start record delay was not set to 0 and/or didn't get queried /// private const ushort START_RECORD_DELAY_UNINITIALIZED = ushort.MaxValue; /// /// the original start record delay before it gets to 0 in squib fire check /// 25462 StartRecDelayInSecond in System Atr#104 when set before arming is overwritten by datapro during diagnostics /// private ushort OriginalStartRecordDelay = START_RECORD_DELAY_UNINITIALIZED; protected virtual ushort? GetMaxEvents() { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.MaxEvents)) { try { QuerySystemAttribute qsa = new QuerySystemAttribute(this, 1000); qsa.DeviceID = 0; qsa.Key = AttributeTypes.SystemAttributes.MaxEvent; qsa.SyncExecute(); return (ushort)qsa.Value; } catch (Exception ex) { APILogger.Log("Exception getting max number of events", ex); } } return null; } /// /// sets the maximum number of events to record /// /// protected virtual void SetMaxEvents(int numEvents) { if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.MaxEvents)) { try { var ssa3 = new SetSystemAttribute(this); ssa3.DeviceID = 0; ssa3.SetValue(AttributeTypes.SystemAttributes.MaxEvent, Convert.ToByte(numEvents), true); ssa3.SyncExecute(); } catch (Exception ex) { APILogger.Log("Exception setting max number of events", ex); } } } /// /// the working guts of PrepareForDiagnostics /// /// our async data private void AsyncSquibFireCheckArm(object asyncInfo) { var packet = asyncInfo as SquibFireCheckArmPacket; if (IsTOM()) { var bfe = new BeginFlashErase(this, 5000); bfe.DeviceGroup = 0; bfe.DeviceID = 0; bfe.SyncExecute(); var done = false; while (!done) { var qfes = new QueryFlashEraseStatus(this, 30 * 1000); qfes.DeviceGroup = 0; qfes.DeviceID = 0; qfes.SyncExecute(); if (qfes.LastError != DFConstantsAndEnums.CommandStatus.StatusNoError) { done = true; return; } else if (qfes.PercentComplete < 100.0f) { Thread.Sleep(200); } else { done = true; } } ResetEventListPriorToArm(); SetArmMode(DFConstantsAndEnums.RecordingMode.CircularBuffer); SetArmAttribute(AttributeTypes.ArmAndEventAttributes.SampleRate, packet.DiagnosticsSampleRateHz, true); SetArmAttribute(AttributeTypes.ArmAndEventAttributes.AAFilterFrequency, Convert.ToSingle(packet.DiagnosticsAAFilterFrequencyHz), true); SetArmAttribute(AttributeTypes.ArmAndEventAttributes.Name, Encoding.ASCII.GetBytes("TESTTRIG"), true); SetArmAttribute(AttributeTypes.ArmAndEventAttributes.Description, Encoding.ASCII.GetBytes(" "), true); SetArmAttribute(AttributeTypes.ArmAndEventAttributes.PreTriggerSamplesRequested, Convert.ToUInt64(.1D * packet.DiagnosticsSampleRateHz), true); var PostTriggerSeconds = .5D + (packet.MaxDelay + packet.MaxDuration) / 1000D; SetArmAttribute(AttributeTypes.ArmAndEventAttributes.PostTriggerSamplesRequested, Convert.ToUInt64(PostTriggerSeconds * packet.DiagnosticsSampleRateHz), true); SetMaxEvents(1); StoreOriginalStartRecordDelayAndClear(); var prepForDC = new PrepareForDataCollection(this); prepForDC.SyncExecute(); // Arm try { var a = new Arm(this); a.SyncExecute(); } catch (Exception armex) { packet.info.Error(armex.Message); } } packet.info.Success(); } /// /// part of squib fire check, set the start record delay to 0 before squib fire check? /// I'm not quite sure why we were doing this, but we were and so we still will, /// but we'll store the original delay if we do so we can restore it later /// 25462 StartRecDelayInSecond in System Atr#104 when set before arming is overwritten by datapro during diagnostics /// protected virtual void StoreOriginalStartRecordDelayAndClear() { //initialize to uninitalized so we don't set it unnecessarily later OriginalStartRecordDelay = START_RECORD_DELAY_UNINITIALIZED; //Until DataPRO supports StartRecDelayInSecond, set to 0 if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.StartRecDelayInSecond)) { var queryDelay = new QuerySystemAttributeSLICE2(this); queryDelay.Key = AttributeTypes.SystemAttributesSLICE2.StartRecDelayInSecond; queryDelay.SyncExecute(); var delay = Convert.ToUInt16(queryDelay.Value); if (0 == delay) { return; } //if the original delay is already 0, we don't need to set startrecorddelay, and we don't //need to restore it ... OriginalStartRecordDelay = delay; var startRecDelayInSecond = new SetSystemAttributeSLICE2(this); startRecDelayInSecond.SetValue(AttributeTypes.SystemAttributesSLICE2.StartRecDelayInSecond, Convert.ToUInt16(0), true); startRecDelayInSecond.SyncExecute(); } } void IDiagnosticsActions.ClearTriggerOut(ServiceCallback callback, object userData) { var packet = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.ClearTriggerOut", new WaitCallback(AsyncClearTriggerOut), packet); } protected virtual void AsyncClearTriggerOut(object asyncInfo) { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } info.Success(); } void IDiagnosticsActions.ClearLatches(ServiceCallback callback, object userData) { var packet = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.ClearLatches", new WaitCallback(AsyncClearLatches), packet); } protected virtual void AsyncClearLatches(object asyncInfo) { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } info.Success(); } /// /// clears any das trigger lines (n/a to slice 1) /// void IDiagnosticsActions.ClearDASTriggerLine(ServiceCallback callback, object userData) { var packet = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("SLICE.ClearDASTriggerLine", new WaitCallback(AsyncClearDASTriggerLine), packet); } /// /// clears any das trigger lines asynchronously /// protected virtual void AsyncClearDASTriggerLine(object asyncInfo) { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } info.Success(); } void IDiagnosticsActions.TriggerCheckTrigger(ServiceCallback callback, object userData) { var packet = new DiagnosticsAsyncPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("SLICE.TriggerCheckTrigger", new WaitCallback(AsyncTriggerCheckTrigger), packet); } /// /// the working guts of PrepareForDiagnostics /// /// our async data private void AsyncTriggerCheckTrigger(object asyncInfo) { var packet = asyncInfo as DiagnosticsAsyncPacket; if (IsTOM()) { var SetSwitch = new SetSwitchImmediate(this); SetSwitch.Switch = (byte)Switches.BaseSwitches.TriggerOut; SetSwitch.SwitchText = Switches.BaseSwitches.TriggerOut.ToString(); SetSwitch.Setting = 1; SetSwitch.DeviceGroup = 0; SetSwitch.DeviceID = 0; SetSwitch.SyncExecute(); } packet.info.Success(); } void IDiagnosticsActions.TriggerCheckDownload(double delay, double duration, float dummyAAFilterFrequencyHz, uint dummySampleRateHz, ServiceCallback callback, object userData) { var packet = new SquibFireCheckArmPacket(); packet.info = new SliceServiceAsyncInfo(callback, userData); packet.MaxDelay = delay; packet.MaxDuration = duration; packet.DiagnosticsSampleRateHz = 10000; packet.DiagnosticsAAFilterFrequencyHz = 2000; LaunchAsyncWorker("SLICE.TriggerCheckDownload", new WaitCallback(AsyncTriggerCheckDownload), packet); } /// /// ALWAYS returns an array of 8 short int values - ideally from stack channel final offset attribute, but /// a constant if not available /// http://manuscript.dts.local/f/cases/45361/handle-bad-SPT-attributes-being-used-for-squibfiretest /// /// private short[] GetDataZeroLevelOffetTOM() { try { var query = new QueryArmAttribute(this); query.Key = AttributeTypes.ArmAndEventAttributes.StackChannelFinalOffsetCounts; query.SyncExecute(); var finalOffsetsADC = query.Value as short[]; if (null == finalOffsetsADC || finalOffsetsADC.Length < 8) { throw new InvalidOperationException("offset attribute invalid"); } return finalOffsetsADC; } catch (Exception ex) { APILogger.Log(ex); } //this is a constant that will allow the operation to run normally but we'd prefer to use the attribute above //as long as it's available return new short[] { -18511, -26107, -18390, -26190, -18529, -26314, -18409, -26259, -32768 }; } /// /// the working guts of PrepareForDiagnostics /// /// our async data private void AsyncTriggerCheckDownload(object asyncInfo) { var packet = asyncInfo as SquibFireCheckArmPacket; if (IsTOM()) { Thread.Sleep(100); var qea = new QueryEventAttribute(this); qea.EventNumber = 0; qea.Key = AttributeTypes.ArmAndEventAttributes.TriggerSampleNumber; qea.SyncExecute(); var trigger = (ulong)qea.Value; var totalChannelsQuery = new QueryEventAttribute(this); totalChannelsQuery.EventNumber = 0; totalChannelsQuery.Key = AttributeTypes.ArmAndEventAttributes.TotalChannels; totalChannelsQuery.SyncExecute(); var totalChannels = (byte)totalChannelsQuery.Value; double sps = packet.DiagnosticsSampleRateHz; var dataQuery = new QueryEventData_SLICE2(this); var LastSample = trigger + Convert.ToUInt64((packet.MaxDelay + packet.MaxDuration + 50D) * sps / 1000D); var FirstSample = trigger - Convert.ToUInt64(.1D * sps); dataQuery.EventNumber = 0; dataQuery.LastSample = LastSample * totalChannels; dataQuery.FirstSample = FirstSample * totalChannels; dataQuery.ChannelsDownloaded = totalChannels; var SamplesToGet = LastSample - FirstSample; var SamplesGotten = 0UL; var channelToRawData = new Dictionary>(); var channelToADCData = new Dictionary>(); var leftOver = new List(); var carryoverStartPoint = FirstSample * totalChannels; while (SamplesGotten < SamplesToGet) { dataQuery.FirstSample = carryoverStartPoint; dataQuery.SyncExecute(); var data = dataQuery.GetRawIndexedChannelData(ref leftOver, ref carryoverStartPoint); var SamplesThisTime = 0UL; ushort[] tmp; for (var i = 0; i < 8; i++) { tmp = data[i]; if (!channelToRawData.ContainsKey(i)) { channelToRawData.Add(i, new List()); channelToADCData.Add(i, new List()); } channelToRawData[i].AddRange(tmp); SamplesThisTime = (ulong)tmp.Length; Trace.WriteLine("samples this time: " + SamplesThisTime.ToString()); } SamplesGotten += SamplesThisTime; } for (var i = 0; i < 8; i++) { for (var currentSample = 0; currentSample < channelToRawData[i].Count; currentSample++) { var source = channelToRawData[i][currentSample]; channelToADCData[i].Add((short)((((source & 0x00FF) << 8) | ((source >> 8) & 0x00FF)) + 0x8000)); } } var dataZeroLevelOffsetsFromAttribute = GetDataZeroLevelOffetTOM(); for (var i = 0; i < 8; i += 2) { var squibVoltage = ConfigData.Modules[0].Channels[i] as OutputSquibChannel; var squibCurrent = ConfigData.Modules[0].Channels[i + 1] as OutputSquibChannel; Trace.Assert(null != squibVoltage && null != squibCurrent, "Missing SQUIB Channel"); if (!squibCurrent.IsConfigured()) { continue; } try { var mydataCurrent = channelToADCData[i + 1].ToArray(); var mydataVoltage = channelToADCData[i].ToArray(); var convertedCurrentData = new List(); var convertedVoltageData = new List(); var threshold = 0D; //notes http://foghat/dtswiki/index.php?title=Squib_Fire_Test //updated per http://fogbugz/fogbugz/default.asp?4860#26469 switch (squibCurrent.FireMode) { case SquibFireMode.CAP: threshold = 2.1D; break; case SquibFireMode.CONSTANT: { threshold = .75D * squibCurrent.SquibOutputCurrent; } break; default: throw new NotSupportedException("Invalid firemode for SLICEPRO TOM: " + squibCurrent.FireMode.ToString()); } var indexOverThreshold = int.MaxValue;//marks where we first cross from below threshold to over //we will use this to ensure delay is accurate var indexBelowThresholdAgain = int.MaxValue; //make sure it's > low threshold and < max threshold //we also need to make sure it's above min threshold (and below max) after duration ms //we also need to make sure it's below threshold after the duration. var scaleFactorMv = squibCurrent.ScaleFactorMv; double offset = squibCurrent.PreTestDataZeroLevelADC; //note current is i+1, while voltage is i, not sure why we switched up order here //10000 is an arbitrary value here - calstation was using a value of 0, but ordinary values are //greater than abs 18000, so anything less than that is pretty sus and retrieving from attributes //is probably called for then if (Math.Abs(offset) < 10000) { offset = dataZeroLevelOffsetsFromAttribute[i+1]; } var scaleFactorMv2 = squibVoltage.ScaleFactorMv; double offset2 = squibVoltage.PreTestDataZeroLevelADC; if (Math.Abs(offset2) < 10000) { offset2 = dataZeroLevelOffsetsFromAttribute[i];} //we actually want the scale factors from the current channel, not the channel we have right now which is init/volt var dCurrent = 0D; var dVoltage = 0D; for (var iSample = 0; iSample < mydataCurrent.Length && iSample < mydataVoltage.Length; iSample++) { try { dCurrent = (mydataCurrent[iSample] - offset) * scaleFactorMv / 1000D; dVoltage = (mydataVoltage[iSample] - offset2) * scaleFactorMv2 / 1000D; convertedCurrentData.Add(dCurrent); convertedVoltageData.Add(dVoltage); if (indexBelowThresholdAgain == int.MaxValue) { if (dCurrent > threshold) { indexOverThreshold = Math.Min(iSample, indexOverThreshold); } else { //only set indexbelowthreshold index once we have gone over the threshold. if (indexOverThreshold < int.MaxValue) { indexBelowThresholdAgain = Math.Min(iSample, indexBelowThresholdAgain); } } } } catch (Exception) { } } double actualDelayMS = indexOverThreshold < int.MaxValue ? (indexOverThreshold - .1D * sps) / (sps / 1000D) : 0; double actualDurationMS = (indexBelowThresholdAgain - (double)indexOverThreshold) / (sps / 1000D);//10ksps var deltaDelay = actualDelayMS - squibCurrent.DelayMS; var deltaDuration = actualDurationMS - squibCurrent.DurationMS; var bPassed = true; ChannelDiagnosticsResults[i].SquibDurationPassed = true; ChannelDiagnosticsResults[i].SquibDelayPassed = true; if (Math.Abs(deltaDelay) >= 1) { bPassed = false; ChannelDiagnosticsResults[i].SquibDelayPassed = false; } if (squibCurrent.DurationMS > 6) { if (actualDurationMS < 6) { bPassed = false; ChannelDiagnosticsResults[i].SquibDurationPassed = false; } } else { if (Math.Abs(deltaDuration) > .25 && squibCurrent.LimitDuration) { bPassed = false; ChannelDiagnosticsResults[i].SquibDurationPassed = false; } } if (squibCurrent.LimitDuration) { ChannelDiagnosticsResults[i].MeasuredDurationMS = actualDurationMS; } else { ChannelDiagnosticsResults[i].MeasuredDurationMS = null; } ChannelDiagnosticsResults[i].MeasuredDelayMS = actualDelayMS; ChannelDiagnosticsResults[i].SquibFirePassed = bPassed; //we want to start just before the squib was supposed to fire //index 0 is -.1, so if delay was 0 and we want to start exactly on the first sample //we'd want startindex of .1*sps, so if we start at .09*sps, we've left in .01 of extra pad var startIndex = Convert.ToInt32(.09D * sps + (squibCurrent.DelayMS * sps) / 1000D); var samplesNeeded = .02D * sps + (squibCurrent.DurationMS * sps / 1000D); ChannelDiagnosticsResults[i].SquibFireCurrentData = convertedCurrentData.GetRange(startIndex, Convert.ToInt32(samplesNeeded)).ToArray(); ChannelDiagnosticsResults[i].SquibFireVoltageData = convertedVoltageData.GetRange(startIndex, Convert.ToInt32(samplesNeeded)).ToArray(); ChannelDiagnosticsResults[i].SquibThreshold = threshold; var timeAxis = new List(2000); var dCurrentTime = 0D; //startindex is at .01s before the squib fire, which is at delayMS //note .01 is 10ms var startOffset = -10D + squibCurrent.DelayMS; for (var iSample = 0; iSample < ChannelDiagnosticsResults[i].SquibFireCurrentData.Length; iSample++) { //here I calculate it first in ms, then add it as in seconds dCurrentTime = startOffset + 1000D * (iSample / sps); //dCurrentTime /= 1000D; timeAxis.Add(dCurrentTime); } ChannelDiagnosticsResults[i].SquibFireTimeAxis = timeAxis.ToArray(); var config = GetConfigAttributes(this); config.SetEventDownloaded(0, 1); } catch (Exception ex) { packet.info.Error(ex.Message); } } RestoreOriginalStartRecordDelay(); } packet.info.Success(); } /// /// this restores the original delay after squib fire check /// 25462 StartRecDelayInSecond in System Atr#104 when set before arming is overwritten by datapro during diagnostics /// protected virtual void RestoreOriginalStartRecordDelay() { try { // restore the squib start record delay if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.StartRecDelayInSecond) && OriginalStartRecordDelay != START_RECORD_DELAY_UNINITIALIZED) { var setStartRecordDelay = new SetSystemAttributeSLICE2(this); setStartRecordDelay.SetValue(AttributeTypes.SystemAttributesSLICE2.StartRecDelayInSecond, OriginalStartRecordDelay, true); setStartRecordDelay.SyncExecute(); } } catch (Exception ex) { APILogger.Log(ex); } } } }