Files
DP44/DataPRO/TDASCommands/RealtimeCommands.cs
2026-04-17 14:55:32 -04:00

740 lines
30 KiB
C#

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
{
/// <summary>
/// Module Index
/// </summary>
public int ModuleIndex { get; set; }
/// <summary>
/// AAFilter Frequency
/// </summary>
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());
}
}
/// <summary>
/// MonditorDataCommandString encapsulates the string parameters for the MD command used with TDAS PRO SIM
/// </summary>
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;
}
}
/// <summary>
/// MonitorData encapsulates the MD command used with TDAS PRO SIM. See also MonitorDataCommandString
/// </summary>
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;
}
}
/// <summary>
/// The G5MonitorDataCommandString class encapsulates the MDX string parameters used with G5MonitorDataCommand
/// </summary>
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));
}
/// <summary>
/// The number of characters in the string for each sample/channel.
/// </summary>
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<short>();
// 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;
}
}
}
/// <summary>
/// G5MonitorData encapsulates the MDX command used by the TDAS G5
/// </summary>
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.
/// <summary>
/// The number of characters in the string for each sample/channel.
/// </summary>
private readonly int CharactersPerSample = 4;
private short[] _rtData;
/// <summary>
/// The received real time data in channel order.
/// </summary>
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<short>();
// 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;
}
}
/// <summary>
/// The MDX command sends it's response, then continuously spews out additional response data. This command consumes the additional responses
/// without sending anything.
/// </summary>
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<byte> _leftOverData = new List<byte>();
protected override void ProcessData()
{
var response = baseResponse as TDASCommandPacketBase;
if (null == response) { return; }
var bytes = new List<byte>(_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<short[]> _rtData;
private List<double> _times = new List<double>();
public double[] Times => _times.ToArray();
public List<short[]> RTData
{
get
{
_rtData = new List<short[]>();
_times = new List<double>();
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<short>();
var times = new List<double>();
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;
}
}
/// <summary>
/// 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.
/// </summary>
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";
}
/// <summary>
/// Use QuitMonitoring to halt realtime data from a G5 or TDAS PRO SIM
/// </summary>
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";
}
/// <summary>
/// The SingleSample command is used with either a TDAS G5 or TDAS PRO SIM to take a single measurement for all channels.
/// </summary>
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<short>();
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());
}
}
}