using DTS.Common; using DTS.Common.Utilities; using DTS.Common.Utilities.Logging; using DTS.Common.Utils; using DTS.Serialization.IRIGCH10.Packets; using System; using System.Collections.Generic; using System.IO; namespace DTS.Serialization.IRIGCH10 { /// /// this class is used to simplify parsing out CH10 data from a binary stream /// public class Chapter10File { /// /// used to hold transport stream headers as they are read /// private readonly List _transportHeaders = new List(); /// /// returns all transport headers that were read, in order they were read /// /// public ITransportStreamHeader[] GetTransportHeaders() { return _transportHeaders.ToArray(); } /// /// holds a list of packet headers as they are read /// private readonly List _packets = new List(); /// /// returns all packet headers that were read, in the order they were read in /// /// public IPacketHeader[] GetPacketHeaders() { return _packets.ToArray(); } /// /// holds any secondary time packets that were read /// private readonly List _secondaryTimePackets = new List(); /// /// returns all secondary time headers in the order they were read in /// /// public ISecondaryTimeFormatHeader[] GetSecondaryTimeHeaders() { return _secondaryTimePackets.ToArray(); } /// /// lookup of a packet header that was read in as well as the start of that packet and end of that packet amongst /// all bytes in the file /// private readonly Dictionary> _packetToByteStartStop = new Dictionary>(); private readonly byte[] _bytes = null; /// /// returns all bytes for a packet (as apposed to just the bytes for the packet header) /// all bytes returned are in their own buffer, independent of bytes read in /// /// /// public byte[] GetBytesForPacket(IPacketHeader packet) { if (!_packetToByteStartStop.ContainsKey(packet)) { return null; } var tuple = _packetToByteStartStop[packet]; var length = tuple.Item2 - tuple.Item1; var bytes = new byte[length]; Buffer.BlockCopy(_bytes, (int)tuple.Item1, bytes, 0, (int)length); return bytes; } public int GoodPackets { get; private set; } = 0; public int RejectedPackets { get; private set; } = 0; /// /// adds a time packet 1 packet to the byte stream /// private static void InsertTimePacket1(BinaryWriter bw, int seconds, int nanoseconds, ref byte timeSequenceNumber, int startSeconds, int startNanoSeconds) { //time packet 1 must always start on a second boundary, so we need to calulate the nearest start second to our //actual start time, and all further packets also need to be on that boundary var time = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)startSeconds, (decimal)startNanoSeconds).ToUniversalTime(); var currentTime = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)seconds, (decimal)nanoseconds).ToUniversalTime(); var newTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second); var tickDelta = time.Ticks - newTime.Ticks; var newRTC = GetRTC(tickDelta); var timePacket = new TimePacketFormat1(timeSequenceNumber, newTime, newRTC, nanoseconds, seconds); timeSequenceNumber++; bw.Write(timePacket.GetBytes()); } /// /// describes a function to get the next Int16 ADC sample from a binary file /// /// the index of the channel among channels in the test /// public delegate short GetNextSampleDelegate(int channelIdx); /// /// returns the number of samples for the channel /// /// the index of the channel among channels in the test /// public delegate long GetChannelLengthDelegate(int channelIndex); public static void WriteFile(string tmats, GetNextSampleDelegate getNextSample, GetChannelLengthDelegate getChannelLength, int totalChannels, int nanoseconds, int seconds, double sampleRate, bool includeSecondaryHeader, string fileName, TickEventHandler tickEventHandler, object tickObject) { var startNanoSeconds = nanoseconds; var startSeconds = seconds; if (System.IO.File.Exists(fileName)) { FileUtils.DeleteFileOrMove(fileName, APILogger.Log); } using (var ms = new FileStream(fileName, FileMode.OpenOrCreate)) { using (var bw = new BinaryWriter(ms)) { //simulate writing the transport bytes ... not needed for ch10 file though ... //first packet should be tmats var tmatsPacket = new TMATSPacket(nanoseconds, seconds, tmats, includeSecondaryHeader); bw.Write(tmatsPacket.GetBytes()); var currentSample = 0L; byte sequenceNumber = 0x01; byte timeSequenceNumber = 0x01; var lastSecondTransmitted = 0; if (!includeSecondaryHeader) { //insert first time packet at time second 1 InsertTimePacket1(bw, seconds, nanoseconds, ref timeSequenceNumber, startSeconds, startNanoSeconds); lastSecondTransmitted = seconds; } var channel0Length = getChannelLength(0); while (currentSample < channel0Length) { var newRTC = GetRTC(currentSample, sampleRate); if (includeSecondaryHeader) { var timePacket = new TimePacketFormat2(timeSequenceNumber, false, nanoseconds, seconds, newRTC, includeSecondaryHeader); var time = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)seconds, (decimal)nanoseconds).ToUniversalTime(); var newTime = new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second); timeSequenceNumber++; bw.Write(timePacket.GetBytes()); } else { if (seconds > lastSecondTransmitted) { lastSecondTransmitted = seconds; InsertTimePacket1(bw, seconds, nanoseconds, ref timeSequenceNumber, startSeconds, startNanoSeconds); } } var analogPacket = GetAnalogPacket(currentSample, getNextSample, channel0Length, totalChannels, nanoseconds, seconds, sampleRate, out var samplesProcessed, sequenceNumber, includeSecondaryHeader); bw.Write(analogPacket.GetBytes()); currentSample += samplesProcessed; sequenceNumber++; var elapsedNanoSeconds = samplesProcessed / (decimal)sampleRate * Constants.NANOS_PER_SECOND; var newNanos = nanoseconds + elapsedNanoSeconds; if (newNanos > Constants.NANOS_PER_SECOND) { while (newNanos >= Constants.NANOS_PER_SECOND) { seconds++; newNanos -= Constants.NANOS_PER_SECOND; } } nanoseconds = Convert.ToInt32(Math.Truncate(newNanos)); tickEventHandler?.Invoke(tickObject, 50D + 50D * currentSample / channel0Length); } } } } private static AnalogDataFormat1Packet GetAnalogPacket(long currentSample, GetNextSampleDelegate getNextSample, long channelLength, int totalChannels, int nanoseconds, int seconds, double sampleRate, out long samplesProcessed, byte sequenceNumber, bool includeSecondaryHeader) { var newRTC = GetRTC(currentSample, sampleRate); var numSamples = channelLength; numSamples -= currentSample; //we should target 100ms of data per packet var numSamplesPerPacket = Math.Floor(sampleRate / 10); if (numSamples > numSamplesPerPacket) { numSamples = Convert.ToInt64(numSamplesPerPacket); } var analogPacket = new AnalogDataFormat1Packet(nanoseconds, seconds, getNextSample, totalChannels, channelLength, newRTC, numSamples, currentSample, sequenceNumber, 3, includeSecondaryHeader); samplesProcessed = numSamples; return analogPacket; } //for convenience just store relative time counter 10MHZ conversion factor private const int RTC_PER_SECOND = 10000000; private static long GetRTC(long currentSample, double sampleRate) { var secondsPassed = currentSample / sampleRate; return AbstractDataPacket.BASE_RTC + Convert.ToInt64(secondsPassed * RTC_PER_SECOND); } private static long GetRTC(long tickDelta) { var seconds = (double)tickDelta / TimeSpan.TicksPerSecond; return AbstractDataPacket.BASE_RTC - Convert.ToInt64(seconds * RTC_PER_SECOND); } /// /// parses out all packets from array of bytes /// /// public Chapter10File(byte[] bytes) { _bytes = new byte[bytes.Length]; Buffer.BlockCopy(bytes, 0, _bytes, 0, bytes.Length); var currentByteIndex = 0L; var badPackets = 0; var goodPackets = 0; while (currentByteIndex < (bytes.Length - PacketHeader.PACKET_HEADER_LENGTH)) { try { currentByteIndex = ReadTransportHeader(bytes, currentByteIndex, out var transportHeader); var startOfChapter10Packet = currentByteIndex; currentByteIndex = ReadChapter10PacketHeader(bytes, currentByteIndex, out var packetHeader); if (packetHeader.PacketSyncPattern != PacketHeader.EXPECTED_SYNC_PATTERN || packetHeader.CheckSum != packetHeader.ComputeCheckSum()) { badPackets++; //this packet is invalid, so we need to skip ahead //skip until we see the next sync pattern, then backstep to the start of the transport bytes, then continue on currentByteIndex = GetIndexOfNextPacket(bytes, startOfChapter10Packet); continue; } _transportHeaders.Add(transportHeader); if (packetHeader.SecondaryHeaderPresent) { ReadChapter10SecondHeaderTimeFormat(bytes, startOfChapter10Packet + PacketHeader.PACKET_HEADER_LENGTH, out var secondaryTimeHeader); _secondaryTimePackets.Add(secondaryTimeHeader); } _packets.Add(packetHeader); _packetToByteStartStop[packetHeader] = new Tuple(startOfChapter10Packet, currentByteIndex); goodPackets++; } catch (Exception ex) { throw new Exception($"Hit exception processing file at byte: {currentByteIndex}, {ex.Message}", ex); } } GoodPackets = goodPackets; RejectedPackets = badPackets; } /// /// determines the next valid packet starting location after the provided starting spot /// this is used to skip over bad data to the next good packet /// /// /// /// public static long GetIndexOfNextPacket(byte[] bytes, long startIndex) { for (var i = startIndex; i < bytes.Length - 1; i++) { if (BitConverter.ToUInt16(bytes, (int)i) == PacketHeader.EXPECTED_SYNC_PATTERN) { return i; } } //if we got here there are no more valid packets, you are done. return bytes.Length; } /// /// reads a secondary time header from provided bytes and starting spot /// /// /// /// private void ReadChapter10SecondHeaderTimeFormat(byte[] bytes, long start, out ISecondaryTimeFormatHeader secondaryTimeFormat) { var headerBytes = new byte[SecondaryTimeFormatHeader.SECONDARY_TIME_HEADER_LENGTH]; Array.Copy(bytes, start, headerBytes, 0, SecondaryTimeFormatHeader.SECONDARY_TIME_HEADER_LENGTH); secondaryTimeFormat = new SecondaryTimeFormatHeader(headerBytes); } /// /// reads a transport header from provided bytes and starting spot /// /// /// /// /// private long ReadTransportHeader(byte[] bytes, long startIndex, out TransportStreamHeader transportHeader) { var transportHeaderBytes = new byte[4]; Array.Copy(bytes, startIndex, transportHeaderBytes, 0, 4); transportHeader = new TransportStreamHeader(transportHeaderBytes); return startIndex + 4L; } /// /// reads a Packet from stream of bytes at given starting spot /// /// /// /// /// public static long ReadChapter10PacketHeader(byte[] bytes, long startIndex, out IPacketHeader packetHeader) { var headerBytes = new byte[PacketHeader.PACKET_HEADER_LENGTH]; Array.Copy(bytes, startIndex, headerBytes, 0, PacketHeader.PACKET_HEADER_LENGTH); packetHeader = new PacketHeader(headerBytes); return startIndex + packetHeader.PacketLength; } } }