/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> public CommonFrameHeader(byte[] buffer, int startIndex) { // Validate SEL Fast Message data image if (buffer[startIndex] != Common.HeaderByte1 || buffer[startIndex + 1] != Common.HeaderByte2) { throw new InvalidOperationException($"Bad data stream, expected header bytes 0xA546 as first bytes in SEL Fast Message frame, got 0x{buffer[startIndex].ToString("X").PadLeft(2, '0')}{buffer[startIndex + 1].ToString("X").PadLeft(2, '0')}"); } ushort sampleCount; uint secondOfCentury; NtpTimeTag timetag; // Parse relevant common header values m_frameSize = (FrameSize)buffer[startIndex + 2]; m_idCode = BigEndian.ToUInt32(buffer, startIndex + 12); sampleCount = BigEndian.ToUInt16(buffer, startIndex + 18); secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 20); // We use an NTP time tag since SEL Fast Message SOC also starts at 1/1/1900 timetag = new NtpTimeTag(secondOfCentury, 0); // Data frames have subsecond time information, so we add this fraction of time to current seconds value timetag = new NtpTimeTag(timetag.Value + sampleCount * 50.0M / 1000.0M); // Cache timestamp value m_timestamp = timetag.ToDateTime().Ticks; }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> public CommonFrameHeader(byte[] buffer, int startIndex) { uint secondOfCentury = BigEndian.ToUInt32(buffer, startIndex); m_sampleCount = BigEndian.ToUInt16(buffer, startIndex + 4); // We go ahead and pre-grab cell's status flags so we can determine framelength - we // leave startindex at 6 so that cell will be able to parse flags as needed - note // this increases needed common frame header size by 2 (i.e., BinaryLength + 2) m_statusFlags = BigEndian.ToUInt16(buffer, startIndex + FixedLength); // NTP timestamps based on NtpTimeTag class are designed to work for dates between // 1968-01-20 and 2104-02-26 based on recommended bit interpretation in RFC-2030. NtpTimeTag timetag = new NtpTimeTag(secondOfCentury, 0); // Cache timestamp value m_timestamp = timetag.ToDateTime().Ticks; }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="configurationFrame">IEEE 1344 <see cref="ConfigurationFrame"/> if already parsed.</param> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> public CommonFrameHeader(ConfigurationFrame configurationFrame, byte[] buffer, int startIndex) { uint secondOfCentury = BigEndian.ToUInt32(buffer, startIndex); m_sampleCount = BigEndian.ToUInt16(buffer, startIndex + 4); // We go ahead and pre-grab cell's status flags so we can determine framelength - we // leave startindex at 6 so that cell will be able to parse flags as needed - note // this increases needed common frame header size by 2 (i.e., BinaryLength + 2) m_statusFlags = BigEndian.ToUInt16(buffer, startIndex + FixedLength); // NTP timestamps based on NtpTimeTag class are designed to work for dates between // 1968-01-20 and 2104-02-26 based on recommended bit interpretation in RFC-2030. NtpTimeTag timetag = new NtpTimeTag(secondOfCentury, 0); // Data frames have subsecond time information, so we add this fraction of time to current seconds value if (TypeID == IEEE1344.FrameType.DataFrame && configurationFrame != null) { timetag.Value += SampleCount / Math.Truncate((double)Common.MaximumSampleCount / (double)configurationFrame.Period) / (double)configurationFrame.FrameRate; } // Cache timestamp value m_timestamp = timetag.ToDateTime().Ticks; }
/// <summary> /// Creates a new <see cref="CommonFrameHeader"/> from given <paramref name="buffer"/>. /// </summary> /// <param name="parseWordCountFromByte">Defines flag that interprets word count in packet header from a byte instead of a word.</param> /// <param name="usePhasorDataFileFormat">Defines flag that determines if source data is in the Phasor Data File Format (i.e., a DST file).</param> /// <param name="configFrame">Previously parsed configuration frame, if available.</param> /// <param name="buffer">Buffer that contains data to parse.</param> /// <param name="startIndex">Start index into buffer where valid data begins.</param> /// <param name="length">Maximum length of valid data from start index.</param> public CommonFrameHeader(bool parseWordCountFromByte, bool usePhasorDataFileFormat, ConfigurationFrame configFrame, byte[] buffer, int startIndex, int length) { uint secondOfCentury; // Determines if format is for DST file or streaming data m_usePhasorDataFileFormat = usePhasorDataFileFormat; if (m_usePhasorDataFileFormat) { // Handle phasor file format data protocol steps if (buffer[startIndex] == PhasorProtocols.Common.SyncByte && buffer[startIndex + 1] == Common.PhasorFileFormatFlag) { // Bail out and leave frame length zero if there's not enough buffer to parse complete fixed portion of header if (length >= DstHeaderFixedLength) { // Read full DST header m_packetNumber = (byte)BPAPDCstream.FrameType.ConfigurationFrame; m_fileType = (FileType)buffer[startIndex + 2]; m_fileVersion = (FileVersion)buffer[startIndex + 3]; m_sourceID = Encoding.ASCII.GetString(buffer, startIndex + 4, 4); uint headerLength = BigEndian.ToUInt32(buffer, startIndex + 8); secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 12); switch (m_fileType) { case FileType.PdcNtp: RoughTimestamp = new NtpTimeTag(secondOfCentury, 0).ToDateTime().Ticks; break; case FileType.PdcUnix: RoughTimestamp = new UnixTimeTag(secondOfCentury).ToDateTime().Ticks; break; default: RoughTimestamp = 0; break; } m_startSample = BigEndian.ToUInt32(buffer, startIndex + 16); m_sampleInterval = BigEndian.ToUInt16(buffer, startIndex + 20); m_sampleRate = BigEndian.ToUInt16(buffer, startIndex + 22); m_rowLength = BigEndian.ToUInt32(buffer, startIndex + 24); m_totalRows = BigEndian.ToUInt32(buffer, startIndex + 28); secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 32); switch (m_fileType) { case FileType.PdcNtp: m_triggerTime = new NtpTimeTag(secondOfCentury, 0).ToDateTime().Ticks; break; case FileType.PdcUnix: m_triggerTime = new UnixTimeTag(secondOfCentury).ToDateTime().Ticks; break; default: m_triggerTime = 0; break; } m_triggerSample = BigEndian.ToUInt32(buffer, startIndex + 36); m_preTriggerRows = BigEndian.ToUInt32(buffer, startIndex + 40); m_triggerPMU = BigEndian.ToUInt16(buffer, startIndex + 44); m_triggerType = BigEndian.ToUInt16(buffer, startIndex + 46); m_userInformation = Encoding.ASCII.GetString(buffer, startIndex + 48, 80).Trim(); m_pmuCount = BigEndian.ToUInt32(buffer, startIndex + 128); FrameLength = unchecked ((ushort)headerLength); } } else { // Must assume this is a data row if there are no sync bytes m_packetNumber = (byte)BPAPDCstream.FrameType.DataFrame; m_rowFlags = BigEndian.ToUInt32(buffer, startIndex); if (configFrame is null) { FrameLength = FixedLength; } else { uint sampleIndex = configFrame.SampleIndex; CommonFrameHeader configFrameHeader = configFrame.CommonHeader; if (configFrameHeader is null) { FrameLength = FixedLength; } else { // Assign row length to make sure parser knows how much data it needs FrameLength = unchecked ((ushort)configFrameHeader.RowLength); // Calculate timestamp as offset plus sample index * frame rate RoughTimestamp = configFrameHeader.RoughTimestamp + Ticks.FromSeconds(sampleIndex * (1.0D / configFrameHeader.FrameRate)); } // Increment sample index for next row configFrame.SampleIndex = sampleIndex + 1; } } } else { // Handle streaming data protocol steps if (buffer[startIndex] != PhasorProtocols.Common.SyncByte) { throw new InvalidOperationException($"Bad data stream, expected sync byte 0xAA as first byte in BPA PDCstream frame, got 0x{buffer[startIndex].ToString("X").PadLeft(2, '0')}"); } // Get packet number m_packetNumber = buffer[startIndex + 1]; // Some older streams have a bad word count (e.g., some data streams have a 0x01 as the third byte // in the stream - this should be a 0x00 to make the word count come out correctly). The following // compensates for this erratic behavior m_wordCount = parseWordCountFromByte ? buffer[startIndex + 3] : BigEndian.ToUInt16(buffer, startIndex + 2); // If this is a data frame get a rough timestamp down to the second (full parse will get accurate timestamp), this way // data frames that don't get fully parsed because configuration hasn't been received will still show a timestamp if (m_packetNumber > 0 && length > 8) { secondOfCentury = BigEndian.ToUInt32(buffer, startIndex + 4); // Until configuration is available, we make a guess at time tag type - this will just be // used for display purposes until a configuration frame arrives. If second of century // is greater than 3155673600 (SOC value for NTP timestamp 1/1/2007), then this is likely // an NTP time stamp (else this is a Unix time tag for the year 2069 - not likely). RoughTimestamp = secondOfCentury > 3155673600 ? new NtpTimeTag(secondOfCentury, 0).ToDateTime().Ticks : new UnixTimeTag(secondOfCentury).ToDateTime().Ticks; } } }