Files
DP44/DataPRO/TDASCommands/TDASCommandPacketBase.cs

528 lines
20 KiB
C#
Raw Permalink Normal View History

2026-04-17 14:55:32 -04:00
using DTS.Common.Utilities.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
namespace DTS.DASLib.Command.TDAS
{
/*
* typedef struct _md {
int delim; delimiter pattern
int key; key to mem state
long itemSize; mem size that this MDB controls
long dataSize; length of valid data buffer
unsigned int nCrc; buffer crc
void *flink; forward link to next buffer
struct _md *next; used for doubly-linked list
struct _md *prev; of all mds
void *this; this buffer of size itemSize
void *freeList; a free list of objects
long nInst; number of memory blocks created
long nActive; number still out there somewhere
long nFree; length of free list
}*/
//note in CVI longs are the same size as ints, or 4 bytes
internal class MDB_BLOCK
{
public enum MDB_Fields
{
delim = 0,
key,
itemSize,
dataSize,
nCrc,
flink,
next,
prev,
ptrThis,
freeList,
nInst,
nActive,
nFree
}
public int GetField(MDB_Fields field)
{
return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(_mdb, 4 * (int)field));
}
private byte[] _mdb;
public const int MDB_BLOCK_SIZE = 52;
public const int DMA_SIZE = 1024;
public const int DMA_BLOCK_SIZE = MDB_BLOCK_SIZE + DMA_SIZE;
public const int MEM_KEY_START = 100;
public static readonly byte[] DelimBytes = { 0xFE, 0x01, 0xFD, 0x02 };
public static readonly int DelimPattern = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(DelimBytes, 0));
public static readonly string DelimString = BitConverter.ToString(DelimBytes);
public MDB_BLOCK(byte[] packet)
{
_mdb = packet;
}
public bool HasData => (null != _mdb && GetField(MDB_Fields.dataSize) > 0);
public short[] GetData()
{
var data = new List<short>();
for (var i = 0; i < GetField(MDB_Fields.dataSize) / 2; i++)
{
try
{
var value = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(_mdb, MDB_BLOCK_SIZE + i * 2));
data.Add(value);
}
catch (Exception) { Trace.WriteLine("failed to convert " + i); }
}
return data.ToArray();
}
}
public class TDASCommandPacketBase : CommandPacketBase
{
public bool RackCommand
{
get => _commandStrings[0].RackCommand;
set => _commandStrings[0].RackCommand = value;
}
public bool SingleModuleCommand
{
get { return _commandStrings[0].SingleModuleCommand; }
set { _commandStrings[0].SingleModuleCommand = value; }
}
public string RackSerialNumber
{
get { return _commandStrings[0].RackSerialNumber; }
set { _commandStrings[0].RackSerialNumber = value; }
}
private char _moduleIndex = ' ';
public int ModuleIndex
{
get => int.Parse(new string(_moduleIndex, 1));
set
{
if (value > 9) { throw new Exception("ModuleIndex must be <10 for this command" + ModuleIndex); }
_moduleIndex = value.ToString()[0];
RackCommand = false;
}
}
public bool AllModule { get; set; }
public bool RebuildBytes { get; set; }
public bool ExpectsData
{
get => _commandStrings[0].ExpectsData;
set => _commandStrings[0].ExpectsData = value;
}
public bool ExpectsMultipleLines
{
get => _commandStrings[0].ExpectsMultipleLines;
set => _commandStrings[0].ExpectsMultipleLines = value;
}
public int LinesExpected
{
get => _commandStrings[0].LinesExpected;
set => _commandStrings[0].LinesExpected = value;
}
private const int MAX_CMD = 1;
private const int MAX_CMD_SIZE = 512;
public const int CDB_SIZE = 536;
private readonly byte _cdbId;
private readonly byte _cdbQueue;
public byte CharWait { get; set; }
public int ReplyWait { get; set; }
private readonly byte[] _reserved;
private readonly int _rackId;
private readonly byte[] _memoryAddress = GENERIC_MEMORY_ADDRESS;
private readonly List<CommandString> _commandStrings = new List<CommandString>();
private readonly byte[] _responseBytes;
private static readonly byte[] GENERIC_MEMORY_ADDRESS = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public override byte[] ToBytes()
{
if (null != _responseBytes) { return _responseBytes; }
var ms = new MemoryStream(CDB_SIZE);
ms.WriteByte(_cdbId);
ms.WriteByte(_cdbQueue);
ms.WriteByte(Convert.ToByte(_commandStrings.Count));
ms.WriteByte(CharWait);
ms.Write(BitConverter.GetBytes(ReplyWait), 0, 4);
ms.Write(_reserved, 0, _reserved.Length);
ms.Write(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(_rackId)), 0, 4);
ms.Write(_memoryAddress, 0, _memoryAddress.Length);
if (RackCommand)
{
ms.Write(new byte[] { 0x31, 0x30, 0x30, 0x30 }, 0, 4);
}
else
{
if (AllModule) { ms.WriteByte(Convert.ToByte('*')); }
if (_moduleIndex != ' ')
{
ms.WriteByte(Convert.ToByte(Convert.ToByte(_moduleIndex)));
}
}
foreach (var cs in _commandStrings)
{
if (RebuildBytes) { cs.RebuildBytes(); }
ms.Write(cs.GetBytes(), 0, cs.Length);
}
//17716 eliminate extra characters filling out outgoing TDAS coms
//if (ms.Length < CDB_SIZE)
//{
// ms.Write(new byte[CDB_SIZE - (int)ms.Length], 0, CDB_SIZE - (int)ms.Length);
//}
return ms.ToArray();
}
public override object ConvertByteToCommandType(byte b)
{
throw new NotSupportedException("TDASCommandPacketBase::ConvertByteToCommandType not supported");
}
private int _expectedBytes;
private int _bytesSoFar;
public bool UseMDBMode { get; set; }
public bool MonitorDataMode { get; set; }
private readonly List<MDB_BLOCK> _data = new List<MDB_BLOCK>();
private int _curOffset;
public int GetExpectedBytes()
{
return _expectedBytes;
}
public short[] GetData()
{
List<short> data = new List<short>();
//we have to skip the first one for some odd reason?
for (int i = 1; i < _data.Count; i++)
{
MDB_BLOCK mdb = _data[i];
if (mdb.HasData)
{
data.AddRange(mdb.GetData());
}
}
return data.ToArray();
}
public bool RealtimeCommand { get; set; }
public override PacketState VerifyPacket(byte[] Bytes)
{
if (!ExpectsData && Bytes.Length > 0)
{
Thread.Sleep(100); //cut from 500 to 100 for performance reasons
APILogger.Log($"VerifyPacket [{SequenceNumber}], returning OK as we don't expect data and have greater than 0 length ({Bytes.Length})");
return PacketState.OK;
}
if (UseMDBMode)
{
if (0 == _curOffset)
{
int index = -1;
string s = BitConverter.ToString(Bytes);
try { index = s.IndexOf(MDB_BLOCK.DelimString, 1); }
catch (Exception) { }
if (index < 0)
{
return PacketState.TooShort;
}
if (index > 0)
{
//need to advance to the start of the block
for (var i = 1; i < Bytes.Length - MDB_BLOCK.DelimBytes.Length; i++)
{
if (Bytes[i] != MDB_BLOCK.DelimBytes[0] || Bytes[i + 1] != MDB_BLOCK.DelimBytes[1] ||
Bytes[i + 2] != MDB_BLOCK.DelimBytes[2] ||
Bytes[i + 3] != MDB_BLOCK.DelimBytes[3]) continue;
_curOffset = i;
break;
}
}
}
while (Bytes.Length - _curOffset >= MDB_BLOCK.DMA_BLOCK_SIZE)
{
byte[] block;
using (var ms = new MemoryStream())
{
//need to advance to the start of the block
for (var i = _curOffset; i < Bytes.Length - MDB_BLOCK.DelimBytes.Length; i++)
{
if (Bytes[i] != MDB_BLOCK.DelimBytes[0] || Bytes[i + 1] != MDB_BLOCK.DelimBytes[1] ||
Bytes[i + 2] != MDB_BLOCK.DelimBytes[2] ||
Bytes[i + 3] != MDB_BLOCK.DelimBytes[3]) continue;
_curOffset = i;
break;
}
//scan forward from _curOffset till we see the
ms.Write(Bytes, _curOffset, MDB_BLOCK.DMA_BLOCK_SIZE);
_curOffset += MDB_BLOCK.DMA_BLOCK_SIZE;
block = ms.ToArray();
}
var mdb = new MDB_BLOCK(block);
_data.Add(mdb);
if (mdb.GetField(MDB_BLOCK.MDB_Fields.delim) == MDB_BLOCK.DelimPattern)
{
if (mdb.GetField(MDB_BLOCK.MDB_Fields.key) == MDB_BLOCK.MEM_KEY_START)
{
var s = Encoding.ASCII.GetString(block, MDB_BLOCK.MDB_BLOCK_SIZE + 5, 10);
var tokens = s.Split(' ');
_expectedBytes = Convert.ToInt32(tokens[0]);
}
else
{
_bytesSoFar += mdb.GetField(MDB_BLOCK.MDB_Fields.dataSize);
if (_bytesSoFar >= _expectedBytes && 0 != _expectedBytes)
{
return PacketState.OK;
}
}
}
else
{
return PacketState.OK;
}
}
return PacketState.TooShort;
}
{
string s = Encoding.ASCII.GetString(Bytes);
//Could be a multi-line response where we've grabbed the newline as the first characters.
if (s.Length > 2 && s.IndexOf("\r\n") >= 0)
{
if (!s.EndsWith("\r\n") && ExpectsMultipleLines)
{
APILogger.Log($"TDASCommandPacketBase:VerifyPacket [{SequenceNumber}], returning too short {s}");
return PacketState.TooShort;
}
if (!ExpectsMultipleLines)
{
APILogger.Log($"TDASCommandPacketBase:VerifyPacket [{SequenceNumber}], returning OK, not expecting multiple lines: {s}");
return PacketState.OK;
}
var lines = s.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
//G5 returns ~ and if we see that we know we still have more lines coming
//TDAS PRO does not, but we do have an expectation of a certain number of lines
if (lines[lines.Length - 1].Contains('~'))
{
APILogger.Log($"[{SequenceNumber}] returning packet too short (contains ~) {s}");
return PacketState.TooShort;
}
if (lines.Length < LinesExpected)
{
APILogger.Log(
$"[{SequenceNumber}] Lines less than expected, returning too short {lines.Length}<{LinesExpected} for {s}");
return PacketState.TooShort;
}
//if command has a required response and we haven't seen it yet,
//consider us as having not received the full response yet.
//18127 Test Channel Run Broadcast can complete before response is received and processed
if (null != _commandStrings && 1 == _commandStrings.Count &&
!string.IsNullOrEmpty(_commandStrings[0].RequiredResponseString))
{
if (!s.Contains(_commandStrings[0].RequiredResponseString))
{
return PacketState.TooShort;
}
}
return PacketState.OK;
}
APILogger.Log($"[{SequenceNumber}] Returning too short - {s} - didn't find CR-EOL");
return PacketState.TooShort;
}
}
private static ushort _globalSequenceNumber;
private static readonly object GLOBAL_SEQUENCE_NUMBER_LOCK = new object();
public override void GetNextSequenceNumber()
{
lock (GLOBAL_SEQUENCE_NUMBER_LOCK)
{
SequenceNumber = _globalSequenceNumber;
_globalSequenceNumber++;
}
}
public override void ComputeCRCs()
{
}
private void AddCommand(CommandString commandString)
{
if (_commandStrings.Count + 1 > MAX_CMD)
{
throw new CommandPacketException(CommandPacketException.ERROR_CODES.TOO_MANY_COMMANDS, CommandPacketException.ERROR_CODES.TOO_MANY_COMMANDS.ToString());
}
int totalBytes = commandString.Length;
foreach (CommandString cs in _commandStrings) { totalBytes += cs.Length; }
if (totalBytes > MAX_CMD_SIZE)
{
throw new CommandPacketException(CommandPacketException.ERROR_CODES.TOO_MANY_BYTES, CommandPacketException.ERROR_CODES.TOO_MANY_BYTES.ToString());
}
_commandStrings.Add(commandString);
SetCommand(0x00, commandString.GetCommandDescription());
Type = "TDAS";
}
public TDASCommandPacketBase(CommandString cs)
{
GetNextSequenceNumber();
_cdbId = 1;
_cdbQueue = Convert.ToByte(false);
//support.dtsweb.com/hc/requests/7294
//this is used to avoid flooding buffer, but was too large, TDC is using 1ms and presumably when we copied
//tdc we must have used an older version
CharWait = 0x01;
ReplyWait = 30;
_reserved = new byte[] { 0x00, 0x00, 0x00, 0x00 };
_rackId = 1006;
AddCommand(cs);
}
public TDASCommandPacketBase(byte[] buffer, CommandPacketBase command)
{
if (null != command)
{
SequenceNumber = command.SequenceNumber;
}
var s = Encoding.ASCII.GetString(buffer);
if (s.Contains("1000ARM"))
{
APILogger.Log($"Sequence [{SequenceNumber}] complete response", s);
}
_responseBytes = buffer;
SetCommand(0x00, "");
Type = "TDAS";
}
public string GetCommandString(int index)
{
return _commandStrings[index].GetCommandPortion();
}
public CommandString GetCommandStringObject(int index) { return _commandStrings[index]; }
public string ToCommandString()
{
byte[] bytes = ToBytes();
return Encoding.ASCII.GetString(bytes, 24, bytes.Length - 24).Replace("\r\n", string.Empty).Replace("\0", string.Empty);
}
}
public abstract class CommandString
{
/// <summary>
/// defines a required string to find in the response
/// if string is not found response is not complete yet
/// override to require a response
/// 18127 Test Channel Run Broadcast can complete before response is received and processed
/// </summary>
public virtual string RequiredResponseString { get; set; } = string.Empty;
public bool ExpectsData { get; set; } = true;
public bool ExpectsMultipleLines { get; set; }
public int LinesExpected { get; set; }
public virtual byte[] GetParameters()
{
return new byte[] { };
}
private bool _bRackCommand = true;
public bool RackCommand
{
get { return _bRackCommand; }
set { _bRackCommand = value; }
}
private bool _bSingleModuleCommand = false;
public bool SingleModuleCommand
{
get { return _bSingleModuleCommand; }
set { _bSingleModuleCommand = value; }
}
private string _RackSerialNumber = string.Empty;
public string RackSerialNumber
{
get { return _RackSerialNumber; }
set { _RackSerialNumber = value; }
}
protected abstract string _CommandString { get; }
public virtual string GetCommandPortion() { return _CommandString; }
protected abstract string _CommandDescription { get; }
public string GetCommandDescription() { return _CommandDescription; }
private byte[] _bytes;
public int Length
{
get
{
if (null == _bytes)
{
_bytes = GetBytes();
}
return _bytes.Length;
}
}
public void RebuildBytes() { _bytes = null; }
public byte[] GetBytes()
{
if (null != _bytes) return _bytes;
var ms = new MemoryStream();
ms.Write(Encoding.ASCII.GetBytes(_CommandString), 0, _CommandString.Length);
byte[] parameters = GetParameters();
if (parameters.Length > 0) { ms.Write(parameters, 0, parameters.Length); }
ms.Write(new byte[] { 0x0d, 0x0a }, 0, 2);
_bytes = ms.ToArray();
return _bytes;
}
}
public class CommandPacketException : Exception
{
public enum ERROR_CODES
{
TOO_MANY_COMMANDS = 0,
TOO_MANY_BYTES = 1,
UNKNOWN = 2
}
public CommandPacketException(ERROR_CODES errorCode, string description)
: base(description)
{
Data["ErrorCode"] = errorCode;
}
public ERROR_CODES ErrorCode
{
get
{
if (!Data.Contains("ErrorCode")) { return ERROR_CODES.UNKNOWN; }
return (ERROR_CODES)Data["ErrorCode"];
}
}
}
}