using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using DTS.Common.Enums; using DTS.Common.Enums.DASFactory; using DTS.Common.Enums.Hardware; using DTS.Common.ICommunication; using DTS.Common.Interface.Connection; using DTS.Common.Interface.DASFactory; using DTS.Common.Utilities.Logging; using DTS.DASLib.Command.TDAS; namespace DTS.DASLib.Service { public partial class TDAS : Communication, IDASCommunication, IConfigurationActions, IDiagnosticsActions, ITriggerCheckActions, IRealTimeActions, IArmActions, IDownloadActions where T : IConnection, new() { #region Real time public bool ControlsDAQ() { return false; } public bool SupportsHardwareInputCheck() { return false; } public bool InvertStart { get { return false; } set { if (value) { throw new NotSupportedException("TDAS does not support inverted start"); } } } public bool InvertTrigger { get { return false; } set { if (value) { throw new NotSupportedException("TDAS does not support inverted trigger"); } } } public bool SupportsTriggerInversion() => HardwareConstants.SupportsTriggerInversion(GetHardwareType(), ProtocolVersion); public bool SupportsStartInversion() => HardwareConstants.SupportsStartInversion(GetHardwareType(), ProtocolVersion); public bool SupportsRealtime() { if (DFConstantsAndEnums.ModuleType.G5Analog == DASInfo.Modules[0].TypeOfModule) { return true; } else { foreach (var module in DASInfo.Modules) { switch (module.TypeOfModule) { case DFConstantsAndEnums.ModuleType.ProDIM: case DFConstantsAndEnums.ModuleType.ProSIM: return true; } } } return false; } public bool IgnoreShortedStart { get { return false; } set { if (value) { throw new NotSupportedException("TDAS does not support ignore shorted start"); } } } public bool IgnoreShortedTrigger { get { return false; } set { if (value) { throw new NotSupportedException("TDAS does not support ignore shorted trigger"); } } } public bool SupportsAutoArm() { return false; } public bool SupportsLevelTrigger() { return false; } public bool SupportsMultipleEvents() { return false; } public bool SupportsMultipleSampleRealtime() { return false; } public bool SupportsMultiChannelRealtime() { return false; } private class RealTimeAsyncPacket { public TDASServiceAsyncInfo info { get; set; } public System.Threading.Timer timer { get; set; } public int samplesPerSecond { get; set; } public int millisecBetweenSamples { get; set; } public bool realtimeMultipleSamplesEnabled { get; set; } public int ModuleIndex { get; set; } public System.Threading.ManualResetEvent StopEvent { get; set; } public bool CareAboutSampleNumber { get; set; } public int minCallbackUpdateTimeMs { get; set; } } void IRealTimeActions.RealTimePolling(ServiceCallback callback, object userData, ManualResetEvent mre, byte[] channels) { var packet = new RealTimeAsyncPacket(); packet.info = new TDASServiceAsyncInfo(callback, userData); packet.StopEvent = mre; if (IsG5()) { packet.millisecBetweenSamples = 50; packet.samplesPerSecond = 1000; packet.CareAboutSampleNumber = false; LaunchAsyncWorker("TDAS.Realtime", new WaitCallback(AsyncRealTime), packet); } else { LaunchAsyncWorker("TDAS.RealtimePolling", new WaitCallback(AsyncRealTimePolling), packet); } } void IRealTimeActions.RealTime(int samplesPerSecond, int millisecBetweenSamples, ServiceCallback callback, object userData, bool realtimeMultipleSamplesEnabled, int moduleIndex, ManualResetEvent stopEvent, byte[] channels, double aaf, int minCallbackUpdateTimeMs, bool UseUDPStreaming, string hostIPAddress) { var packet = new RealTimeAsyncPacket { info = new TDASServiceAsyncInfo(callback, userData), millisecBetweenSamples = millisecBetweenSamples, samplesPerSecond = samplesPerSecond, realtimeMultipleSamplesEnabled = realtimeMultipleSamplesEnabled, ModuleIndex = moduleIndex, CareAboutSampleNumber = true, StopEvent = stopEvent, minCallbackUpdateTimeMs = minCallbackUpdateTimeMs }; LaunchAsyncWorker("TDAS.RealTime", new WaitCallback(AsyncRealTime), packet); } void IRealTimeActions.RealTimeTiltPolling(ServiceCallback callback, object userData, ManualResetEvent stopEvent) { var info = new TDASServiceAsyncInfo(callback, userData); info.Success(); } public string UDPStreamAddress { get; } void IRealTimeActions.SetUDPStreamProfile(ServiceCallback callback, object userData, UDPStreamProfile streamProfile, string udpAddress, ushort timeChannelId, ushort dataChannelId, uint[] tmnsConfig, ushort irigTimeDataPacketIntervalMs) { var info = new TDASServiceAsyncInfo(callback, userData); info.Success(); } void IRealTimeActions.GetUDPStreamProfile(ServiceCallback callback, object userData) { var info = new TDASServiceAsyncInfo(callback, userData); info.Success(); } private object CallbackLock = new object(); private DateTime lastUpdate = DateTime.MinValue; private const int MIN_CALLBACK_UPDATE_TIME = 10; private List _timeStamps = new List(); private List _sequenceNumbers = new List(); private List _sampleNumbers = new List(); private List _dataSamples = new List(); private const int BufferSize = 4096; private readonly byte[] _buffer = new byte[BufferSize]; private RealTimeAsyncPacket _packet; private const int NumChannels = 8; private const int StartChannel = 0; private void ProcessData(IAsyncResult ar) { lock (CallbackLock) { var bytes = sock.EndReceive(ar); if (0 == bytes) { return; } var s = Encoding.ASCII.GetString(_buffer, 0, bytes); s = s.Replace("\r\n", Convert.ToChar(0xBF).ToString()); var lines = s.Split(Convert.ToChar(0xBF)); foreach (var line in lines) { var rtData = new short[NumChannels][]; if (!line.Contains("MD")) { continue; } var tokens = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length < 8) { continue; } //0 = command, 1 = time, 2 = channel 0, etc for (var i = 0; i < NumChannels; i++) { rtData[i] = new short[1]; rtData[i][0] = short.MaxValue; if (i >= StartChannel) { var index = i - StartChannel + 2; if (index < tokens.Length) { short.TryParse(tokens[i - StartChannel + 2], out rtData[i][0]); } } } _dataSamples.Add(rtData); } //TDAS reporting interval is slow enough, don't restrict it further! //if (DateTime.Now.Subtract(lastUpdate).TotalMilliseconds > MIN_CALLBACK_UPDATE_TIME) { _packet.info.NewData(_dataSamples, _sampleNumbers, _timeStamps, _sequenceNumbers); _dataSamples.Clear(); //lastUpdate = DateTime.Now; } } sock.BeginReceive(_buffer, 0, BufferSize, new AsyncCallback(ProcessData), null); } /// /// only used with RACK, /// is a polling (sampleaverage/single sample) realtime mode /// /// private void AsyncRealTimePolling(object asyncInfo) { var packet = asyncInfo as RealTimeAsyncPacket; //List sampleNumbers = new List(); //List data = new List(); const int numTDASDIMChannels = 16; ulong sampleNumber = 0; var numChannels = ConfigData.Modules.Sum(module => module.NumberOfChannels()); while (!(this as DTS.Common.Interface.DASFactory.ICommunication).IsCanceled() && !packet.StopEvent.WaitOne(0)) { var curChannel = 0; //first array is channels, second array are samples var rtData = new short[numChannels][]; foreach (var module in DASInfo.Modules) { try { switch (module.TypeOfModule) { case DFConstantsAndEnums.ModuleType.EMPTYBANK: continue; case DFConstantsAndEnums.ModuleType.ProTOM: { curChannel += 16; //8squibs, 8 digitals continue; } } var ss = new SampleAverage(this) { ModuleIndex = module.ModuleArrayIndex }; ss.SyncExecute(); if (module.TypeOfModule == DFConstantsAndEnums.ModuleType.ProDIM) { //apparently the DIM for SA just returns 1.0, 0 for (int ch = 0; ch < numTDASDIMChannels; ch++) { rtData[curChannel++] = new short[] { 0 == ss.ChannelValues[ch] ? short.MinValue : short.MaxValue }; } } else { for (int ch = 0; ch < 8; ch++) { rtData[curChannel++] = new short[] { Convert.ToInt16(ss.ChannelValues[ch]) }; } } //if (IsG5() && i == 0) //{ // int digitalbits = (ss.ChannelValues[9] << 16) | ss.ChannelValues[8]; // BitVector32 bv = new BitVector32(digitalbits); // for (int digCh = 0; digCh < 16; digCh++) // { // if (bv[(1 << digCh)]) // { // rtData[32 + digCh] = new short[] {short.MinValue}; // } // else // { // rtData[32 + digCh] = new short[] {short.MaxValue}; // } // } //} } catch (Exception ex) { APILogger.Log(ex); } } packet.info.NewData(new List() { rtData }, new List(new ulong[] { sampleNumber }), new List(new ulong[] { ulong.MinValue }), new List(new ulong[] { ulong.MinValue })); Thread.Sleep(50); sampleNumber++; //if (DateTime.Now.Subtract(lastUpdate).TotalMilliseconds >= MIN_CALLBACK_UPDATE_TIME) //{ // lastUpdate = DateTime.Now; // packet.info.NewData(data, sampleNumbers); // sampleNumbers.Clear(); // data.Clear(); //} } packet.info.Success(); } /// /// indicates whether the DAS supports streaming /// 10572 implement SW side for single command streaming realtime /// public bool SupportsIndividualChannelRealtimeStreaming => false; /// /// this is used to space out the samples in a multiple sample packet of g5 realtime /// I based it on the max SPS, which is what we always send to the g5 (1k SPS) /// through empirical testing with the sig-gen, I changed to 1/978 for U5 /// private const double _g5RealtimeInterval = 1 / 978D; //private DateTime _lastRTLogTime = DateTime.MinValue; //private int _samplesProcessed = 0; /// /// The worker for realtime service. /// /// A RealTimeAsyncPacket for responses private void AsyncRealTime(object asyncInfo) { var packet = asyncInfo as RealTimeAsyncPacket; _packet = packet; //tdas devices for realtime have both a sampling rate and a reporting rate //ultimately the reporting rate will drive what we see in realtime //the user has requested a specific realtime rate, however right now //we just force the rate on TDAS equipment, this means we have to go and //adjust our sample numbers as if we were running at the requested rate. double expectedSampleRate = packet.samplesPerSecond; bool bG5 = IsG5(); try { if (!SupportsRealtime()) { packet.info.Success(); return; } FlushTimeoutMilliSec = 0; var sampleNumbers = new List(); var timeStamps = new List(); var sequenceNumbers = new List(); var data = new List(); var dtSTart = DateTime.Now; var sampleNumberCarryover = 0; if (bG5) { var mdx = new G5MonitorData(this); mdx.SyncExecute(); } else { Command.TDAS.ProMonitorData md = new Command.TDAS.ProMonitorData(this, DASInfo.Modules[packet.ModuleIndex].TypeOfModule == DFConstantsAndEnums.ModuleType.ProSIM); md.ModuleIndex = packet.ModuleIndex; md.SyncExecute(); } // Then consume the constant responses. Be sure to turn off logging for performance. Command.TDAS.MonitorDataNextSampleBase.MonitorType mt; if (IsG5()) { mt = Command.TDAS.MonitorDataNextSampleBase.MonitorType.G5; } else { if (DASInfo.Modules[packet.ModuleIndex].TypeOfModule == DFConstantsAndEnums.ModuleType.ProDIM) { mt = MonitorDataNextSampleBase.MonitorType.DIM; } else { mt = MonitorDataNextSampleBase.MonitorType.Pro; } } Command.TDAS.MonitorDataNextSampleBase next = new Command.TDAS.MonitorDataNextSampleBase(this, mt); next.LogCommands = false; while (true && !(this as DTS.Common.Interface.DASFactory.ICommunication).IsCanceled() && !packet.StopEvent.WaitOne(1)) { Thread.Sleep(50); sampleNumbers.Clear(); timeStamps.Clear(); data.Clear(); // get the next response(s) next.SyncExecute(); var moreData = next.RTData; var timeSamples = next.Times; // Maybe we're ahead of the das. if (0 == moreData.Count()) { continue; } if (bG5) { for (int currentSample = 0; currentSample < moreData.Count; currentSample++) { short digitalBits = moreData[currentSample][32]; short[] newData = new short[48]; Array.Copy(moreData[currentSample], newData, 32); System.Collections.Specialized.BitVector32 bv = new System.Collections.Specialized.BitVector32(Convert.ToInt32(digitalBits)); for (int iChannelIdx = 0; iChannelIdx < 16; iChannelIdx++) { //also note that the bitvector [] operator expects a bitmask, not an index ... if (bv[(1 << iChannelIdx)]) { newData[32 + iChannelIdx] = short.MinValue; } else { newData[32 + iChannelIdx] = short.MaxValue; } } moreData[currentSample] = newData; } } else { //the service is expecting a DAS full of realtime sample data, //however with realtime on a rack, we're only going to have one module responding with data, so we need to fake data from the other modules //for now since the first SIM module is the only module we're accepting data from I can spoof data from all other channels bool bFound = false; List> largerDataArray = new List>(); for (int sample = 0; sample < moreData.Count; sample++) { bFound = false; for (int iModule = 0; iModule < DASInfo.Modules.Length; iModule++) { if (largerDataArray.Count <= sample) { largerDataArray.Add(new List()); } switch (DASInfo.Modules[iModule].TypeOfModule) { case DFConstantsAndEnums.ModuleType.EMPTYBANK://ignore break; case DFConstantsAndEnums.ModuleType.ProDIM: if (!bFound && iModule == packet.ModuleIndex) { bFound = true; foreach (var d in moreData[sample]) { System.Collections.Specialized.BitVector32 bv = new System.Collections.Specialized.BitVector32(d); short[] array = new short[16]; for (int i = 0; i < 16; i++) { if (bv[1 << i]) { if (!IsG5()) { array[i] = short.MaxValue; } else { array[i] = short.MaxValue; } } else { if (!IsG5()) { array[i] = short.MinValue; } else { array[i] = short.MinValue; } } } largerDataArray[sample].AddRange(array); } } else { largerDataArray[sample].AddRange(new short[] { short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue }); } break; case DFConstantsAndEnums.ModuleType.ProSIM: if (!bFound && iModule == packet.ModuleIndex) { bFound = true; largerDataArray[sample].AddRange(moreData[sample]); } else { //add 8 channels of short.minvalue largerDataArray[sample].AddRange(new short[] { short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue, short.MinValue }); } break; default: for (int i = 0; i < DASInfo.Modules[iModule].NumberOfChannels; i++) { largerDataArray[sample].Add(short.MinValue); } break; } } } moreData = new List(largerDataArray.Count); for (int sample = 0; sample < largerDataArray.Count; sample++) { moreData.Add(largerDataArray[sample].ToArray()); } } // Need to transpose the data to comply with the service interface. int maxSample = moreData.Count(); if (!packet.CareAboutSampleNumber) { maxSample = 1;//just do one sample back, we are in meter table mode and don't need the update speed... } for (int currentSample = 0; currentSample < maxSample; currentSample++) { short[][] transposedData = new short[moreData[0].Count()][]; for (int currentChannel = 0; currentChannel < transposedData.Length; currentChannel++) { transposedData[currentChannel] = new short[1]; transposedData[currentChannel][0] = moreData[currentSample][currentChannel]; } data.Add(transposedData.ToArray()); if (packet.CareAboutSampleNumber) { if (IsG5()) { //compute sample numbers by starting at 0 and then just incrementing //finally, convert from that number to what the requested rate is. ideally the request rate is faster to prevent sample //number overlap, but it's more important the time scale is correct //this should just cause aliasing if the sample numbers overlap sampleNumbers.Add( Convert.ToUInt64(Convert.ToDouble(sampleNumberCarryover * _g5RealtimeInterval) * expectedSampleRate)); timeStamps.Add(ulong.MinValue); if (sampleNumberCarryover == Int32.MaxValue) { sampleNumberCarryover = 0; } sampleNumberCarryover++; } else { sampleNumbers.Add(Convert.ToUInt64(timeSamples[currentSample] * expectedSampleRate)); timeStamps.Add(ulong.MinValue); } } else { if (sampleNumberCarryover == Int32.MaxValue) { sampleNumberCarryover = 0; } else { sampleNumberCarryover++; } sampleNumbers.Add(Convert.ToUInt64(sampleNumberCarryover)); timeStamps.Add(ulong.MinValue); } } // Send the data to the caller. packet.info.NewData(data, sampleNumbers, timeStamps, sequenceNumbers); } if ((this as DTS.Common.Interface.DASFactory.ICommunication).IsCanceled()) { packet.info.Cancel(); } StopRealtime(); packet.info.Success(); } catch (CanceledException) { // We land here when the user cancels real time. packet.info.Cancel(); } catch (System.Exception ex) { packet.info.Error(ex.Message, ex); } } /// /// Call this service to terminate real time streaming. /// /// User provided callback /// User provided data object void IRealTimeActions.ExitRealTimeMode(ServiceCallback callback, object userData) { var info = new TDASServiceAsyncInfo(callback, userData); LaunchAsyncWorker("TDAS.ExitRealTimeMode", new WaitCallback(AsyncExitRealTimeMode), info); } private void StopRealtime() { if (!SupportsRealtime()) { return; } else { if (IsG5()) { QuitG5Monitoring qm = new QuitG5Monitoring(this); qm.SyncExecute(); } else { try { QuerySerialNumber qs = new QuerySerialNumber(this, 2000); qs.SyncExecute(); } catch (Exception ex) { APILogger.Log(ex); } } } } private void AsyncExitRealTimeMode(object asyncInfo) { var info = asyncInfo as TDASServiceAsyncInfo; try { StopRealtime(); if (null != DASArmStatus) DASArmStatus.IsInRealtime = false;// FB15550: Update IsInRealtime flag if we exit RT so DataPRO is aware info.Success(); } catch (CanceledException) { info.Cancel(); } catch (System.Exception ex) { info.Error(ex.Message, ex); } } #endregion } }