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,19 @@
using DTS.Common.ICommunication;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class EndRealtimeMode : RealtimeCommandBase
{
protected override Commands _Command => Commands.EndRealtimeMode;
public EndRealtimeMode(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
}
public EndRealtimeMode(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec)
{
}
}
}

View File

@@ -0,0 +1,134 @@
using System.Collections.Generic;
using DTS.Common.Enums.DASFactory;
using DTS.Common.ICommunication;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class GetRealtimeSamples : RealtimeCommandBase, IGetRealtimeSamples
{
protected override Commands _Command => Commands.GetRealtimeSamples;
protected ulong _sampleNumber;
/// <summary>
/// The first sample number of the samples being returned. The remaining samples in
/// the response packet will be numbered sequentially and without gaps from this number.
/// If 12 samples are returned, and SampleNumber is 83042, the first sample will be number
/// 83042, the second 83043, etc.
/// </summary>
public ulong SampleNumber => _sampleNumber;
protected ulong _timeStamp;
public ulong TimeStamp => _timeStamp;
protected ulong _sequenceNumber;
public ulong SequenceNumber => _sequenceNumber;
protected List<short[]> _data;
public short[] GetChannelData(int zeroBasedChannel)
{
return _data[zeroBasedChannel];
}
protected ushort _channels;
public ushort Channels
{
get
{
if (null == _data) return 0;
return _channels;
}
set => _channels = value;
}
protected int _samplesReturned;
/// <summary>
/// Contains the number of samples that were returned in this realtime packet. Can be 0!
/// </summary>
public int SamplesReturned => _samplesReturned;
public GetRealtimeSamples(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock, 2000)
{
// don't need to put anything in here ... its presense
// tells the firmware that we support multiple-sample
// realtime
command.Parameter = new byte[1];
}
/// <summary>
/// constructor for GetRealtimeSamples, a class implementing IGetRealtimeSamples
/// this class is capable of returning realtime samples
/// </summary>
/// <param name="sock"></param>
/// <param name="TimeoutMillisec"></param>
/// <param name="bPolling">whether realtime is using polling or not</param>
public GetRealtimeSamples(DTS.Common.Interface.DASFactory.ICommunication sock, int TimeoutMillisec, bool bPolling = false)
: base(sock, TimeoutMillisec)
{
// don't need to put anything in here ... its presense
// tells the firmware that we support multiple-sample
// realtime
command.Parameter = new byte[1];
}
protected override CommandReceiveAction WholePackage()
{
try
{
if (response.Status == DFConstantsAndEnums.CommandStatus.StatusNoError)
{
// Figure out the number of samples returned
var samplesReturned = response.ParameterLength / (_channels * 2 + 8);
_samplesReturned = samplesReturned;
var unsignedData = new List<ushort[]>(_channels);
_data = new List<short[]>(_channels);
// Grab the sample number
if (response.ParameterLength > 0)
{
response.GetParameter(0, out _sampleNumber);
}
// Create the data arrays by channel
for (var i = 0; i < _channels; i++)
{
unsignedData.Add(new ushort[samplesReturned]);
_data.Add(new short[samplesReturned]);
}
// Grab the data
for (var sample = 0; sample < samplesReturned; sample++)
{
// Data order for a 9 channel stack
// 7 8 9 4 5 6 1 2 3 7 8 9 4 5 6 1 2 3 etc.
for (var channel = 0; channel < _channels; channel++)
{
var sliceNumber = channel / 3;
var sliceOffset = (_channels / 3 - sliceNumber - 1) * 3;
var channelInSlice = channel % 3;
var offset = sliceOffset + channelInSlice;
response.GetParameter(8 + sample * (8 + _channels * 2) + 2 * offset, out ushort val);
unsignedData[channel][sample] = val;
_data[channel][sample] = (short)(unsignedData[channel][sample] - 0x8000);
}
}
return CommandReceiveAction.StopReceiving;
}
}
catch
{
_samplesReturned = 0;
}
return CommandReceiveAction.StopReceiving;
}
public override void ResponseToString(ref List<List<string>> lines)
{
base.ResponseToString(ref lines);
lines.Add(new List<string> { $"Sample number: {ResponseStatus}, Samples returned: {SampleNumber}, Time Stamp returned: {TimeStamp}, Sequence Number returned: {SequenceNumber}" });
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using DTS.Common.Enums.DASFactory;
using DTS.Common.ICommunication;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class GetRealtimeSamplesSLICE2 : GetRealtimeSamples
{
public GetRealtimeSamplesSLICE2(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock) { LogCommands = false; }
public GetRealtimeSamplesSLICE2(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec) { LogCommands = false; }
protected override CommandReceiveAction WholePackage()
{
try
{
if (response.Status == DFConstantsAndEnums.CommandStatus.StatusNoError)
{
// Figure out the number of samples returned
var samplesReturned = (response.ParameterLength - 8) / (_channels * 2);
_samplesReturned = samplesReturned;
_data = new List<short[]>(_channels);
// Grab the sample number
if (response.ParameterLength > 0)
{
response.GetParameter(0, out _sampleNumber);
}
// Create the data arrays by channel
for (var i = 0; i < _channels; i++)
{
_data.Add(new short[samplesReturned]);
}
// Grab the data
var parameter = 8;
for (var sample = 0; sample < samplesReturned; sample++)
{
for (var channel = 0; channel < _channels; channel++)
{
response.GetParameter(parameter, out ushort val);
//changed from unsigned to signed
_data[channel][sample] = (short)((((val & 0x00FF) << 8) | ((val >> 8) & 0x00FF)) + 0x8000);
parameter += 2;
}
}
return CommandReceiveAction.StopReceiving;
}
}
catch
{
_samplesReturned = 0;
}
return CommandReceiveAction.StopReceiving;
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using DTS.Common.Enums.DASFactory;
using DTS.Common.ICommunication;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class GetRealtimeSamplesSLICE6 : GetRealtimeSamples
{
public GetRealtimeSamplesSLICE6(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock) { }
public GetRealtimeSamplesSLICE6(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec) { }
protected override CommandReceiveAction WholePackage()
{
try
{
if (response.Status == DFConstantsAndEnums.CommandStatus.StatusNoError)
{
// Figure out the number of samples returned
var samplesReturned = (response.ParameterLength - 8) / (_channels * 2);
_samplesReturned = samplesReturned;
_data = new List<short[]>(_channels);
// Grab the sample number
if (response.ParameterLength > 0)
{
response.GetParameter(0, out _sampleNumber);
}
// Create the data arrays by channel
for (int i = 0; i < _channels; i++)
{
_data.Add(new short[samplesReturned]);
}
// Grab the data
var parameter = 8;
for (var sample = 0; sample < samplesReturned; sample++)
{
for (var channel = 0; channel < _channels; channel++)
{
response.GetParameter(parameter, out ushort val);
//Slice6 data is signed data. No need to convert.
_data[channel][sample] = (short)((((val & 0x00FF) << 8) | ((val >> 8) & 0x00FF)));
parameter += 2;
}
}
return CommandReceiveAction.StopReceiving;
}
}
catch
{
_samplesReturned = 0;
}
return CommandReceiveAction.StopReceiving;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DTS.Common.Enums.DASFactory;
using DTS.Common.Interface.DASFactory;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class GetRealtimeSamplesTSRAIR : GetRealtimeSamples
{
public GetRealtimeSamplesTSRAIR(ICommunication sock)
: base(sock) { }
public GetRealtimeSamplesTSRAIR(ICommunication sock, int TimeoutMillisec)
: base(sock, TimeoutMillisec) { }
protected List<UInt64> _timestamps;
public List<UInt64> Timestamps => _timestamps;
protected override CommandReceiveAction WholePackage()
{
try
{
if (response.Status == DFConstantsAndEnums.CommandStatus.StatusNoError)
{
// Figure out the number of samples returned
int samples_returned = (response.ParameterLength - 8) / (_channels * 2);
_samplesReturned = samples_returned;
int data_bytes_returned = samples_returned * _channels * 2;
_data = new List<short[]>(_channels);
// Grab the sample number
if (response.ParameterLength > 0)
{
response.GetParameter(0, out _sampleNumber);
}
// Create the data arrays by channel
for (int i = 0; i < _channels; i++)
{
_data.Add(new short[samples_returned]);
}
// Grab the data
int parameter = 8;
for (int sample = 0; sample < samples_returned; sample++)
{
for (int channel = 0; channel < _channels; channel++)
{
response.GetParameter(parameter, out ushort uval);
//TSRAIR data is unsigned data. Convert sign, no need to normalize
_data[channel][sample] = (short)((((uval & 0x00FF) << 8) | ((uval >> 8) & 0x00FF)));
parameter += 2;
}
}
return CommandReceiveAction.StopReceiving;
}
}
catch
{
_samplesReturned = 0;
}
return CommandReceiveAction.StopReceiving;
}
}
}

View File

@@ -0,0 +1,43 @@
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
/// <summary>
/// this interface describes objects that are able to get realtime samples from
/// SLICE devices, the implementation differs based on whether it's using streaming
/// or is a SLICE6 device, etc, but all still operate through the same interface
/// </summary>
public interface IGetRealtimeSamples
{
/// <summary>
/// returns the sample number for the first sample, with all samples after that being consecutive
/// </summary>
ulong SampleNumber { get; }
ulong TimeStamp { get; }
ulong SequenceNumber { get; }
/// <summary>
/// returns all samples for the given channel
/// </summary>
/// <param name="zeroBasedChannel"></param>
/// <returns></returns>
short[] GetChannelData(int zeroBasedChannel);
/// <summary>
/// right now this holds the total channel count for the unit
/// </summary>
ushort Channels { get; set; }
/// <summary>
/// the count of samples per channel that were returned
/// if return value is 10 and there's 10 channels, there's 100 samples, or 10 samples per 10 channels
/// </summary>
int SamplesReturned { get; }
/// <summary>
/// whether to log the command or not
/// (not applicable on streaming mode as no command is executed)
/// default is normally false otherwise the log will be spammy
/// </summary>
bool LogCommands { get; set; }
/// <summary>
/// this is the execute that retrieves samples
/// </summary>
void SyncExecute();
}
}

View File

@@ -0,0 +1,41 @@
using DTS.Common.ICommunication;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public abstract class RealtimeCommandBase : CommandBase
{
protected enum Commands
{
Reserved = 0x00,
StartRealtimeMode = 0x01,
EndRealtimeMode = 0x02,
GetRealtimeSamples = 0x03,
RetrieveSingleSample = 0x04,
RetrieveSampleAverage = 0x05,
RetrieveInternalOffsetSampleAvg = 0x06,
StartRealtimeStreamingMode = 0x07,
GetRealtimeStreamSamples = 0x08,
StartTimeStampStreamMode = 0x09, // CMDRT_START_TIMESTAMPED_STREAM_MODE = 0x09: Start IRIG realtime stream with destination IP address from system attribute
GetTimeStampStreamSamples = 0x0A, // CMDRT_TIMESTAMPED_STREAM_SAMPLES = 0x0A: streaming data from cmd 0x09
ChannelTappedTest = 0x0B,
I106StreamConfigSet = 0x0C, // FB15313 add streaming config options
I106StreamConfigGet = 0x0D,
};
protected abstract Commands _Command { get; }
protected RealtimeCommandBase(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
command.Type = CommandPacket.CommandType.Realtime;
command.SetCommand((byte)_Command, _Command.ToString());
}
protected RealtimeCommandBase(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec)
{
command.Type = CommandPacket.CommandType.Realtime;
command.SetCommand((byte)_Command, _Command.ToString());
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
/// <summary>
/// this class handles decoding a SPS realtime stream packet.
/// </summary>
public class RealtimeStreamDecoder
{
/* per lp,
* Byte index:
0 Sync = CMD_SYNC_BYTE = 0xFA
1 type = CMD_TYPE_REALTIME = 0x08
2 command = CMDRT_REALTIME_STREAM_SAMPLE_PUSH = 0x08
3 status = STATUS_ERROR_NONE = 0x00
4 group = 0x00;
5 id = 0x00;
6-7 datalength
8-9 sequence number
10-11 headerCRC16
12-13 dataCRC16 (currently not used)
… payload
8-byte uint64_t Timestamp (for first sample in payload.)
4-byte uint32_t Channel list (bit set = channel present. Bit clear = channel not present.
8-byte uint64_t Sample Number (starting with first ADC scan in payload)
Number of ADC-Scan sample = (datalength 20) / (channellist*2)
*/
/// <summary>
/// this is the order of information in the packet
/// the enum value is the offset in bytes from the start of the packet
/// </summary>
public enum ByteIndex
{
Sync = 0,
Type = 1,
Command = 2,
Status = 3,
Group = 4,
Id = 5,
DataLength = 6,
SequenceNumber = 8,
HeaderCRC = 10,
DataCRC = 12,
TimeStamp = 14,
ChannelList = 22,
SampleNumber = 26,
DataStart = 34
}
public ushort SequenceNumber { get; }
public ulong TimeStamp { get; }
public ulong SampleNumber { get; }
public int[] Channels { get; }
/// <summary>
/// this is all data, not transformed into (short) yet, which requires 2's compliment
/// it is also for all channels in the realtime stream
/// </summary>
public ushort[] RtData { get; }
/// <summary>
/// constructors and decodes byte stream
///
/// </summary>
/// <param name="bytes"></param>
public RealtimeStreamDecoder(IReadOnlyList<byte> bytes)
{
if (bytes.Count > (int)ByteIndex.DataStart)
{
TimeStamp = GetULong(bytes, (int)ByteIndex.TimeStamp);
SequenceNumber = GetUShort(bytes, (int)ByteIndex.SequenceNumber);
SampleNumber = GetULong(bytes, (int)ByteIndex.SampleNumber);
RtData = new ushort[(bytes.Count - (int)ByteIndex.DataStart) / 2];
var uintChannelList = GetUINT(bytes, (int)ByteIndex.ChannelList);
var channels = new List<int>();
var ba = new BitArray(BitConverter.GetBytes(uintChannelList));
for (var i = 0; i < ba.Length; i++)
{
if (ba.Get(i))
{
channels.Add(i);
}
}
Channels = channels.ToArray();
for (var idx = 0; idx < RtData.Length; idx++)
{
//all the data is stored as uint16 with channel order so
// AABBCCDDEEFF, so split out the ushorts
RtData[idx] = GetRTUShort(bytes, (int)(ByteIndex.DataStart + idx * 2));
}
}
else
{
RtData = new ushort[0];
SampleNumber = 0;
Channels = new int[0];
}
}
/// <summary>
/// returns a ulong given a sequence of bytes and a starting offset in bytes
/// that the ulong starts at
/// SLICE header bytes are sent in reverse order so we have to fix them first
/// </summary>
/// <param name="bytes"></param>
/// <param name="offset"></param>
/// <returns></returns>
private static ulong GetULong(IReadOnlyList<byte> bytes, int offset)
{
return (ulong)bytes[offset + 7] << 0 |
(ulong)bytes[offset + 6] << 8 |
(ulong)bytes[offset + 5] << 16 |
(ulong)bytes[offset + 4] << 24 |
(ulong)bytes[offset + 3] << 32 |
(ulong)bytes[offset + 2] << 40 |
(ulong)bytes[offset + 1] << 48 |
(ulong)bytes[offset + 0] << 56;
}
/// <summary>
/// gets a ushort from the response parameters
/// SLICE header bytes are sent in reverse order, so we have to fix
/// them first
/// </summary>
/// <param name="bytes"></param>
/// <param name="offset"></param>
/// <returns></returns>
private static uint GetUINT(IReadOnlyList<byte> bytes, int offset)
{
return (uint)bytes[offset + 3] << 0 |
(uint)bytes[offset + 2] << 8 |
(uint)bytes[offset + 1] << 16 |
(uint)bytes[offset + 0] << 32;
}
/// <summary>
/// returns a ushort given a sequence of bytes and a starting offset in bytes
/// SLICE header bytes are sent in reverse order, so we have to fix the byte
/// order first
/// </summary>
/// <param name="bytes"></param>
/// <param name="offset"></param>
/// <returns></returns>
private static ushort GetUShort(IReadOnlyList<byte> bytes, int offset)
{
return (ushort)(bytes[offset + 1] |
bytes[offset + 0] << 8);
}
/// <summary>
/// returns a ushort given a sequence of bytes and a starting offset in bytes
/// note that DATA is not in reverse order unlike the header, so we
/// DON'T need to fix the order
/// </summary>
/// <param name="bytes"></param>
/// <param name="offset"></param>
/// <returns></returns>
private static ushort GetRTUShort(IReadOnlyList<byte> bytes, int offset)
{
return (ushort)(bytes[offset + 0] |
bytes[offset + 1] << 8);
}
}
}

View File

@@ -0,0 +1,290 @@
using System;
using System.Linq;
using DTS.Common.Constant;
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.SLICE.RealtimeCommands
{
/// <summary>
/// this "command" gets the next set of samples
/// note that in streaming mode no actual command is sent, we just pick up whatever data is in the RECV buffer
/// </summary>
public class RealtimeStreamingNextSamples : CommandBase, IGetRealtimeSamples
{
public RealtimeStreamingNextSamples(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
baseCommand = new CommandPacket();
}
public RealtimeStreamingNextSamples(DTS.Common.Interface.DASFactory.ICommunication sock, int msTimeout)
: base(sock, msTimeout)
{
baseCommand = new CommandPacket();
}
public bool DigitalInput { get; set; }
public bool[] TransitionMode { get; set; }
/// <summary>
/// the realtime data sample data
/// </summary>
public RealtimeStreamDecoder RtData { get; private set; }
/// <summary>
/// these are both used in garbage packet detection
/// sample numbers are always increasing, sequence numbers are increasing but wrap around at ushort.max
/// we could just use only sample number and not sample number and sequence
/// for the garbage packet detection, but the number of samples per packet can be variable
/// (especially with different SPS and number of channels) while the sequence is always +1 increase
/// </summary>
private ulong _lastProcessedSampleNumber = ulong.MaxValue;
private ushort _lastSequenceNumber = 0;
/// <summary>
/// this is a fairly arbitrary choice, this is just used for garbage packet detection
/// in practical use we are unlikely to drop more than 50 sequence at 2k sps
/// so if we have more than a 1k difference there's a good chance the packet is garbage
/// </summary>
private const int MAX_SEQUENCE_DELTA = 1000;
public bool SignedData { get; set; } = false;
public void ProcessData()
{
try
{
#if REALTIME_LOGGING
var dt = DateTime.Now;
System.Diagnostics.Trace.WriteLine($"{dt.Hour}:{dt.Minute}:{dt.Second}.{dt.Millisecond} ProcessData");
#endif
RtData = null;
System.Threading.Thread.Sleep(20);
var bytes = baseResponse.ToBytes();
var headerCRC = response.HeaderCRC;
response.ComputeCRCs();
if (response.HeaderCRC != headerCRC)
{
//garbage packet, don't process
return;
}
if (!bytes.Any()) return;
RtData = new RealtimeStreamDecoder(bytes);
var bFailsSequenceCheck = false;
var sequenceDelta = Math.Abs(RtData.SequenceNumber - _lastSequenceNumber);
if (RtData.SequenceNumber != 0 && sequenceDelta > MAX_SEQUENCE_DELTA)
{
#if REALTIME_LOGGING
System.Diagnostics.Trace.WriteLine("garbage packet");
#endif
//crc matched, but we've still got a garbage packet, as determined by looking at the sequence #
bFailsSequenceCheck = true;
}
_lastSequenceNumber = RtData.SequenceNumber;
if (null == RtData || RtData.SampleNumber == _lastProcessedSampleNumber || bFailsSequenceCheck)
{
#if REALTIME_LOGGING
if (RtData.SampleNumber == _lastProcessedSampleNumber)
{
System.Diagnostics.Trace.WriteLine("Same packet #");
}
#endif
RtData = null;
return;
}
#if REALTIME_LOGGING
System.Diagnostics.Trace.WriteLine($"Processed: {RtData.SampleNumber}");
#endif
_lastProcessedSampleNumber = RtData.SampleNumber;
ChannelData = new short[Channels][];
for (var idx = 0; idx < Channels; idx++)
{
ChannelData[idx] = new short[SamplesReturned];
}
int insertPt;
short adc;
for (var i = 0; i < RtData.RtData.Length; i++)
{
//data is in the form of ABC where A is channel 0, and C is channel 2, so we can
//figure out the channel idx using modulo
var channelIdx = RtData.Channels[i % RtData.Channels.Length];
//per other realtime functions, we transform the ushort to a short
if (SignedData) { adc = (short)RtData.RtData[i]; }
else { adc = (short)(RtData.RtData[i] - 0x8000); }
//data is in the form of ABC, so what sample we are on from the first sample can be computed
//by dividing by the number of channels in the response
insertPt = Convert.ToInt32(Math.Floor((double)i / RtData.Channels.Length));
ChannelData[channelIdx][insertPt] = adc;
}
}
catch (Exception)
{
#if REALTIME_LOGGING
System.Diagnostics.Trace.WriteLine(ex.Message);
#endif
RtData = null;
}
}
/// <inheritdoc />
/// <summary>
/// the sample number of the first sample in the data stream
/// </summary>
public ulong SampleNumber => RtData.SampleNumber;
public ulong TimeStamp => RtData.TimeStamp;
public ulong SequenceNumber => RtData.SequenceNumber;
/// <summary>
/// All channel data for the DAS, note that if a channel is not in realtime
/// then it's short data values or not defined
/// this is all channels.
/// </summary>
private short[][] ChannelData { get; set; }
/// <inheritdoc />
/// <summary>
/// gets the sample data for a given channel index
/// </summary>
/// <param name="zeroBasedChannel"></param>
/// <returns></returns>
public short[] GetChannelData(int zeroBasedChannel)
{
return ChannelData[zeroBasedChannel];
}
/// <inheritdoc />
/// <summary>
/// this is the total number of channels on the DAS
/// this is used to build ChannelData
/// </summary>
public ushort Channels { get; set; }
/// <inheritdoc />
/// <summary>
/// count of how many samples have been performed
/// if data is ABCABCABC, then there are 3 samples returned
/// </summary>
public int SamplesReturned => RtData?.RtData.Length / RtData?.Channels.Length ?? 0;
/// <summary>
/// We need to override SyncExecute because we don't want to send anything. 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);
}
if (baseResponse.Status != DFConstantsAndEnums.CommandStatus.StatusNoError)
{
// didn't go well
var msg = MakeLogString("SyncExecute: response.Status = " + baseResponse.Status);
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);
APILogger.Log(ex);
}
// everything is fine, let it exit
if (LogCommands)
{
LogCommand(false);
}
break;
case CommunicationConstantsAndEnums.CommunicationResult.ReceiveFailed:
{
var msg = MakeLogString("SyncExecute: ComReport.Result == " + ComReport.Result);
LogCommand(false);
throw new CommandException(CommandErrorReason.ReceiveFailed, msg);
}
case CommunicationConstantsAndEnums.CommunicationResult.ReceiveTimeout:
{
var msg = MakeLogString("SyncExecute: ComReport.Result == " + ComReport.Result);
LogCommand(false);
throw new CommandException(CommandErrorReason.ReceiveFailed, msg);
}
case CommunicationConstantsAndEnums.CommunicationResult.SendFailed:
case CommunicationConstantsAndEnums.CommunicationResult.SendTimeout:
{
var msg = MakeLogString("SyncExecute: ComReport.Result == " + ComReport.Result);
LogCommand(false);
throw new CommandException(CommandErrorReason.SendFailed, msg);
}
default:
{
var msg = MakeLogString("SyncExecute: Unknown ComReport.Result == " + ComReport.Result);
LogCommand(false);
throw new Exception(msg);
}
}
}
finally
{
recorder.ExecuteIsBusy = false;
ProcessData();
}
}
}
}

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using DTS.Common.Enums.DASFactory;
using DTS.Common.ICommunication;
using DTS.Common.Utilities;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class RetrieveSampleAverage : RealtimeCommandBase
{
protected override Commands _Command => Commands.RetrieveSampleAverage;
const int SAMPLES_POSITION = 0;
const int DATA_POSITION = 2;
private ushort _samples;
public ushort Samples
{
set { _samples = value; command.SetParameter(SAMPLES_POSITION, _samples); }
get => _samples;
}
public short[] _data;
public short GetChannelData(int zeroBasedChannel)
{
return _data[zeroBasedChannel];
}
public ushort Channels
{
get
{
if (null == _data) return 0;
return (ushort)_data.Length;
}
}
public RetrieveSampleAverage(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
command.Parameter = new byte[DATA_POSITION];
}
public RetrieveSampleAverage(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec)
{
command.Parameter = new byte[DATA_POSITION];
}
protected override CommandReceiveAction WholePackage()
{
if (response.Status != DFConstantsAndEnums.CommandStatus.StatusNoError)
return CommandReceiveAction.StopReceiving;
// Figure out how many samples were actually used to compute the means --
// we may have requested more than the recorder has RAM to store
response.GetParameter(SAMPLES_POSITION, out _samples);
// Figure out the number of channels returned and grab the means
var numChannels = (response.Parameter.Length - 2) / 2;
_data = new short[numChannels];
for (var i = 0; i < numChannels; i++)
{
response.GetParameter(2 * i + DATA_POSITION, out _data[i]);
}
return CommandReceiveAction.StopReceiving;
}
public override void CommandToString(ref List<List<string>> lines)
{
base.CommandToString(ref lines);
lines.Add(new List<string> { $"Samples Requested: {Samples}" });
}
public override void ResponseToString(ref List<List<string>> lines)
{
base.ResponseToString(ref lines);
lines.Add(new List<string>
{
$"Channels: {Channels}, Samples Used: {Samples}, Data: {ArrayToString.ArrayObjectToString(_data)}"
});
}
}
}

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using DTS.Common.Enums.DASFactory;
using DTS.Common.ICommunication;
using DTS.Common.Utilities;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class RetrieveSingleSample : RealtimeCommandBase
{
protected override Commands _Command => Commands.RetrieveSingleSample;
public short[] _data;
public short GetChannelData(int zeroBasedChannel)
{
return _data[zeroBasedChannel];
}
public ushort Channels
{
get
{
if (null == _data) return 0;
return (ushort)_data.Length;
}
}
public RetrieveSingleSample(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
}
public RetrieveSingleSample(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec)
{
}
protected override CommandReceiveAction WholePackage()
{
try
{
if (response.Status == DFConstantsAndEnums.CommandStatus.StatusNoError)
{
// Figure out the number of samples returned
var unsignedData = new ushort[response.Parameter.Length / 2];
_data = new short[response.Parameter.Length / 2];
for (var i = 0; i < response.Parameter.Length / 2; i++)
{
response.GetParameter(2 * i, out unsignedData[i]);
if (unsignedData[i] > 32768)
{
_data[i] = (short)(unsignedData[i] - 65536);
}
else
{
_data[i] = (short)unsignedData[i];
}
}
return CommandReceiveAction.StopReceiving;
}
}
catch { }
return CommandReceiveAction.StopReceiving;
}
public override void ResponseToString(ref List<List<string>> lines)
{
base.ResponseToString(ref lines);
lines.Add(new List<string> { $"Channels: {Channels}, Data: {ArrayToString.ArrayObjectToString(_data)}" });
}
}
}

View File

@@ -0,0 +1,38 @@
using DTS.Common.ICommunication;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class StartRealtimeMode : RealtimeCommandBase
{
private bool _bSupportsMultipleSampleRealtime = true;
public bool SupportsMultipleSampleRealtime
{
get => _bSupportsMultipleSampleRealtime;
set
{
_bSupportsMultipleSampleRealtime = value;
if (value) { command.Parameter = new byte[1]; }
else { command.Parameter = new byte[0]; }
}
}
protected override Commands _Command => Commands.StartRealtimeMode;
public StartRealtimeMode(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
// don't need to put anything in here ... its presense
// tells the firmware that we support multiple-sample
// realtime
command.Parameter = new byte[1];
}
public StartRealtimeMode(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec)
{
// don't need to put anything in here ... its presense
// tells the firmware that we support multiple-sample
// realtime
command.Parameter = new byte[1];
}
}
}

View File

@@ -0,0 +1,91 @@
using DTS.Common.ICommunication;
using System.Collections.Generic;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
public class StartTimeStampStreamMode : RealtimeCommandBase
{
private bool _bSupportsIrigTimeStampSampleRealtime = true;
public bool SupportsMultipleSampleRealtime
{
get { return _bSupportsIrigTimeStampSampleRealtime; }
set
{
_bSupportsIrigTimeStampSampleRealtime = value;
if (value) { command.Parameter = new byte[1]; }
else { command.Parameter = new byte[0]; }
}
}
protected override Commands _Command => Commands.StartTimeStampStreamMode;
public StartTimeStampStreamMode(DTS.Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
MinimumProtocolVersion = sock.GetMinProto(Common.Enums.DASFactory.DFConstantsAndEnums.ProtocolLimitedCommands.UDPRealtimeStream);
}
public StartTimeStampStreamMode(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
: base(sock, timeoutMillisec)
{
MinimumProtocolVersion = sock.GetMinProto(Common.Enums.DASFactory.DFConstantsAndEnums.ProtocolLimitedCommands.UDPRealtimeStream);
}
private byte[] _paramsToSend;
public byte[] ParamsToSend
{
get => _paramsToSend;
set
{
_paramsToSend = value;
command.Parameter = new byte[value.Length];
for (var i = 0; i < value.Length; i++)
{
command.SetParameter(i, _paramsToSend[i]);
}
}
}
public override void CommandToString(ref List<List<string>> list)
{
base.CommandToString(ref list);
list.Add(new List<string>(new[] { $"ParamsToSend: {System.Text.Encoding.UTF8.GetString(ParamsToSend)}" }));
}
}
/// <summary>
/// this puts the firmware into realtime streaming mode
/// in this mode the firmware will constantly send out realtime data
/// </summary>
public class StartRealtimeStreamingMode : RealtimeCommandBase
{
protected override Commands _Command => Commands.StartRealtimeStreamingMode;
public StartRealtimeStreamingMode(DTS.Common.Interface.DASFactory.ICommunication sock, byte[] channelList)
: base(sock)
{
ChannelList = channelList;
}
public StartRealtimeStreamingMode(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec, byte[] channelList)
: base(sock, timeoutMillisec)
{
ChannelList = channelList;
}
private byte[] _channelList;
/// <summary>
/// channels to collect data on, each channel should be represented as a byte
/// </summary>
public byte[] ChannelList
{
get => _channelList;
set
{
_channelList = value;
command.Parameter = new byte[value.Length];
for (var i = 0; i < value.Length; i++)
{
command.SetParameter(i, value[i]);
}
}
}
}
}

View File

@@ -0,0 +1,307 @@
using System;
using System.Collections.Generic;
using DTS.Common.Enums.DASFactory;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
/// <summary>
/// these classes was ported from FWTU
/// </summary>
public class StreamConfigUDPGet : RealtimeCommandBase
{
private const int COMMAND_PAYLOAD_SIZE = 65;
protected override Commands _Command => Commands.I106StreamConfigGet;
private byte _stream_profile_number;
private byte[] _udpipport;
private ushort _timechannelid;
private ushort _datachannelid;
private uint _tmnsPcmSubFrameId; // _tmns0;
private uint _tmnsMsgId; // _tmns1;
private uint _tmnsPcmMinorPerMajor; // _tmns2;
private uint _tmnsTmatsPortNumber; // _tmns3;
private uint _ienaUdpSrcPortNumber; // _tmns4;
private uint _udpRsv1; // _tmns5;
private uint _udpRsv2; // _tmns6;
private uint _udpRsv3; // _tmns7;
public byte Stream_Profile_Number
{
get => _stream_profile_number;
set { _stream_profile_number = value; command.SetParameter(0, _stream_profile_number); }
}
public byte[] UdpIpPort
{
get { return _udpipport; }
set { _udpipport = value; command.SetParameter(1, _udpipport); }
}
public ushort TimeChannelID
{
get => _timechannelid;
set { _timechannelid = value; command.SetParameter(29, _timechannelid); }
}
public ushort DataChannelID
{
get => _datachannelid;
set { _datachannelid = value; command.SetParameter(31, _datachannelid); }
}
public uint TMNS_PCMSUBFRAMEID
{
get => _tmnsPcmSubFrameId;
set
{
_tmnsPcmSubFrameId = value;
command.SetParameter(33, _tmnsPcmSubFrameId);
}
}
public uint TMNS_MSGID
{
get { return _tmnsMsgId; }
set
{
_tmnsMsgId = value; command.SetParameter(37, _tmnsMsgId);
}
}
public uint TMNS_PCMINORPERMAJOR
{
get => _tmnsPcmMinorPerMajor;
set
{
_tmnsPcmMinorPerMajor = value; command.SetParameter(41, _tmnsPcmMinorPerMajor);
}
}
public uint TMNS_TMATSPORTNUMBER
{
get => _tmnsTmatsPortNumber;
set
{
_tmnsTmatsPortNumber = value; command.SetParameter(45, _tmnsTmatsPortNumber);
}
}
public uint IENAUDP_PortNumber
{
get => _ienaUdpSrcPortNumber;
set
{
_ienaUdpSrcPortNumber = value; command.SetParameter(49, _ienaUdpSrcPortNumber);
}
}
public uint TMNS5
{
get => _udpRsv1;
set
{
_udpRsv1 = value; command.SetParameter(53, _udpRsv1);
}
}
public uint TMNS6
{
get => _udpRsv2;
set
{
_udpRsv2 = value; command.SetParameter(57, _udpRsv2);
}
}
public uint TMNS7
{
get => _udpRsv3;
set
{
_udpRsv3 = value; command.SetParameter(61, _udpRsv3);
}
}
public StreamConfigUDPGet(Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
command.Parameter = new byte[COMMAND_PAYLOAD_SIZE];
}
public StreamConfigUDPGet(Common.Interface.DASFactory.ICommunication sock, int TimeoutMillisec)
: base(sock, TimeoutMillisec)
{
command.Parameter = new byte[COMMAND_PAYLOAD_SIZE];
}
protected override CommandReceiveAction WholePackage()
{
if (response.Status == DFConstantsAndEnums.CommandStatus.StatusNoError)
{
if (response.ParameterLength > 0)
{
response.GetParameter(0, out _stream_profile_number);
_udpipport = new byte[29];
for (int i = 0; i < 28; i++)
{
response.GetParameter(1 + i, out _udpipport[i]);
}
response.GetParameter(29, out _timechannelid);
response.GetParameter(31, out _datachannelid);
response.GetParameter(33, out _tmnsPcmSubFrameId);
response.GetParameter(37, out _tmnsMsgId);
response.GetParameter(41, out _tmnsPcmMinorPerMajor);
response.GetParameter(45, out _tmnsTmatsPortNumber);
response.GetParameter(49, out _ienaUdpSrcPortNumber);
response.GetParameter(53, out _udpRsv1);
response.GetParameter(57, out _udpRsv2);
response.GetParameter(61, out _udpRsv3);
}
else { _stream_profile_number = 0; }
}
return CommandReceiveAction.StopReceiving;
}
public override void ResponseToString(ref List<List<string>> lines)
{
base.ResponseToString(ref lines);
lines.Add(new List<string>() { $"Stream Profile Number: {_stream_profile_number}" });
lines.Add(new List<string>() { $"UDP IP PORT: {_udpipport}" });
lines.Add(new List<string>() { $"Time Channel ID: {_timechannelid}" });
lines.Add(new List<string>() { $"Data Channel ID: {_timechannelid}" });
lines.Add(new List<string>() { $"TMNS: {_tmnsPcmSubFrameId}, {_tmnsMsgId}, {_tmnsPcmMinorPerMajor}, {_tmnsTmatsPortNumber}, {_ienaUdpSrcPortNumber}, {_udpRsv1}, {_udpRsv2}, {_udpRsv3}," });
}
}
public class StreamConfigUDPSet : RealtimeCommandBase
{
const int COMMAND_PAYLOAD_SIZE = 74;
protected override RealtimeCommandBase.Commands _Command
{
get { return Commands.I106StreamConfigSet; }
}
private byte _stream_profile_number;
private byte[] _udpipport;
private ushort _irig106config0;
private ushort _irig106config1;
private uint _tmnsPcmSubFrameId; // _tmns0;
private uint _tmnsMsgId; // _tmns1;
private uint _tmnsPcmMinorPerMajor; // _tmns2;
private uint _tmnsTmatsPortNumber; // _tmns3;
private uint _ienaUdpSrcPortNumber; // _tmns4;
private uint _udpRsv1; // _tmns5;
private uint _udpRsv2; // _tmns6;
private uint _udpRsv3; // _tmns7;
public byte Stream_Profile_Number
{
get => _stream_profile_number;
set { _stream_profile_number = value; command.SetParameter(0, _stream_profile_number); }
}
public byte[] UdpIpPort
{
get => _udpipport;
set
{
_udpipport = value;
command.SetParameter(1, _udpipport);
}
}
public ushort Irig106Config0
{
get => _irig106config0;
set
{
_irig106config0 = value; command.SetParameter(29, _irig106config0);
}
}
public ushort Irig106Config1
{
get => _irig106config1;
set
{
_irig106config1 = value; command.SetParameter(31, _irig106config1);
}
}
public uint TMNS_PCMSubFrameId
{
get => _tmnsPcmSubFrameId;
set
{
_tmnsPcmSubFrameId = value; command.SetParameter(33, _tmnsPcmSubFrameId);
}
}
public uint TMNS_MsgId
{
get => _tmnsMsgId;
set
{
_tmnsMsgId = value; command.SetParameter(37, _tmnsMsgId);
}
}
public uint TMNS_PCMMinorPerMajor
{
get => _tmnsPcmMinorPerMajor;
set
{
_tmnsPcmMinorPerMajor = value; command.SetParameter(41, _tmnsPcmMinorPerMajor);
}
}
public uint TMNS_TMATSPortNumber
{
get => _tmnsTmatsPortNumber;
set
{
_tmnsTmatsPortNumber = value; command.SetParameter(45, _tmnsTmatsPortNumber);
}
}
public uint IENAUDP_PortNumber
{
get => _ienaUdpSrcPortNumber;
set
{
_ienaUdpSrcPortNumber = value; command.SetParameter(49, _ienaUdpSrcPortNumber);
}
}
public uint TMNS5
{
get => _udpRsv1;
set
{
_udpRsv1 = value; command.SetParameter(53, _udpRsv1);
}
}
public uint TMNS6
{
get => _udpRsv2;
set
{
_udpRsv2 = value; command.SetParameter(57, _udpRsv2);
}
}
public uint TMNS7
{
get { return _udpRsv3; }
set
{
_udpRsv3 = value; command.SetParameter(61, _udpRsv3);
}
}
public StreamConfigUDPSet(Common.Interface.DASFactory.ICommunication sock)
: base(sock)
{
command.Parameter = new byte[COMMAND_PAYLOAD_SIZE];
}
public StreamConfigUDPSet(Common.Interface.DASFactory.ICommunication sock, int TimeoutMillisec)
: base(sock, TimeoutMillisec)
{
command.Parameter = new byte[COMMAND_PAYLOAD_SIZE];
}
public override void ResponseToString(ref List<List<string>> lines)
{
base.ResponseToString(ref lines);
lines.Add(new List<string>() { $"Stream Profile Number: {_stream_profile_number}" });
lines.Add(new List<string>() { $"UDP IP PORT: {_udpipport}" });
lines.Add(new List<string>() { $"Irig Config: {_irig106config0}" });
lines.Add(new List<string>() { $"TMNS: {_tmnsPcmSubFrameId}, {_tmnsMsgId}, {_tmnsPcmMinorPerMajor}, {_tmnsTmatsPortNumber}, {_ienaUdpSrcPortNumber}, {_udpRsv1}, {_udpRsv2}, {_udpRsv3}," });
}
}
}

View File

@@ -0,0 +1,199 @@
using DTS.Common.Utilities.Logging;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using DTS.Common.Enums;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
/// <summary>
/// this class was ported from FWTU
/// </summary>
public class StreamReaderUDP
{
public string StreamAddress { get; }
/// <summary>
/// appears to be the parameters (line) sent to the start command
/// </summary>
public byte[] cmdline { get; set; }
private Socket _udpSocket { get; set; }
/// <summary>
/// IP of our receive endpoint. Either 0.0.0.0 or a specified adapter's IP
/// FB15531: Follow pattern for multicast AutoDiscovery in StreamReader
/// </summary>
public string HostIPAddress { get; set; } = IPAddress.Any.ToString();
public void CloseSocket()
{
try
{
_udpSocket.Close();
}
catch (Exception e)
{
APILogger.LogException(e);
}
}
public UDPStreamProfile UDPStreamType
{
get;
set;
}
private EndPoint _uDPEndpoint;
public EndPoint UDPEndpoint
{
get => _uDPEndpoint;
set => _uDPEndpoint = value;
}
public ulong UDPSampleNumber { get; set; }
public StreamReaderUDP(string streamAddress, string hostAddress, UDPStreamProfile uDPStreamType, byte[] channels)
{
StreamAddress = streamAddress.TrimEnd('/');
UDPSampleNumber = 0;
UDPStreamType = uDPStreamType;
Channels = channels;
HostIPAddress = hostAddress;
Configure();
}
public byte[] Channels { get; set; } = new byte[0];
private void Configure()
{
// setup parameter for command
var channelMaskAndReserved = 0; // default to all channels.
//I'm not sure this is supported yet...
//if (Channels.Any())
//{
// foreach (var ch in Channels)
// {
// channelMaskAndReserved |= 1 << ch;
// }
//}
var channelList = BitConverter.GetBytes(channelMaskAndReserved);
// create parameter for streaming command.
cmdline = new byte[4 + StreamAddress.Length];
var paramNetAddr = Encoding.ASCII.GetBytes(StreamAddress);
Buffer.BlockCopy(channelList, 0, cmdline, 0, channelList.Length);
Buffer.BlockCopy(paramNetAddr, 0, cmdline, channelList.Length,
paramNetAddr
.Length); // System.Buffer.BlockCopy(netAddr.ToArray(), 0, cmdline, channelList.Length, netAddr.Length);
// get IP and port udp://239.1.2.10:portID
var parts = StreamAddress.Split(':');
if (parts.Length != 3)
{
throw new Exception($"Invalid UDP address:{StreamAddress}");
}
// remove '//' or '/' from IP and port
var udphost = parts[1].Trim('/');
var udpport = parts[2].Trim('/');
_udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
var iep = IPAddress.TryParse(HostIPAddress, out var address) ? new IPEndPoint(address, Convert.ToUInt16(udpport)) : new IPEndPoint(IPAddress.Any, Convert.ToUInt16(udpport));
UDPEndpoint = iep;
try
{
_udpSocket.Bind(iep);
}
catch (Exception e)
{
APILogger.LogException(e);
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 2000);
_udpSocket.Bind(iep); // retry again.
}
// check for udp broadcast
var ipChecks = udphost.Split('.');
var ipV4Check = Convert.ToInt32(ipChecks[0]);
if ((ipV4Check >= 224) & (ipV4Check <= 239))
{
var mcastOption = new MulticastOption(IPAddress.Parse(udphost));
if (iep.Address != IPAddress.Any)
{
mcastOption = new MulticastOption(IPAddress.Parse(udphost), iep.Address);
}
_udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, mcastOption); // "239.1.2.10")));
}
//set timer for recv_socket
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 2000);
}
/// <summary>
/// receives any packets waiting
/// returns null if no information is ready, otherwise a structure filled with information
/// </summary>
/// <returns></returns>
public UDPStreamPacket Read()
{
//receive data
var databuf = new byte[2000];
var recv = _udpSocket.ReceiveFrom(databuf, ref _uDPEndpoint);
if (recv <= 0)
{
return null;
}
var data = new byte[recv];
Buffer.BlockCopy(databuf, 0, data, 0, recv);
var udpData = new UDPRealtimeByteConverter(data);
var udpStreamPacket = new UDPStreamPacket();
udpStreamPacket.ChannelData = new short[Channels.Length][];
//fun fact, sending all channels right now, so regardless of how many were configured to send, use this
var numChannels = 6;
//calculate how many samples are in the packet, but use the floor incase it's not complete
var numSamples = Convert.ToUInt64(Math.Floor((double)udpData.RtData.Length / numChannels));
for (var i = 0; i < Channels.Length; i++)
{
udpStreamPacket.ChannelData[i] = new short[numSamples];
}
//forward declarations to reduce a little churn
byte channel = 0;
var list = Channels.ToList();
var sampleIndex = 0UL;
short adc = 0;
for (var i = 0; i < udpData.RtData.Length; i++)
{
//which channel this sample is for
channel = Convert.ToByte(i % numChannels);
//which channel in the list this is for
var channelIdx = list.IndexOf(channel);
//channel is not in list, don't care about this sample
if (channelIdx < 0) { continue; }
//which sample in a sequence of multiple samples this is
sampleIndex = Convert.ToUInt64(Math.Floor((double)i / numChannels));
//we should have all complete packets, but if there's an incomplete round
//this will skip it
if (sampleIndex >= numSamples) { continue; }
//add the sample into the list of samples
adc = (short)udpData.RtData[i];
udpStreamPacket.ChannelData[channelIdx][sampleIndex] = adc;
}
udpStreamPacket.PTPTimesec = Convert.ToUInt32(udpData.PtpTimeStampSec);
udpStreamPacket.PTPTimeNsec = Convert.ToUInt32(udpData.PtpTimeStampNsec);
udpStreamPacket.PTPSyncStatusError = Convert.ToBoolean(udpData.PacketFlags & 0x20);
udpStreamPacket.ADCOverflowStatus = Convert.ToBoolean(udpData.PacketFlags & 0x10);
udpStreamPacket.TimeStamp = 0L;
//packet does not contain a sample number, so we calculate it
udpStreamPacket.SampleNumber = udpData.SequenceNumber * numSamples;
return udpStreamPacket;
}
}
}

View File

@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
/// <summary>
/// this class was ported from FWTU RealtimeCommands, it appears almost verbatim to there
/// </summary>
public class UDPRealtimeByteConverter
{
public enum UDP_BYTE_INDEX
{
PKT_PATTERN = 0, // u16
CHANNEL_ID = 2, // u16
PKT_LEN = 4, // u32
DATA_LEN = 8, // u32
DATA_TYPE_VER = 12, // u8
SEQ_NUMBER = 13, // u8
PKT_FLAGS = 14, // u8
DATA_TYPE = 15, // u8
REL_TIME32 = 16, // u32 of 48
REL_TIME16 = 20, // u16
HDR_CRC16 = 22, // u16
PTP_U32_LSW = 24, // u32
PTP_U32_MSW = 28, // u32
RSV_U16 = 32, // u32
HDR2CRC16 = 34, // u16
CSDW = 36, // u32
DATA_START = 40, // data start address
}
// This is list of data type for UDP to support
public enum UDP_DATA_TYPE
{
// 0x21 for analog format 1. 0x01 = CGDP format 1 (setup record). 0x12 = Time Data, Format 2 (PTP time)
CGDP_TYPE = 0x01, // Setup XML record. 1. Setup Record, periodic message sent out.
TIME_DATA_PTP = 0x12, // Time data, format 2. Periodic message.
ANALOG_DATA_FORMAT_1 = 0x21, // Analog data format 3. Periodic message.
}
public ushort PacketPattern { get; private set; }
public ushort DataChannelID { get; private set; }
public uint PacketLength { get; private set; }
/// <summary>
/// careful if you use this, it's not the same as
/// RtData.Length ...
/// </summary>
public uint DataLength { get; private set; }
public byte DataTypeVersion { get; private set; }
public byte SequenceNumber { get; private set; }
public byte PacketFlags { get; private set; }
public byte DataType { get; private set; }
public uint RelativeTime32 { get; private set; }
public ushort RelativeTime16 { get; private set; }
public ushort HdrCrc16 { get; private set; }
public uint PtpTimeStampSec { get; private set; }
public uint PtpTimeStampNsec { get; private set; }
public uint Hdr2Crc16 { get; private set; }
public uint ChannelMask { get; private set; }
public ulong UdpSampleCount { get; private set; }
public ushort[] RtData { get; private set; }
public uint[] Channels { get; private set; }
public byte SequenceNumberPrev { get; private set; }
public UDPRealtimeByteConverter(byte[] bytes)
{
// TBD: Currently expect 36byte header. If simplified version with 24Byte,
// we have to account for the right offset. RTC will be used then.
PacketPattern = GetUShort(bytes, (int)UDP_BYTE_INDEX.PKT_PATTERN);
DataChannelID = GetUShort(bytes, (int)UDP_BYTE_INDEX.CHANNEL_ID);
PacketLength = GetUInt(bytes, (int)UDP_BYTE_INDEX.PKT_LEN);
DataLength = GetUInt(bytes, (int)UDP_BYTE_INDEX.DATA_LEN);
DataTypeVersion = GetByte(bytes, (int)UDP_BYTE_INDEX.DATA_TYPE_VER);
SequenceNumber = GetByte(bytes, (int)UDP_BYTE_INDEX.SEQ_NUMBER);
PacketFlags = GetByte(bytes, (int)UDP_BYTE_INDEX.PKT_FLAGS);
DataType = GetByte(bytes, (int)UDP_BYTE_INDEX.DATA_TYPE); // 0x21 for analog format 1. 0x01 = CGDP format 1 (setup record). 0x12 = Time Data, Format 2 (PTP time)
RelativeTime32 = GetUInt(bytes, (int)UDP_BYTE_INDEX.REL_TIME32);
RelativeTime16 = GetUShort(bytes, (int)UDP_BYTE_INDEX.REL_TIME16);
HdrCrc16 = GetUShort(bytes, (int)UDP_BYTE_INDEX.HDR_CRC16);
PtpTimeStampSec = GetUInt(bytes, (int)UDP_BYTE_INDEX.PTP_U32_MSW);
PtpTimeStampNsec = GetUInt(bytes, (int)UDP_BYTE_INDEX.PTP_U32_LSW);
Hdr2Crc16 = GetUShort(bytes, (int)UDP_BYTE_INDEX.HDR2CRC16);
if (DataType == (int)UDP_DATA_TYPE.ANALOG_DATA_FORMAT_1)
{
RtData = new ushort[(DataLength - 4) / 2]; // ushort[(bytes.Length - (int)UDP_BYTE_INDEX.DATA_START) / 2];
var mychannels = new List<uint>();
for (var i = 0; i < 6; i++) // var length = dataLength;
{
//if (ba.Get(i)) assume all 6 channels if UDP.
{
mychannels.Add(Convert.ToUInt32(i));
}
}
Channels = mychannels.ToArray();
UdpSampleCount = Convert.ToUInt64(RtData.Length / Channels.Length);
SequenceNumberPrev = SequenceNumber;
// TBD: check for pktFlag if the optional 2nd header is being used or not. If not,
// we can start the data from UDP_BYTE_INDEX.PTP_U32_LSW
for (var idx = 0; idx < RtData.Length; idx++)
{
RtData[idx] = GetRTUShort(bytes, (int)(UDP_BYTE_INDEX.DATA_START + idx * 2));
RtData[idx] ^= 0x8000;
}
}
else
{
// TBD for other packet types.
RtData = null;
}
}
private static ulong GetULong(byte[] bytes, int offset)
{
return (ulong)bytes[offset + 7] << 0 |
(ulong)bytes[offset + 6] << 8 |
(ulong)bytes[offset + 5] << 16 |
(ulong)bytes[offset + 4] << 24 |
(ulong)bytes[offset + 3] << 32 |
(ulong)bytes[offset + 2] << 40 |
(ulong)bytes[offset + 1] << 48 |
(ulong)bytes[offset + 0] << 56;
}
private static ushort GetUShort(byte[] bytes, int offset)
{
return (ushort)(bytes[offset + 1] |
bytes[offset + 0] << 8);
}
private static byte GetByte(byte[] bytes, int offset)
{
return bytes[offset];
}
private static uint GetUInt(byte[] bytes, int offset)
{
return (uint)bytes[offset + 3] << 0 |
(uint)bytes[offset + 2] << 8 |
(uint)bytes[offset + 1] << 16 |
(uint)bytes[offset + 0] << 24;
}
/// <summary>
/// data apparently has a different byte order than parameters, so we have to
/// rearrange byte order for parameters but not data.
/// </summary>
/// <param name="bytes"></param>
/// <param name="offset"></param>
/// <returns></returns>
private static ushort GetRTUShort(byte[] bytes, int offset)
{
return (ushort)(bytes[offset + 0] |
bytes[offset + 1] << 8);
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
namespace DTS.DASLib.Command.SLICE.RealtimeCommands
{
/// <summary>
/// ported from FWTU, appears almost verbatim to there
/// </summary>
public class UDPStreamPacket
{
/// <summary>
/// first array channels, second array sample index
/// </summary>
public short[][] ChannelData { get; set; }
public long TimeStamp { get; set; }
public ulong SampleNumber { get; set; }
public ulong SequenceNumber { get; set; }
public uint PTPTimesec { get; set; }
public uint PTPTimeNsec { get; set; }
public bool PTPSyncStatusError { get; set; }
public bool ADCOverflowStatus { get; set; }
public string PTPTimeString => string.Concat(PTPTimesec, ":", PTPTimeNsec.ToString("000000000"));
public UDPStreamPacket()
{
}
}
}