This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,674 @@
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<T> : Communication<T>,
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<UInt64> _timeStamps = new List<ulong>();
private List<UInt64> _sequenceNumbers = new List<ulong>();
private List<UInt64> _sampleNumbers = new List<ulong>();
private List<short[][]> _dataSamples = new List<short[][]>();
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);
}
/// <summary>
/// only used with RACK,
/// is a polling (sampleaverage/single sample) realtime mode
/// </summary>
/// <param name="asyncInfo"></param>
private void AsyncRealTimePolling(object asyncInfo)
{
var packet = asyncInfo as RealTimeAsyncPacket;
//List<ulong> sampleNumbers = new List<ulong>();
//List<short[][]> data = new List<short[][]>();
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<short[][]>() { rtData }, new List<ulong>(new ulong[] { sampleNumber }), new List<ulong>(new ulong[] { ulong.MinValue }), new List<ulong>(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();
}
/// <summary>
/// indicates whether the DAS supports streaming
/// 10572 implement SW side for single command streaming realtime
/// </summary>
public bool SupportsIndividualChannelRealtimeStreaming => false;
/// <summary>
/// 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
/// </summary>
private const double _g5RealtimeInterval = 1 / 978D;
//private DateTime _lastRTLogTime = DateTime.MinValue;
//private int _samplesProcessed = 0;
/// <summary>
/// The worker for realtime service.
/// </summary>
/// <param name="asyncInfo">A RealTimeAsyncPacket for responses</param>
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<ulong>();
var timeStamps = new List<ulong>();
var sequenceNumbers = new List<ulong>();
var data = new List<short[][]>();
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<List<short>> largerDataArray = new List<List<short>>();
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<short>()); }
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<short[]>(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);
}
}
/// <summary>
/// Call this service to terminate real time streaming.
/// </summary>
/// <param name="callback">User provided callback</param>
/// <param name="userData">User provided data object</param>
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
}
}