using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; using DTS.Common.Enums.Communication; using DTS.Common.Enums.DASFactory; using DTS.Common.ICommunication; using DTS.Common.Utilities; using DTS.Common.Utilities.Logging; namespace DTS.DASLib.Command.TDAS { public class AAFilter : CommandBase { private TDASCommandPacketBase _command => baseCommand as TDASCommandPacketBase; private AAFilterCommandString _aafCommand => _command.GetCommandStringObject(0) as AAFilterCommandString; public int ModuleIndex { get => _command.ModuleIndex; set => _command.ModuleIndex = value; } public float Frequency { get => _aafCommand.Frequency; set => _aafCommand.Frequency = value; } public AAFilter(DTS.Common.Interface.DASFactory.ICommunication sock) : base(sock) { var command = new TDASCommandPacketBase(new AAFilterCommandString()) { RebuildBytes = true, ReplyWait = 0 }; baseCommand = command; } public AAFilter(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec) : base(sock, timeoutMillisec) { var command = new TDASCommandPacketBase(new AAFilterCommandString()) { RebuildBytes = true, ReplyWait = 0 }; baseCommand = command; } } internal class AAFilterCommandString : CommandString { /// /// Module Index /// public int ModuleIndex { get; set; } /// /// AAFilter Frequency /// public float Frequency { get; set; } = 0; protected override string _CommandDescription => "AAFilter"; protected override string _CommandString => "F"; public AAFilterCommandString() { RackCommand = true; } public override byte[] GetParameters() { var sb = new StringBuilder(); sb.Append(" "); sb.Append(ModuleIndex.ToString()); sb.Append(" "); sb.Append(_CommandString); sb.Append(" "); sb.Append(Frequency.ToString(CultureInfo.InvariantCulture)); return Encoding.ASCII.GetBytes(sb.ToString()); } } /// /// MonditorDataCommandString encapsulates the string parameters for the MD command used with TDAS PRO SIM /// internal class MonitorDataCommandString : CommandString { public string SamplesPerSendString => SamplesPerSend.ToString(); public int SamplesPerSend { get; set; } = 5; public double SampleRate { get; set; } = 200D; public string SampleRateString => string.Format("{0:0}", SampleRate); // This doesn't actually do anything, but this is the historical value passed public string Channels { get; set; } = "01234567"; // This doesn't actually do anything, but this is the historical value passed public string Seconds { get; set; } = "360000"; // This doesn't actually do anything, but this is the historical value passed private char _trigger = 'G'; public char Trigger { get => _trigger; set { if (value == 'G' || value == 'W') { _trigger = value; } else { throw new Exception("Trigger must be 'G' or 'W' " + value); } } } // This doesn't actually do anything, but this is the historical value passed public string TestId { get; set; } = "Test1"; protected override string _CommandDescription => "Monitor Data - MD"; protected override string _CommandString => "MD"; public override byte[] GetParameters() { System.IO.MemoryStream ms = new System.IO.MemoryStream(); ms.WriteByte(Convert.ToByte(' ')); ms.Write(Encoding.ASCII.GetBytes(SamplesPerSendString), 0, SamplesPerSendString.Length); ms.WriteByte(Convert.ToByte(' ')); ms.Write(Encoding.ASCII.GetBytes(SampleRateString), 0, SampleRateString.Length); ms.WriteByte(Convert.ToByte(' ')); ms.Write(Encoding.ASCII.GetBytes(Channels), 0, Channels.Length); ms.WriteByte(Convert.ToByte(' ')); ms.Write(Encoding.ASCII.GetBytes(Seconds), 0, Seconds.Length); ms.WriteByte(Convert.ToByte(' ')); ms.WriteByte(Convert.ToByte(Trigger)); ms.WriteByte(Convert.ToByte(' ')); ms.Write(Encoding.ASCII.GetBytes(TestId), 0, TestId.Length); return ms.ToArray(); } public MonitorDataCommandString() { RackCommand = false; } } /// /// MonitorData encapsulates the MD command used with TDAS PRO SIM. See also MonitorDataCommandString /// public class MonitorData : CommandBase { private TDASCommandPacketBase _command => baseCommand as TDASCommandPacketBase; public int ModuleIndex { get => _command.ModuleIndex; set => _command.ModuleIndex = value; } public MonitorData(DTS.Common.Interface.DASFactory.ICommunication sock) : base(sock) { baseCommand = new TDASCommandPacketBase(new MonitorDataCommandString()); } public MonitorData(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout) : base(sock, msTimeout) { baseCommand = new TDASCommandPacketBase(new MonitorDataCommandString()); } public void Execute() { Execute(CommandCallback, this); } public CommandReceiveAction CommandCallback(ICommandReport report) { return CommandReceiveAction.ContinueReceiving; } } //PROSIM "MD 5 200 01234567 360000 G TEST1"; //PRODIM static const char* pPRODIMMDCommand = "MD 1000 50 01234567 360000 G TEST1"; internal class ProMonitorDataCommandString : MonitorDataCommandString { protected override string _CommandDescription => "Monitor Data (Pro)"; protected override string _CommandString => "MD"; public ProMonitorDataCommandString(bool bSIM) { if (bSIM) { SamplesPerSend = 5; SampleRate = 200D; } else { SamplesPerSend = 1000; SampleRate = 50; } RackCommand = false; } } /// /// The G5MonitorDataCommandString class encapsulates the MDX string parameters used with G5MonitorDataCommand /// internal class G5MonitorDataCommandString : MonitorDataCommandString { protected override string _CommandDescription => "Monitor Data Extended - MDX"; protected override string _CommandString => "MDX"; public G5MonitorDataCommandString() { SamplesPerSend = 10; SampleRate = 1000D; RackCommand = false; } } public class ProMonitorData : MonitorData { public ProMonitorData(DTS.Common.Interface.DASFactory.ICommunication sock, bool bSIM) : base(sock) { baseCommand = new TDASCommandPacketBase(new ProMonitorDataCommandString(bSIM)); } public ProMonitorData(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout, bool bSIM) : base(sock, msTimeout) { baseCommand = new TDASCommandPacketBase(new ProMonitorDataCommandString(bSIM)); } /// /// The number of characters in the string for each sample/channel. /// private readonly int CharactersPerSample = 4; private short[] _rtData; public short[] RTData { get { if (null != _rtData) return _rtData; // This parses the actual response into ResponseData. ProcessData(); // Split the faux time stamp (0.0) from the sample data. var tokens = ResponseData.Split(' '); var data = new List(); // Parse the string into 4 byte chunks, one for each channel for (var currentOffset = 0; currentOffset < tokens[1].Length; currentOffset += CharactersPerSample) { var currentSample = tokens[1].Substring(currentOffset, CharactersPerSample); int ignoredCharacters; var sampleBytes = HexEncoding.GetBytes(currentSample, out ignoredCharacters); data.Add((short)(sampleBytes[0] * 256 + sampleBytes[1])); } _rtData = data.ToArray(); return _rtData; } } } /// /// G5MonitorData encapsulates the MDX command used by the TDAS G5 /// public class G5MonitorData : MonitorData { public G5MonitorData(DTS.Common.Interface.DASFactory.ICommunication sock) : base(sock) { baseCommand = new TDASCommandPacketBase(new G5MonitorDataCommandString()); } public G5MonitorData(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout) : base(sock, msTimeout) { baseCommand = new TDASCommandPacketBase(new G5MonitorDataCommandString()); } // The response format is 0.0 XXXXYYYY...ZZZZ // Where XXXX is the ADC value for the first channel, YYYY is the second channel and ZZZZ is the last channel which will be the second digital input. // There are 34 channels total (32 analog + 2 digital), 4 * 34 = 136 bytes long plus the four for 0.0 and the space making a total of 140 characters. /// /// The number of characters in the string for each sample/channel. /// private readonly int CharactersPerSample = 4; private short[] _rtData; /// /// The received real time data in channel order. /// public short[] RTData { get { if (null != _rtData) return _rtData; // This parses the actual response into ResponseData. ProcessData(); // Split the faux time stamp (0.0) from the sample data. var tokens = ResponseData.Split(' '); var data = new List(); // Parse the string into 4 byte chunks, one for each channel for (var currentOffset = 0; currentOffset < tokens[1].Length; currentOffset += CharactersPerSample) { var currentSample = tokens[1].Substring(currentOffset, CharactersPerSample); int ignoredCharacters; var sampleBytes = HexEncoding.GetBytes(currentSample, out ignoredCharacters); data.Add((short)(sampleBytes[0] * 256 + sampleBytes[1])); } _rtData = data.ToArray(); return _rtData; } } } internal class G5MonitorDataNextSampleCommandString : MonitorDataCommandString { protected override string _CommandDescription => "Monitor Data Extended - MDX"; protected override string _CommandString => "MDX"; public G5MonitorDataNextSampleCommandString() { RackCommand = false; } } internal class ProMonitorDataNextSampleCommandString : MonitorDataCommandString { protected override string _CommandDescription => "Monitor Data (Pro) - MD"; protected override string _CommandString => "MD"; public ProMonitorDataNextSampleCommandString() { RackCommand = false; } } /// /// The MDX command sends it's response, then continuously spews out additional response data. This command consumes the additional responses /// without sending anything. /// public class MonitorDataNextSampleBase : CommandBase { public enum MonitorType { Pro, G5, DIM }; private MonitorType _type; public MonitorDataNextSampleBase(DTS.Common.Interface.DASFactory.ICommunication sock, MonitorType type) : base(sock) { _type = type; switch (type) { case MonitorType.G5: baseCommand = new TDASCommandPacketBase(new G5MonitorDataNextSampleCommandString()) { MonitorDataMode = true }; break; case MonitorType.Pro: case MonitorType.DIM: baseCommand = new TDASCommandPacketBase(new ProMonitorDataNextSampleCommandString()) { MonitorDataMode = true }; break; } } public MonitorDataNextSampleBase(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout, MonitorType type) : base(sock, msTimeout) { _type = type; switch (type) { case MonitorType.G5: baseCommand = new TDASCommandPacketBase(new G5MonitorDataNextSampleCommandString()); break; case MonitorType.Pro: case MonitorType.DIM: baseCommand = new TDASCommandPacketBase(new ProMonitorDataNextSampleCommandString()); break; } } private readonly List _leftOverData = new List(); protected override void ProcessData() { var response = baseResponse as TDASCommandPacketBase; if (null == response) { return; } var bytes = new List(_leftOverData); _leftOverData.Clear(); bytes.AddRange(response.ToBytes()); string s = Encoding.ASCII.GetString(bytes.ToArray()); TDASCommandPacketBase command = baseCommand as TDASCommandPacketBase; var start = s.IndexOf(command.GetCommandString(0)); var end = s.LastIndexOf("\r\n"); if (start < end && start >= 0) { var csLength = command.GetCommandString(0).Length + 1; _responseData = s.Substring(start + csLength, end - start - csLength).TrimStart(); if (IsErrored()) { throw new InvalidOperationException(_responseData); } } else { _responseData = ""; } } private readonly int CharactersPerSample = 4; private List _rtData; private List _times = new List(); public double[] Times => _times.ToArray(); public List RTData { get { _rtData = new List(); _times = new List(); ProcessData(); // There is a good chance we'll get multiple lines in one response var lines = ResponseData.Split('\n'); // Because the G5 is sending data as fast as it can with no flow control, it's possible that we might fall behind and data may be dropped. // As a result there are a few safety checks to skip over partial reads. // this particular check doesn't seem helpful for PRO if (1 == lines.Length && 140 != lines[0].Length && _type == MonitorType.G5) { _leftOverData.AddRange(Encoding.ASCII.GetBytes(lines[0])); return _rtData; } for (var iLine = 0; iLine < lines.Length; iLine++) { var currentLine = lines[iLine]; if (string.IsNullOrWhiteSpace(currentLine)) { continue; } var index = currentLine.IndexOf("MD"); switch (_type) { case MonitorType.G5: if (index >= 0) { currentLine = currentLine.Substring(index + 5); } break; case MonitorType.Pro: case MonitorType.DIM: if (index >= 0) { currentLine = currentLine.Substring(index + 4); } break; } var tokens = currentLine.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); var data = new List(); var times = new List(); switch (_type) { case MonitorType.G5: { // Partial line, bogus line, or trailing empty line, if it's the last line in the data, we add it back in the buffer for next time if (2 != tokens.Length) { if (iLine == lines.Length - 1) { _leftOverData.AddRange(Encoding.ASCII.GetBytes(lines[iLine])); } continue; } if (137 != tokens[1].Length) { if (iLine == lines.Length - 1) { _leftOverData.AddRange(Encoding.ASCII.GetBytes(lines[iLine])); } continue; } // Intentionally drop the second to last sample. It's the second bank of digital channels which aren't exposed through a VDS. tokens[1] = tokens[1].Substring(0, tokens[1].Length - CharactersPerSample - 5) + tokens[1].Substring(tokens[1].Length - CharactersPerSample - 1, 4); for (var currentOffset = 0; currentOffset <= tokens[1].Length - CharactersPerSample; currentOffset += CharactersPerSample) { var currentSample = tokens[1].Substring(currentOffset, CharactersPerSample); int ignoredCharacters; var sampleBytes = HexEncoding.GetBytes(currentSample, out ignoredCharacters); data.Add((short)(sampleBytes[0] * 256 + sampleBytes[1])); } } break; case MonitorType.DIM: { if (5 != tokens.Length) { if (iLine == lines.Length - 1) { _leftOverData.AddRange(Encoding.ASCII.GetBytes(lines[iLine])); continue; } } else { double d; times.Add(double.TryParse(tokens[0], out d) ? d : 0D); try { var s = string.Concat(tokens[1], tokens[2], tokens[3], tokens[4]); s = s.Replace("\r", ""); s = new string(s.Reverse().ToArray()); var val = Convert.ToInt16(s, 2); data.Add(val); } catch (Exception ex) { APILogger.Log(ex); data.Add(0); } } } break; case MonitorType.Pro: { if (9 != tokens.Length) { if (iLine == lines.Length - 1) { _leftOverData.AddRange(Encoding.ASCII.GetBytes(lines[iLine])); continue; } } else { if (double.TryParse(tokens[0], out double d)) { times.Add(d); } for (var i = 1; i < tokens.Length; i++) { var currentSample = tokens[i].Replace("\n", "").Replace("+", ""); data.Add(short.TryParse(currentSample, out short newdata) ? newdata : short.MinValue); } } } break; } _rtData.Add(data.ToArray()); _times.AddRange(times.ToArray()); } return _rtData; } } /// /// We need to override SyncExecute because we don't want to send anything (that would tell the G5 to stop sending). Instead we just want to /// read whatever is out there. Otherwise this is mostly cut and paste of normal SyncExecute with some streamlining for our specific case. /// public override void SyncExecute() { // this is a try/finally to handle the ExecuteIsBusy try { // there can be only one! recorder.ExecuteIsBusy = true; if (recorder.IsCanceled()) { throw new CanceledException(); } UserCallback = null; UserCallbackData = null; IsSynchronous = true; SyncEvent.Reset(); recorder.PseudoExecute(new byte[0], ExecuteCallback, null, IO_Timeout); var syncExecTimeout = IO_Timeout; try { if (!WaitWithCondition.Wait(SyncEvent, syncExecTimeout, recorder.CancelEvent)) { // timeout LogString("SyncExecute: timeout"); throw new TimeoutException(MakeLogString("SyncExecute: timeout")); } } catch (WaitWithCondition.ConditionMetException) { throw new CanceledException(); } // we didn't timeout, check the result switch (ComReport.Result) { case CommunicationConstantsAndEnums.CommunicationResult.Canceled: throw new CanceledException(); case CommunicationConstantsAndEnums.CommunicationResult.ReceiveOK: if (baseResponse == null) { LogString("SyncExecute: ReceiveOK but response==null!"); LogCommand(false); throw new Exception(MakeLogString("SyncExecute: ReceiveOK but response==null!")); } if (baseResponse.Status != DFConstantsAndEnums.CommandStatus.StatusNoError) { // didn't go well var msg = MakeLogString("SyncExecute: response.Status = " + baseResponse.Status.ToString()); LogCommand(false); if (baseResponse.Status == DFConstantsAndEnums.CommandStatus.StatusInvalidModeForCommand) { throw new CommandException(CommandErrorReason.InvalidMode, msg); } if (baseResponse.Status == DFConstantsAndEnums.CommandStatus.StatusUnimplemented || baseResponse.Status == DFConstantsAndEnums.CommandStatus.StatusInvalidCommand || baseResponse.Status == DFConstantsAndEnums.CommandStatus.StatusInvalidCommandType) { throw new NotImplementedException(msg); } var ex = new Exception(msg); ex.Data.Add("Status", baseResponse.Status); throw ex; } // everything is fine, let it exit if (LogCommands) { LogCommand(false); } break; case CommunicationConstantsAndEnums.CommunicationResult.ReceiveFailed: { var msg = MakeLogString("SyncExecute: ComReport.Result == " + ComReport.Result.ToString()); LogCommand(false); throw new CommandException(CommandErrorReason.ReceiveFailed, msg); } case CommunicationConstantsAndEnums.CommunicationResult.ReceiveTimeout: { var msg = MakeLogString("SyncExecute: ComReport.Result == " + ComReport.Result.ToString()); LogCommand(false); throw new CommandException(CommandErrorReason.ReceiveFailed, msg); } case CommunicationConstantsAndEnums.CommunicationResult.SendFailed: case CommunicationConstantsAndEnums.CommunicationResult.SendTimeout: { var msg = MakeLogString("SyncExecute: ComReport.Result == " + ComReport.Result.ToString()); LogCommand(false); throw new CommandException(CommandErrorReason.SendFailed, msg); } default: { var msg = MakeLogString("SyncExecute: Unknown ComReport.Result == " + ComReport.Result.ToString()); LogCommand(false); throw new Exception(msg); } } } finally { recorder.ExecuteIsBusy = false; } } } internal class QuitG5CommandString : CommandString { protected override string _CommandString => "Q"; protected override string _CommandDescription => "Quit Monitoring Data - Q"; } /// /// Use QuitMonitoring to halt realtime data from a G5 or TDAS PRO SIM /// public class QuitG5Monitoring : CommandBase { public int ModuleIndex { set => ((TDASCommandPacketBase)baseCommand).ModuleIndex = value; } public QuitG5Monitoring(DTS.Common.Interface.DASFactory.ICommunication sock) : base(sock) { baseCommand = new TDASCommandPacketBase(new QuitG5CommandString()); } public QuitG5Monitoring(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout) : base(sock, msTimeout) { baseCommand = new TDASCommandPacketBase(new QuitG5CommandString()); } } internal class SingleSampleCommandString : CommandString { protected override string _CommandString => "SS"; protected override string _CommandDescription => "Single Sample - SS"; } /// /// The SingleSample command is used with either a TDAS G5 or TDAS PRO SIM to take a single measurement for all channels. /// public class SingleSample : CommandBase { private short[] _rtData; public short[] RTData { get { if (null != _rtData) return _rtData; ProcessData(); var tokens = ResponseData.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); var data = new List(); foreach (var token in tokens) { if (short.TryParse(token, out short temp) && data.Count < 8) { data.Add(temp); } else { //digital channels are always included with G5, and we already have 8 values, so ... chances are it's the digital bits if (!int.TryParse(token, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int i)) continue; data.Add((short)i); data.Add((short)(i >> 16)); } } _rtData = data.ToArray(); return _rtData; } } private TDASCommandPacketBase _command => baseCommand as TDASCommandPacketBase; public int ModuleIndex { get => _command.ModuleIndex; set => _command.ModuleIndex = value; } public SingleSample(DTS.Common.Interface.DASFactory.ICommunication sock) : base(sock) { baseCommand = new TDASCommandPacketBase(new SingleSampleCommandString()); } public SingleSample(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout) : base(sock, msTimeout) { baseCommand = new TDASCommandPacketBase(new SingleSampleCommandString()); } } }