using DTS.Common.DAS.Concepts; using DTS.Common.DASResource; using DTS.Common.Enums.DASFactory; using DTS.Common.ICommunication; using DTS.Common.Interface.Connection; using DTS.Common.Interface.DASFactory; using DTS.Common.Interface.DASFactory.Download; using DTS.Common.Utilities.Logging; using DTS.DASLib.Command; using DTS.DASLib.Command.SLICE; using DTS.DASLib.Command.SLICE.DownloadCommands; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Windows.Forms; namespace DTS.DASLib.Service { public partial class Slice : Communication, IDASCommunication, IConfigurationActions, IDiagnosticsActions, ITriggerCheckActions, IRealTimeActions, IArmActions, IDownloadActions where T : IConnection, new() { public virtual DateTime SystemBaseTime => DateTime.MinValue; public virtual bool SupportsTimeSynchronization => false; public virtual bool RangeBandwidthLimited => false; public virtual bool RequireDiagnosticRateMatchSampleRate() { return false; } #region Downloading public class SliceSetEventInfoAsync : SliceServiceAsyncInfo { public string Id { get; set; } public Guid Guid { get; set; } public int EventIndex { get; set; } public ulong StartRecordSample { get; set; } public ulong TotalSamples { get; set; } public ulong[] TriggerSamples { get; set; } public uint EventHasDownloaded { get; set; } public SliceSetEventInfoAsync(ServiceCallback callback, object userData, int eventIndex, ulong startRecordSample, ulong totalSamples, ulong[] triggerSamples, Guid guid, string id, uint eventHasDownloaded) : base(callback, userData) { EventIndex = eventIndex; StartRecordSample = startRecordSample; TriggerSamples = triggerSamples; TotalSamples = totalSamples; Id = id; Guid = guid; EventHasDownloaded = eventHasDownloaded; } } /// void IDownloadActions.CorrectT0s(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.CorretT0s", new WaitCallback(AsyncCorrectT0s), info); } /// /// scans data to find where railed data starts and attempts to identify T0 in the dataset /// /// protected virtual void AsyncCorrectT0s(object asyncInfo) { if (!(asyncInfo is SliceServiceAsyncInfo info)) { return; } try { //scan through for a channel that is configured, then look for a T0 and start of railed data for (var 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]; if (!channel.IsConfigured()) { continue; } if (!FindRailAndT0(channel.Number, out var railIdx, out var t0Idx, info)) { //failed to find railed data (maybe it was railed from the start) //try again on the next channel ... continue; } //we found a T0 and start of railed data, set T0 and total samples recorded attributes info.NewData(DFConstantsAndEnums.T0CorrectionStatus.SettingAttributes); SetT0AndTotalSamples(t0Idx, railIdx); info.Success(); return; } } info.Error($"T0 correction not available"); return; } catch (Exception ex) { info.Error(ex.Message, ex); } } /// /// sets the TriggerSampleNumber and TotalSamplesRecorded attributes to /// the requested values /// /// /// protected void SetT0AndTotalSamples(ulong t0Idx, ulong railIdx) { var sea = new SetEventAttribute(this); sea.SetValue(AttributeTypes.ArmAndEventAttributes.TriggerSampleNumber, t0Idx, true); sea.SyncExecute(); sea = new SetEventAttribute(this); sea.SetValue(AttributeTypes.ArmAndEventAttributes.TotalSamplesRecorded, railIdx - 1, true); sea.SyncExecute(); } /// /// scans for when a rail starts and a possible T0 as defined by a local max peak or local min trough /// returns true if rail is found, however note that a railindex of T0 means that the data is starting /// off railed and never recorded. /// railed data must be railed for a certain number of samples to be railed /// (defined by T0_REQUIRED_CONSECUTIVE_RAILS) /// makes use of FindRail to find the rail, and then FindT0 to find the t0, so this /// is an aggregator of those functions /// /// channel to scan on /// index of where railed data starts /// index of max/min local peak/trough /// protected bool FindRailAndT0(int channel, out ulong railIndex, out ulong T0Index, SliceServiceAsyncInfo info) { info.NewData(DFConstantsAndEnums.T0CorrectionStatus.ScanningForPowerLoss); railIndex = 0; T0Index = 0; var start = 0UL; var end = Convert.ToUInt64(MaxMemory()); var samples = GetSamples(channel, start, start + T0_SAMPLING_SIZE); //if no samples were retrieved then there are other issues preventing getting data ... //(like maybe corruption or other missing event data) if (0 == samples.Length) { return false; } //first check to see that we don't start with railed data, if we do //we can just stop right away if (FindRailStart(samples, out var railStart, out _)) { if (railStart == 0) { return false; } else { railIndex = railStart; } start += T0_SAMPLING_SIZE; } while (0 == railIndex) { //we'll search binary from start to end, so find where that is var delta = (end - start) / 2; var segment = start + delta; //we'll be requesting T0_SAMPLING_SIZE samples, so try to put //our midpoint in the middle of that number of samples as well if (segment >= T0_SAMPLING_SIZE) { segment -= T0_SAMPLING_SIZE / 2; } samples = GetSamples(channel, segment, segment + T0_SAMPLING_SIZE); //if there are 0 samples then there is nothing we can probe ... var allRailed = false; if (0 == samples.Length) { end = segment; allRailed = true; } else { if (FindRailStart(samples, out railStart, out allRailed)) { //if we found the start of railed data AND we all samples aren't railed //then we did actually find the start of railed data, so record it and stop looking for it if (!allRailed) { railIndex = segment + railStart; break; } } } if (end - start <= T0_SAMPLING_SIZE) { //didn't find the rail and we searched all the samples in this block and those //are all the samples were we could find it... return false; } else { if (allRailed) { //if all the data was railed, then the start of railed data is before our current point //move the end of where we are looking accordingly //now the segment is T0_SAMPLING_SIZE # of samples, so we could slide this even further end = segment; } else { //if we didn't find the start of railed data and it's not all railed, that //means the start of railed data must happen after our current point //adjust the start point where we are looking accordingly start = segment + T0_SAMPLING_SIZE; } } } //if our rail index > 0, then it's possible there's a T0 we can find via max/min peak/trough if (railIndex > 0) { APILogger.Log($"{SerialNumber} appears to rail at {railIndex}"); info.NewData(DFConstantsAndEnums.T0CorrectionStatus.ScanningForPeaksAndTroughs); T0Index = FindT0(channel, railIndex); } return railIndex != 0; } private const int FIND_T0_UPDATE_FREQUENCY = 10000; /// /// scans the channel data from 0 to the where rail is hit looking for peaks and troughs /// /// channel to look at on das /// the index of where railed data starts at /// index of guessed T0 private ulong FindT0(int channel, ulong railIndex) { var samples = GetSamples(channel, 0, railIndex); //if we receive no samples there is nowhere to look for the T0 ... if (0 == samples.Length) { return 0; } //we want to find the max/min peak value, so goes down after (or up on min); var min = short.MaxValue; var max = short.MinValue; var minIndex = -1; var maxIndex = -1; //go to sample -1 as we are looking for a peak or trough, so the next sample has to go up from trough or down from peak for (var i = 1; i < samples.Length - 1; i++) { if (0 == i % FIND_T0_UPDATE_FREQUENCY) { APILogger.Log($"{SerialNumber} : Find T0 : {100 * i / samples.Length:F0}%"); } var adc = samples[i]; if (adc < min) { //a local bottom found, we are < min and the next is greater if (samples[i + 1] > adc) { min = adc; minIndex = i; } } if (adc > max) { if (samples[i + 1] < adc) { //a local peak found, we are > max and the next is lesser max = adc; maxIndex = i; } } } APILogger.Log($"{SerialNumber} has min:{min}@{minIndex} and max:{max}@{maxIndex}"); //it could be a trough or a peak, find which has a larger difference and go with it if (Math.Abs(min) > max) { return (ulong)minIndex; } return (ulong)maxIndex; } /// /// attempts to find the start of railed data /// /// samples to check for rails in /// index of where railed data starts (relative to the samples input) /// all data is railed from the start (relative to samples input) /// true if railed data was found private bool FindRailStart(short[] samples, out ulong railStart, out bool startsRailed) { //we require REQUIRED_CONS_RAILS, so only go up to length - required number for (var i = 0; i < samples.Length - T0_REQUIRED_CONSECUTIVE_RAILS; i++) { //if we find a railed sample, then check the next x samples are also railed if (IsRailed(samples[i])) { var isValid = true; for (var consecutive = 0; consecutive < T0_REQUIRED_CONSECUTIVE_RAILS; consecutive++) { if (!IsRailed(samples[i + consecutive])) { //for an optimization we could jump to i+consecutive +1 since we know the next possible //start of rail is there ... isValid = false; break; } } if (isValid) { //we found our required number of samples, so set the index and return railStart = (ulong)i; startsRailed = 0 == railStart; return true; } } } //we didn't find the start of railed data railStart = 0UL; startsRailed = false; return false; } /// /// tests whether a sample is railed /// /// /// private bool IsRailed(short sample) { return sample == short.MinValue || sample == short.MaxValue; } /// /// how many samples to grab when probing dataset /// we want a number that is around the max a DAS will return, /// however because of page boundaries this we still have no /// idea how many file operations will hit on the DAS /// private const int T0_SAMPLING_SIZE = 900; /// /// when detecting railed data data must be railed for atleast this /// number of consecutive samples to avoid situations where a signal /// is bouncing off of a rail or is on the rail but has some noise /// and isn't in fact railed /// private const int T0_REQUIRED_CONSECUTIVE_RAILS = 5; private const int T0_LOGDOWNLOAD_CUTOFF = 100000; private const ulong T0_LOGDOWNLOAD_FREQUENCY_PERCENT = 5UL; /// /// returns all the samples for device between start and end /// /// channel to query /// start sample index /// last sample index to include /// protected short[] GetSamples(int channel, ulong start, ulong end) { var numNeeded = end - start; var newData = new List(); var dr = new DownloadRequest(); dr.DASChannelNumber = DownloadRequest.ALL_CHANNELS; dr.EventNumber = 0; dr.StartSample = start; dr.EndSample = end; SetWhatToDownload(dr, false); var waitHandle = new ManualResetEvent(false); ulong count = 0; var lastUpdate = 0UL; Download((ServiceCallbackData data) => { switch (data.Status) { case ServiceCallbackData.CallbackStatus.Success: waitHandle.Set(); break; case ServiceCallbackData.CallbackStatus.NewData: foreach (var dataSample in data.DataSamples) { newData.AddRange(dataSample.Data[channel]); count += (ulong)dataSample.Data[channel].Length; if (numNeeded > T0_LOGDOWNLOAD_CUTOFF) { var percent = 100 * count / numNeeded; if ((percent - lastUpdate) > T0_LOGDOWNLOAD_FREQUENCY_PERCENT) { lastUpdate = percent; APILogger.Log($"{SerialNumber} : Downloading: {percent}%"); } } } break; case ServiceCallbackData.CallbackStatus.Failure: waitHandle.Set(); break; } }, this); waitHandle.WaitOne(); return newData.ToArray(); } void IDownloadActions.SetEventInfo(int eventIndex, string id, Guid guid, ulong totalSamples, ulong[] triggerSamples, ulong startRecordSample, uint eventHasDownloaded, ServiceCallback callback, object userData) { var info = new SliceSetEventInfoAsync(callback, userData, eventIndex, startRecordSample, totalSamples, triggerSamples, guid, id, eventHasDownloaded); LaunchAsyncWorker("Slice.SetEventInfo", new WaitCallback(AsyncSetEventInfo), info); } private void AsyncSetEventInfo(object asyncInfo) { var info = asyncInfo as SliceSetEventInfoAsync; try { if (!string.IsNullOrEmpty(info.Id)) { var sea = new SetEventAttribute(this); sea.EventNumber = Convert.ToUInt16(info.EventIndex); sea.SetValue(AttributeTypes.ArmAndEventAttributes.Name, info.Id.ToCharArray(), true); sea.SyncExecute(); } if (Guid.Empty != info.Guid) { var sea = new SetEventAttribute(this); sea.SetValue(AttributeTypes.ArmAndEventAttributes.EventGuid, info.Guid.ToString(), true); sea.EventNumber = Convert.ToUInt16(info.EventIndex); sea.SyncExecute(); } if (ulong.MaxValue != info.StartRecordSample) { var sea = new SetEventAttribute(this); sea.EventNumber = Convert.ToUInt16(info.EventIndex); sea.SetValue(AttributeTypes.ArmAndEventAttributes.StartRecordSampleNumber, info.StartRecordSample, true); sea.SyncExecute(); } if (ulong.MaxValue != info.TotalSamples) { var sea = new SetEventAttribute(this); sea.EventNumber = Convert.ToUInt16(info.EventIndex); sea.SetValue(AttributeTypes.ArmAndEventAttributes.TotalSamplesRecorded, info.TotalSamples, true); sea.SyncExecute(); } if (null != info.TriggerSamples) { var sea = new SetEventAttribute(this); sea.EventNumber = Convert.ToUInt16(info.EventIndex); sea.SetValue(AttributeTypes.ArmAndEventAttributes.TriggerSampleNumber, info.TriggerSamples[0], true); sea.SyncExecute(); } if (uint.MaxValue != info.EventHasDownloaded) { var sea = new SetEventAttribute(this); sea.EventNumber = Convert.ToUInt16(info.EventIndex); sea.SetValue(AttributeTypes.ArmAndEventAttributes.EventHasBeenDownloaded, info.EventHasDownloaded, true); sea.SyncExecute(); } } catch (Exception ex) { info.Error("Failed to set event info", ex); return; } info.Success(); } public virtual long MaxMemory() { if (null == DASInfo || 0 == DASInfo.NumberOfBytesPerSampleClock) { return 0L; } return (long)(DASInfo.MaxEventStorageSpaceInBytes / DASInfo.NumberOfBytesPerSampleClock); } public virtual uint MaxSampleRate(int numberOfConfiguredChannels) { int modules = DASInfo.Modules.Length; return modules > 0 ? Convert.ToUInt32(120000 / modules) : 120000; } public virtual uint MinSampleRate() { return 5; } public virtual uint MaxAAFilterRate() { return MaxSampleRate(0) / 5; } protected class SliceDownloadState : SliceServiceAsyncInfo { public IDownloadRequest Request; public ulong SamplesDownloaded; // how many samples have we downloaded so far public QueryEventDataBase DownloadCommand; public SliceDownloadState(ServiceCallback cb, object cbObj, IDownloadRequest _Request) : base(cb, cbObj) { Request = _Request; SamplesDownloaded = 0; DownloadCommand = null; } } protected class SliceUARTDownloadState : SliceServiceAsyncInfo { public IUARTDownloadRequest Request; public ulong BytesDownloaded; // how many bytes have we downloaded so far public QueryUARTEventData DownloadCommand; public SliceUARTDownloadState(ServiceCallback cb, object cbObj, IUARTDownloadRequest _request) : base(cb, cbObj) { Request = _request; BytesDownloaded = 0; DownloadCommand = null; } } public virtual void Download(ServiceCallback callback, object userData) { if (!Connected) { // "Slice.Download: Not currently connected" throw new Exception(Strings.Slice_Download_Err1); } if (WhatToDownload.SamplesToSkip < 2) { // no sub-sampling var state = new SliceDownloadState(callback, userData, WhatToDownload); ThreadPool.QueueUserWorkItem(ExtraDownloadStart, state); } else { var state = new SliceDownloadState(callback, userData, WhatToDownload); LaunchAsyncWorker("Slice.Download", DownloadSubSampled, state); } } protected virtual void ExtraDownloadStart(object obj) { try { DownloadEventStartCmd(obj as SliceDownloadState); } catch (Exception ex) { APILogger.Log("MessageBox", Strings.SLICEDownloadExtraDownloadStartError, ex); } } protected virtual QueryEventDataBase GetQueryEventData() { return new QueryEventDataBase(this, AbstractCommandBase.Default_IO_Timeout); } protected virtual void DownloadEventStartCmd(SliceDownloadState state) { try { if (state.SamplesDownloaded >= state.Request.EndSample - state.Request.StartSample + 1) return; state.DownloadCommand = GetQueryEventData(); state.DownloadCommand.LogCommands = false; state.DownloadCommand.EventNumber = state.Request.EventNumber; state.DownloadCommand.Channel = state.Request.DASChannelNumber; if (state.Request.DASChannelNumber == DownloadRequest.ALL_CHANNELS) { state.DownloadCommand.ChannelsDownloaded = IsTOM() ? 9 : EventInfo.Events[state.Request.EventNumber].Modules.Sum(module => module.NumberOfChannels()); } state.DownloadCommand.LastSample = state.Request.EndSample; // Ask for the next batch of samples state.DownloadCommand.FirstSample = state.Request.StartSample + state.SamplesDownloaded; state.DownloadCommand.Execute(DownloadEventCallback, state); } catch (CanceledException) { state.Cancel(); } catch (Exception ex) { state.Error(ex.Message, ex); } } protected CommandReceiveAction DownloadEventCallback(ICommandReport report) { var state = report.CallbackObject as SliceDownloadState; try { if (report.Status == CommandStatus.Failure) { // no, it didn't work out // "Slice.Download: BAD DATA" state.Error(Strings.Slice_DownloadEventCallback_Err1); return CommandReceiveAction.StopReceiving; } if (report.Status == CommandStatus.Canceled) { // user wants us to bail out state.Cancel(); return CommandReceiveAction.StopReceiving; } var rep = report as QueryEventDataReport; // OK, we have our data block state.SamplesDownloaded += (ulong)state.DownloadCommand.Count; var channelsToUse = IsTOM() ? 16 : state.DownloadCommand.ChannelsDownloaded; var newData = new short[channelsToUse][]; for (var channelIdx = 0; channelIdx < state.DownloadCommand.ChannelsDownloaded; channelIdx++) { newData[channelIdx] = rep.Data[channelIdx].ToArray(); } for (var channelIdx = state.DownloadCommand.ChannelsDownloaded; channelIdx < channelsToUse; channelIdx++) { newData[channelIdx] = rep.Data[0].ToArray();//Hack alert: In the case of a TOM, we only request 9 channels, but need to return 16 total } state.NewData(newData, 0, ulong.MinValue, ulong.MinValue); double ratio = Math.Min(1.0, state.SamplesDownloaded / (double)(state.Request.EndSample - state.Request.StartSample + 1)); state.Progress((int)(ratio * 100.0)); // if we have it all we're done if (state.SamplesDownloaded < (state.Request.EndSample - state.Request.StartSample + 1)) { // otherwise, update parameters and call again DownloadEventStartCmd(state); } else { var config = GetConfigAttributes(this);//new ConfigAttributes(this); config.SetEventDownloaded(state.Request.EventNumber, 1); state.Success(); } } catch (CanceledException) { state.Cancel(); } catch (Exception ex) { state.Error(ex.Message, ex); } return CommandReceiveAction.StopReceiving; } private void DownloadSubSampled(object asyncInfo) { var state = asyncInfo as SliceDownloadState; try { var NumberOfChannels = EventInfo.Events[WhatToDownload.EventNumber].Modules.Sum(module => module.NumberOfChannels()); // these two are per channel of course ulong TotalNumberOfSamples = WhatToDownload.EndSample - WhatToDownload.StartSample + 1; ulong NumberOfSubSamples = TotalNumberOfSamples / WhatToDownload.SamplesToSkip; // allocate array to return to user var SubSampledData = new short[NumberOfChannels][]; for (var idx = 0; idx < NumberOfChannels; idx++) { SubSampledData[idx] = new short[NumberOfSubSamples]; } // loop thru and get our samples var SubSampleIdx = 0; for (var sampleIdx = WhatToDownload.StartSample; sampleIdx < WhatToDownload.EndSample && (ulong)SubSampleIdx < NumberOfSubSamples; sampleIdx += WhatToDownload.SamplesToSkip) { var newData = GetSingleSample(WhatToDownload.EventNumber, NumberOfChannels, sampleIdx); // loop thru the channels and get data from cmd for (var channelIdx = 0; channelIdx < NumberOfChannels; channelIdx++) { SubSampledData[channelIdx][SubSampleIdx] = newData[channelIdx][0]; } SubSampleIdx++; // calculate where we are double ratio = Math.Min(1.0, SubSampleIdx / (double)NumberOfSubSamples); state.Progress((int)(ratio * 100.0)); Application.DoEvents(); } // send data to user state.NewData(SubSampledData, 0, ulong.MinValue, ulong.MinValue); state.Success(); } catch (CanceledException) { state.Cancel(); } catch (Exception ex) { state.Error(ex.Message, ex); } } private short[][] GetSingleSample(ushort eventNumber, int numberOfChannels, ulong sampleNumber) { // create cmd //var DownloadCommand = new QueryEventData(this); var DownloadCommand = GetQueryEventData(); // we don't want to log this DownloadCommand.LogCommands = false; // the event number to retrive DownloadCommand.EventNumber = eventNumber; // the channels to get DownloadCommand.Channel = DownloadRequest.ALL_CHANNELS; DownloadCommand.ChannelsDownloaded = numberOfChannels; // the sample to get DownloadCommand.FirstSample = sampleNumber; DownloadCommand.LastSample = sampleNumber; // call HW to get samples DownloadCommand.SyncExecute(); // allocate array to return to user var newData = new short[numberOfChannels][]; // loop thru the channels and get data from cmd for (var channelIdx = 0; channelIdx < numberOfChannels; channelIdx++) { DownloadCommand.GetChannelData(channelIdx, out newData[channelIdx]); } return newData; } #endregion #region Query download public virtual bool CheckAAF(float rate) { return true; } public class QueryDownloadProgress { private SliceServiceAsyncInfo info; private readonly int TotalNumberOfSteps; private int CurrentStep; public QueryDownloadProgress(SliceServiceAsyncInfo _info, int steps, int initialStep) { info = _info; TotalNumberOfSteps = steps; CurrentStep = initialStep; report(); } private void report() { if (CurrentStep <= TotalNumberOfSteps && TotalNumberOfSteps > 0) { var step = (int)(CurrentStep / (double)TotalNumberOfSteps * 100.0); info.Progress(step); } else { info.Progress(100); } } public void Step() { CurrentStep++; report(); } } internal class QueryDownloadAsyncInfo : SliceServiceAsyncInfo { public int EventIndex { get; set; } public QueryDownloadAsyncInfo(ServiceCallback callback, object userData, int eventIndex) : base(callback, userData) { EventIndex = eventIndex; } } void IDownloadActions.QueryDownload(ServiceCallback callback, object userData, int eventIndex, TDASServiceSetupInfo setupInfo) { var info = new QueryDownloadAsyncInfo(callback, userData, eventIndex); LaunchAsyncWorker("Slice.QueryDownload", new WaitCallback(AsyncQueryDownload), info); } protected void GetEventTimeStampInfo(int eventIdx, out uint startRecordTimestampSec, out uint triggerTimestampSec, out uint startRecordTimestampNanoSec, out uint triggerTimestampNanoSec, out bool pTPMasterSync) { startRecordTimestampSec = 0; triggerTimestampSec = 0; startRecordTimestampNanoSec = 0; triggerTimestampNanoSec = 0; pTPMasterSync = false; if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.PTPTimestamp)) { try { var qea = new QueryEventAttribute(this); qea.EventNumber = Convert.ToUInt16(eventIdx); qea.Key = AttributeTypes.ArmAndEventAttributes.ADCStartTimeStampSecNanoSec; qea.SyncExecute(); startRecordTimestampSec = ((uint[])qea.Value)[0]; startRecordTimestampNanoSec = ((uint[])qea.Value)[1]; } catch (Exception ex) { APILogger.Log(ex); } try { var qea = new QueryEventAttribute(this); qea.EventNumber = Convert.ToUInt16(eventIdx); qea.Key = AttributeTypes.ArmAndEventAttributes.TriggerTimeStampSecNanoSec; qea.SyncExecute(); triggerTimestampSec = ((uint[])qea.Value)[0]; triggerTimestampNanoSec = ((uint[])qea.Value)[1]; } catch (Exception ex) { APILogger.Log(ex); } try { uint lockedTime, outOfSyncTime; var qea = new QueryEventAttribute(this); qea.EventNumber = Convert.ToUInt16(eventIdx); qea.Key = AttributeTypes.ArmAndEventAttributes.PtpSyncLockedTimeSec; qea.SyncExecute(); lockedTime = (uint)qea.Value; qea.EventNumber = Convert.ToUInt16(eventIdx); qea.Key = AttributeTypes.ArmAndEventAttributes.PtpOutOfSyncTimeSec; qea.SyncExecute(); outOfSyncTime = (uint)qea.Value; if (lockedTime > outOfSyncTime) { pTPMasterSync = true; } else { pTPMasterSync = false; } } catch (Exception ex) { APILogger.Log(ex); } } } protected virtual void AsyncQueryDownload(object asyncInfo) { var info = asyncInfo as QueryDownloadAsyncInfo; // since we don't know exact how many XML attributes, modules or channels that's in the // configuration, we guess on the high end. // (44 per stored config + 4 + 3 per module + 3 per channel) * number of events // equals 44 + 4 + 30 + 90 = 168 per event const int StepsPerEvent = 168; try { if (info.EventIndex < 0) { RetrieveDASBootCount(); } else { TurnOffDiagnosticsMode(); } var EventCount = new QueryTotalEventCount(this); EventCount.SyncExecute(); var config = GetConfigAttributes(this);//new ConfigAttributes(this); // 6 * modules + 44 var progress = new QueryDownloadProgress(info, StepsPerEvent * EventCount.Count, 1); // we'll also freshen up the cached event GUIDs // NOT IF WE ARE QUERYING A SPECIFIC GUID var eventGuids = new Guid[EventCount.Count]; var faultFlags = new ushort[EventCount.Count]; var faultFlagsEx = new ushort[EventCount.Count]; var armAttempts = new byte[EventCount.Count]; var extendedFaultFlags = new List(); var dlReport = new DownloadReport(); //22295 Since all SLICE6Air DAS derive from IUARTDownload, SLICE6Air Ethernet Recorders //would fall in here and create a UARTEvent if we didn't call IsSLICE6ERFirmware. An issue //(FB 23347) was opened to draw attention to possibly modifying this workaround. if ((this is IUARTDownload) && !(DFConstantsAndEnums.IsSLICE6ERFirmware(FirmwareVersion)) && (0 > info.EventIndex)) { dlReport.UARTEvents = new DownloadReport.UARTEventInfo[EventCount.Count]; for (var eventIdx = 0; eventIdx < EventCount.Count; eventIdx++) { if (info.EventIndex >= 0 && eventIdx != info.EventIndex) { continue; } var uartInfo = new QueryUARTEventInfo(this) { EventNumber = (ushort)eventIdx }; uartInfo.SyncExecute(); // the object to store it all in var uartEventInfo = new DownloadReport.UARTEventInfo() { EventNumber = uartInfo.EventNumber, DataPresent = uartInfo.DataPresent, DataDownloaded = uartInfo.DataDownloaded, TotalByteCount = uartInfo.TotalByteCount, TriggerByteCount = uartInfo.TriggerByteCount, StartTimestamp = uartInfo.StartTimestamp, EndTimestamp = uartInfo.EndTimestamp, BaudRate = uartInfo.BaudRate }; if (0 > info.EventIndex) { // store it in the object dlReport.UARTEvents[eventIdx] = uartEventInfo; } else { EventInfo.UARTEvents[eventIdx] = uartEventInfo; } } } if (0 > info.EventIndex) { dlReport.Events = new DownloadReport.EventInfo[EventCount.Count]; } for (var eventIdx = 0; eventIdx < EventCount.Count; eventIdx++) { GetEventTimeStampInfo(eventIdx, out var startRecordTimestampSec, out var triggerTimestampSec, out var startRecordTimestampNanoSec, out var triggerTimestampNanoSec, out var pTPMasterSync); if (info.EventIndex >= 0 && eventIdx != info.EventIndex) { continue; } extendedFaultFlags.Add(GetExtendedFaultFlags(eventIdx)); // Retrieved the stored configuration var storedConfigStr = config.RetrieveEventXMLConfig(eventIdx, progress, this); // I/O * n // convert it from XML to object var storedConfig = ConfigurationData.DeserializeFromString(storedConfigStr); // the object to store it all in var eventInfo = new DownloadReport.EventInfo(); // get the event level values eventInfo.Description = config.GetEventDescription(eventIdx); // I/O try { if (null != ConfigData) { eventInfo.Description = ConfigData.Description; } } catch (Exception) { } progress.Step(); eventInfo.TestID = config.GetEventID(eventIdx, this).TrimEnd(new char[] { '\0' }); ; // I/O progress.Step(); eventInfo.EventNumber = eventIdx; progress.Step(); try { //eventInfo.TestGUID = config.GetEventGuid(eventIdx); // I/O eventInfo.TestGUID = GetEventGuid(eventIdx); } catch (Exception) { eventInfo.TestGUID = Guid.NewGuid(); } try { eventInfo.FaultFlags = config.GetEventFaultFlags(eventIdx); eventInfo.FaultFlagsEx = config.GetEventFaultFlagsEx(eventIdx); } catch (Exception ex) { APILogger.Log("could not get fault flags", ex); } try { eventInfo.ArmAttempts = config.GetEventArmAttempts(eventIdx); } catch (Exception ex) { APILogger.Log("could not get arm attempts", ex); } eventGuids[eventIdx] = eventInfo.TestGUID; faultFlags[eventIdx] = eventInfo.FaultFlags; faultFlagsEx[eventIdx] = eventInfo.FaultFlagsEx; armAttempts[eventIdx] = eventInfo.ArmAttempts; eventInfo.HasBeenDownloaded = config.EventHasBeenDownloaded(eventIdx, out uint flag); // I/O progress.Step(); // figure out how many modules we have var numberOfChannels = GetEventTotalChannels(eventIdx); // I/O progress.Step(); var numberOfModules = config.CalculateNumberOfModules(storedConfig, numberOfChannels, IsTOM()); var numberOfUARTs = config.CalculateNumberOfUARTs(storedConfig); var numberOfStreamOuts = config.CalculateNumberOfStreamOuts(storedConfig); var numberOfStreamIns = config.CalculateNumberOfStreamIns(storedConfig); // make sure we have the right number of modules // make sure we have the right number of UARTs FB18363 if (numberOfModules + numberOfUARTs + numberOfStreamOuts + numberOfStreamIns != storedConfig.Modules.Length) { // "Slice.QueryDownload: The information in the recorder is corrupt" info.Error(Strings.Slice_QueryDownload_Err1); //SS: need to add code here to try to extract enough info return; } eventInfo.Modules = new DASModule[numberOfModules]; var numberOfSamples = config.GetEventTotalSamples(eventIdx); // I/O var triggerSampleNumber = config.GetEventTriggerSampleNumber(eventIdx); // I/O var eventStartRecordSampleNumber = config.GetEventStartRecordSampleNumber(eventIdx); // I/O var eventStartTime = config.GetEventStartTime(eventIdx); // I/O var levelTriggerOffsetCorrection = config.GetEventLevelTriggerT0AdjustmentSamples(eventIdx); var levelTriggerSeen = config.GetEventLevelTriggerSeen(eventIdx); var actualSampleRate = config.GetEventSamplerate(eventIdx); var stackActualSampleRate = config.GetEventStackSamplerate(eventIdx); // 17873 download should only use event attributes rather than arm attributes var factors = config.GetEventScaleFactors(eventIdx); var aafilter = config.GetEventAAFilter(eventIdx); var currentChannel = 0; //toyota boshoku -flat data issue zendesk 5702? //basically make sure we are using the event attribute ScaleFactorMvADC and not what is in the //xml for (var moduleIdx = 0; moduleIdx < numberOfModules; moduleIdx++) { // take the module from the stored config var module = (DASModule)storedConfig.Modules[moduleIdx]; module.SampleRateHz = Convert.ToUInt32(actualSampleRate); module.EmbeddedSampleRateHz = stackActualSampleRate; /// /// Amongst other things, the level trigger offset correction, if it exists, is getting applied /// to the module trigger sample numbers below. There shouldn't be any danger of this value being /// erroneously applied to outside "tweaking" since the only outside tweak should only ever happen /// if there was no trigger. Still I'm a little concerned that this value will still be invisibly /// applied to user-overridden trigger sample numbers. We could conceivably guard against this /// situation with an "original trigger sample number" concept, which we could then check against /// and not re-apply things like level trigger offset correction if the current value doesn't /// match it, implying it has been manually overridden. /// // now update the module with dynamic data module.OwningDAS = this; module.NumberOfSamples = numberOfSamples; progress.Step(); module.TriggerSampleNumbers = new ulong[1]; // only one so far var phaseShift = GetPhaseShiftSamples(Convert.ToUInt32(1 + module.ModuleArrayIndex), Convert.ToDouble(actualSampleRate), Convert.ToUInt32(aafilter), triggerSampleNumber); module.TriggerSampleNumbers[0] = triggerSampleNumber + phaseShift; eventInfo.WasTriggered = module.TriggerSampleNumbers[0] > 0; progress.Step(); module.StartRecordSampleNumber = 0; if (module.RecordingMode == DFConstantsAndEnums.RecordingMode.CircularBuffer || module.RecordingMode == DFConstantsAndEnums.RecordingMode.CircularBufferPlusUART || module.RecordingMode == DFConstantsAndEnums.RecordingMode.AutoCircularBufferMode || module.RecordingMode == DFConstantsAndEnums.RecordingMode.a16_CircularBufferAndStreamSubSampleMode || module.RecordingMode == DFConstantsAndEnums.RecordingMode.RAMActive || module.RecordingMode == DFConstantsAndEnums.RecordingMode.MultipleEventRAMActive) { ulong preTriggerSamples = Convert.ToUInt64(System.Math.Abs(module.PreTriggerSeconds * module.SampleRateHz + 1D)); if (preTriggerSamples < module.TriggerSampleNumbers[0]) { module.StartRecordSampleNumber = module.TriggerSampleNumbers[0] - preTriggerSamples; } } else if (module.RecordingMode == DFConstantsAndEnums.RecordingMode.AutoActiveMode || module.RecordingMode == DFConstantsAndEnums.RecordingMode.AerospaceWithMotion) { ulong preTriggerSamples = Convert.ToUInt64(System.Math.Abs(module.PreTriggerSeconds * module.SampleRateHz + 1D)); if (preTriggerSamples < module.TriggerSampleNumbers[0]) { module.StartRecordSampleNumber = eventStartRecordSampleNumber; } } #region Get_PTP_Event_Timestamp module.StartRecordTimestampSec = startRecordTimestampSec; module.TriggerTimestampSec = triggerTimestampSec; module.StartRecordTimestampNanoSec = startRecordTimestampNanoSec; module.TriggerTimestampNanoSec = triggerTimestampNanoSec; module.PTPMasterSync = pTPMasterSync; #endregion #region Slice6TiltSensor var tiltSensorDataPre = new short[3]; module.TiltSensorAxisXDegreesPre = double.NaN; module.TiltSensorAxisYDegreesPre = double.NaN; module.TiltSensorAxisZDegreesPre = double.NaN; module.TiltSensorAxisXDegreesPost = double.NaN; module.TiltSensorAxisYDegreesPost = double.NaN; module.TiltSensorAxisZDegreesPost = double.NaN; if (IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands.InSliceTiltSensorADCPre)) { // Get the saved values from the final pre - test query(if any) try { var query = new QueryEventAttribute(this) { Key = AttributeTypes.ArmAndEventAttributes.tiltSensorPreEventADC }; query.SyncExecute(); tiltSensorDataPre = query.Value as short[]; } catch (System.Exception ex) { APILogger.Log(ex); } var tiltSensorCals = new double[18]; for (int tiltCalKeyOffset = 0; tiltCalKeyOffset < 18; tiltCalKeyOffset++) { var qSA_BS6 = new QuerySystemAttribute_BridgeSlice6(this); qSA_BS6.DeviceID = 1; qSA_BS6.Key = (AttributeTypes.SystemAttributes_BridgeSlice6)((int)AttributeTypes.SystemAttributes_BridgeSlice6.TILTSENSOR_CAL_1 + tiltCalKeyOffset); qSA_BS6.SyncExecute(); tiltSensorCals[tiltCalKeyOffset] = (float)qSA_BS6.Value; } var tiltDataEU = Test.Module.GetTiltDegreesEU(tiltSensorDataPre, tiltSensorCals, module.TiltAxes, module.AxisIgnored, new float[] { (float)module.MountOffsetAxisOne, (float)module.MountOffsetAxisTwo }); module.TiltSensorAxisXDegreesPre = tiltDataEU[0]; module.TiltSensorAxisYDegreesPre = tiltDataEU[1]; module.TiltSensorAxisZDegreesPre = tiltDataEU[2]; } #endregion progress.Step(); // update the channel info for (var channelIdx = 0; channelIdx < module.Channels.Length; channelIdx++) { var channel = (DASChannel)module.Channels[channelIdx]; channel.OwningModule = module; channel.EventStartTime = eventStartTime; if (channel is AnalogInputDASChannel analog) { if (analog.ScalefactorMilliVoltsPerADC != factors[currentChannel]) { //2019-04-11 toyota boshoku flatline data issue APILogger.Log( $"{SerialNumber}:{analog.Number} has a mismatched XML scale factor vs attribute scale factor ({analog.ScalefactorMilliVoltsPerADC} vs {factors[currentChannel]})"); analog.ScalefactorMilliVoltsPerADC = factors[currentChannel]; } } // CGO // This is a hacked change for now to support 00G8 and prior firmware // in the 1.04 release of SLICEWare. This and the other CGO commented // block of code need more general cleanup before 1.05. if (null == levelTriggerOffsetCorrection) { channel.LevelTriggerT0AdjustmentSamples = 0; } else if (currentChannel < levelTriggerOffsetCorrection.Length) { channel.LevelTriggerT0AdjustmentSamples = levelTriggerOffsetCorrection[currentChannel]; } else { channel.LevelTriggerT0AdjustmentSamples = 0; } if (null == levelTriggerSeen) { channel.LevelTriggerSeen = false; } else if (currentChannel < levelTriggerSeen.Length) { channel.LevelTriggerSeen = levelTriggerSeen[currentChannel]; } else { channel.LevelTriggerSeen = false; } currentChannel++; progress.Step(); } // store it in the object eventInfo.Modules[moduleIdx] = module; } if (0 > info.EventIndex) { // store it in the object dlReport.Events[eventIdx] = eventInfo; } else { EventInfo.Events[eventIdx] = eventInfo; } } // now assigned the data to the public property if (0 > info.EventIndex) { SetEventInfo(dlReport); } SetEventFaultFlags(faultFlags); ((IDownload)this)?.SetExtendedFaultFlags(extendedFaultFlags.ToArray()); SetEventGuids(eventGuids); SetEventArmAttemps(armAttempts); info.Success(); } catch (CanceledException) { info.Cancel(); } catch (Exception ex) { info.Error(ex.Message, ex); } } #endregion #region Query downloaded status void IDownloadActions.QueryDownloadedStatus(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.QueryDownloadedStatus", new WaitCallback(AsyncQueryDownloadedStatus), info); } private void AsyncQueryDownloadedStatus(object asyncInfo) { var info = asyncInfo as SliceServiceAsyncInfo; try { var EventCount = new QueryTotalEventCount(this); EventCount.SyncExecute(); var config = GetConfigAttributes(this);//new ConfigAttributes(this); var progress = new QueryDownloadProgress(info, EventCount.Count + 1, 1); var eventDownloadedStatus = new bool[EventCount.Count]; var eventGuids = new Guid[EventCount.Count]; for (int eventIdx = 0; eventIdx < EventCount.Count; eventIdx++) { eventDownloadedStatus[eventIdx] = config.EventHasBeenDownloaded(eventIdx, out uint flag); // I/O progress.Step(); try { eventGuids[eventIdx] = GetEventGuid(eventIdx); } catch (Exception) { eventGuids[eventIdx] = Guid.NewGuid(); } } // now assigned the data to the public property SetEventDownloadStatus(eventDownloadedStatus); SetEventGuids(eventGuids); info.Success(); } catch (CanceledException) { info.Cancel(); } catch (Exception ex) { info.Error(ex.Message, ex); } } #endregion #region Set trigger sample numbers void IDownloadActions.SetTriggerSampleNumbers(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.SetTriggerSampleNumbers", new WaitCallback(AsyncSetTriggerSampleNumbers), info); } private void AsyncSetTriggerSampleNumbers(object asyncInfo) { var info = asyncInfo as SliceServiceAsyncInfo; try { if (EventInfo == null || EventInfo.Events == null || EventInfo.Events.Length == 0 || EventInfo.Events[0].Modules == null || EventInfo.Events[0].Modules.Length == 0) { info.Error("SetTriggerSampleNumbers: EventInfo, Events or Modules are null or empty"); return; } var config = GetConfigAttributes(this);//new ConfigAttributes(this); for (int eventIdx = 0; eventIdx < EventInfo.Events.Length; eventIdx++) { if (EventInfo.Events[eventIdx].Modules != null && EventInfo.Events[eventIdx].Modules.Length > 0 && EventInfo.Events[eventIdx].Modules[0].TriggerSampleNumbers.Length > 0) { // we only support 1 trigger at this point config.SetEventTriggerSampleNumber(eventIdx, EventInfo.Events[eventIdx].Modules[0].TriggerSampleNumbers[0]); } } info.Success(); } catch (CanceledException) { info.Cancel(); } catch (Exception ex) { info.Error(ex.Message, ex); } } #endregion #region Set downloaded void IDownloadActions.SetDownloaded(ServiceCallback callback, object userData) { var info = new SliceServiceAsyncInfo(callback, userData); LaunchAsyncWorker("Slice.SetDownloaded", new WaitCallback(AsyncSetDownloaded), info); } private void AsyncSetDownloaded(object asyncInfo) { var info = asyncInfo as SliceServiceAsyncInfo; try { if (EventInfo == null || EventInfo.Events == null || EventInfo.Events.Length == 0 || EventInfo.Events[0].Modules == null || EventInfo.Events[0].Modules.Length == 0) { info.Error("SetDownloaded: EventInfo, Events or Modules are null or empty"); return; } var config = GetConfigAttributes(this); for (var eventIdx = 0; eventIdx < EventInfo.Events.Length; eventIdx++) { if (EventInfo.Events[eventIdx].Modules != null && EventInfo.Events[eventIdx].Modules.Length > 0 && EventInfo.Events[eventIdx].Modules[0].TriggerSampleNumbers.Length > 0) { config.SetEventDownloaded(eventIdx, 1); } } info.Success(); } catch (CanceledException) { info.Cancel(); } catch (Exception ex) { info.Error(ex.Message, ex); } } #endregion } }