//****************************************************************************************************** // FrameParser.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 01/14/2005 - J. Ritchie Carroll // Generated original version of source code. // 09/15/2009 - Stephen C. Wills // Added new header and license agreement. // 12/17/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using GSF.Parsing; namespace GSF.PhasorProtocols.IEEE1344 { /// /// Represents a frame parser for an IEEE 1344 binary data stream that returns parsed data via events. /// /// /// Frame parser is implemented as a write-only stream - this way data can come from any source. /// public class FrameParser : FrameParserBase { #region [ Members ] // Events /// /// Occurs when an IEEE 1344 has been received. /// /// /// is the that was received. /// public new event EventHandler> ReceivedConfigurationFrame; /// /// Occurs when an IEEE 1344 has been received. /// /// /// is the that was received. /// public new event EventHandler> ReceivedDataFrame; /// /// Occurs when an IEEE 1344 has been received. /// /// /// is the that was received. /// public new event EventHandler> ReceivedHeaderFrame; /// /// Occurs when an IEEE 1344 has been received. /// /// /// is the that was received. /// /// Command frames are normally sent, not received, but there is nothing that prevents this. /// /// public new event EventHandler> ReceivedCommandFrame; // Fields private ConfigurationFrame m_configurationFrame; private FrameImageCollector m_configurationFrameImages; private FrameImageCollector m_headerFrameImages; private ushort m_lastSampleCount; private ushort m_samplesPerFrame; #endregion #region [ Constructors ] /// /// Creates a new . /// /// Frame types that should perform check-sum validation; default to /// Determines if header lengths should be trusted over parsed byte count. public FrameParser(CheckSumValidationFrameTypes checkSumValidationFrameTypes = CheckSumValidationFrameTypes.AllFrames, bool trustHeaderLength = true) : base(checkSumValidationFrameTypes, trustHeaderLength) { m_samplesPerFrame = ushort.MaxValue; } #endregion #region [ Properties ] /// /// Gets or sets current used for parsing 's encountered in the data stream from a device. /// /// /// If a has been parsed, this will return a reference to the parsed frame. Consumer can manually assign a /// to start parsing data if one has not been encountered in the stream. /// public override IConfigurationFrame ConfigurationFrame { get => m_configurationFrame; set => m_configurationFrame = CastToDerivedConfigurationFrame(value); } /// /// Gets flag that determines if this protocol parsing implementation uses synchronization bytes. /// /// /// The IEEE 1344 protocol does not use synchronization bytes, as a result this property returns false. /// public override bool ProtocolUsesSyncBytes => false; // IEEE 1344 doesn't use synchronization bytes #endregion #region [ Methods ] /// /// Start the data parser. /// public override void Start() { // We narrow down parsing types to just those needed... base.Start(new[] { typeof(DataFrame), typeof(ConfigurationFrame), typeof(HeaderFrame) }); } /// /// Parses a common header instance that implements for the output type represented /// in the binary image. /// /// Buffer containing data to parse. /// Offset index into buffer that represents where to start parsing. /// Maximum length of valid data from offset. /// The which includes a type ID for the to be parsed. /// /// /// Derived classes need to provide a common header instance (i.e., class that implements ) /// for the output types; this will primarily include an ID of the that the data image represents. This parsing is /// only for common header information, actual parsing will be handled by output type via its /// method. This header image should also be used to add needed complex state information about the output type being parsed if needed. /// /// /// If there is not enough buffer available to parse common header (as determined by ), return null. Also, if /// the protocol allows frame length to be determined at the time common header is being parsed and there is not enough buffer to parse /// the entire frame, it will be optimal to prevent further parsing by returning null. /// /// protected override ICommonHeader ParseCommonHeader(byte[] buffer, int offset, int length) { // See if there is enough data in the buffer to parse the common frame header. // Note that in order to get status flags (which contain frame length), we need at least two more bytes if (length < CommonFrameHeader.FixedLength + 2) return null; // Parse common frame header CommonFrameHeader parsedFrameHeader = new(buffer, offset); // As an optimization, we also make sure entire frame buffer image is available to be parsed - by doing this // we eliminate the need to validate length on all subsequent data elements that comprise the frame if (length < parsedFrameHeader.FrameLength) return null; // Expose the frame buffer image in case client needs this data for any reason OnReceivedFrameBufferImage(parsedFrameHeader.FrameType, buffer, offset, parsedFrameHeader.FrameLength); // Handle special parsing states switch (parsedFrameHeader.TypeID) { case FrameType.DataFrame: // Assign data frame parsing state parsedFrameHeader.State = new DataFrameParsingState(parsedFrameHeader.FrameLength, m_configurationFrame, DataCell.CreateNewCell, TrustHeaderLength, ValidateDataFrameCheckSum); break; case FrameType.ConfigurationFrame: // Assign configuration frame parsing state (note that IEEE 1344 only supports a single device, hence 1 cell) parsedFrameHeader.State = new ConfigurationFrameParsingState(parsedFrameHeader.FrameLength, ConfigurationCell.CreateNewCell, TrustHeaderLength, ValidateConfigurationFrameCheckSum, 1); // Cumulate configuration frame images... CumulateFrameImage(parsedFrameHeader, buffer, offset, ref m_configurationFrameImages); break; case FrameType.HeaderFrame: // Assign header frame parsing state parsedFrameHeader.State = new HeaderFrameParsingState(parsedFrameHeader.FrameLength, parsedFrameHeader.DataLength, TrustHeaderLength, ValidateHeaderFrameCheckSum); // Cumulate header frame images... CumulateFrameImage(parsedFrameHeader, buffer, offset, ref m_headerFrameImages); break; } return parsedFrameHeader; } /// /// Raises the event. /// /// to send to event. protected override void OnReceivedConfigurationFrame(IConfigurationFrame frame) { // IEEE 1344 configuration frames can span multiple frame images, so we don't allow base class to raise this event until all frames have been assembled... if (frame is not ISupportFrameImage frameImage) return; if (frameImage.CommonHeader is not CommonFrameHeader commonHeader || !commonHeader.IsLastFrame) return; base.OnReceivedConfigurationFrame(frame); // Cache new configuration frame for parsing subsequent data frames... if (frame is ConfigurationFrame configurationFrame) m_configurationFrame = configurationFrame; } /// /// Raises the event. /// /// to send to event. protected override void OnReceivedHeaderFrame(IHeaderFrame frame) { // IEEE 1344 header frames can span multiple frame images, so we don't allow base class to raise this event until all frames have been assembled... if (frame is not ISupportFrameImage frameImage) return; if (frameImage.CommonHeader is CommonFrameHeader commonHeader && commonHeader.IsLastFrame) base.OnReceivedHeaderFrame(frame); } /// /// Raises the event. /// /// to send to event. protected override void OnReceivedDataFrame(IDataFrame frame) { if (frame is DataFrame dataFrame) { // Keep track of the minimum number of samples between // data frames as the number of samples per frame ushort sampleCount = dataFrame.CommonHeader.SampleCount; ushort diff = (ushort)(sampleCount - m_lastSampleCount); if (diff != 0 && diff < m_samplesPerFrame) m_samplesPerFrame = diff; m_lastSampleCount = sampleCount; // Calculate subsecond time information based on sample count, // then add this fraction of time to current seconds value if (dataFrame.ConfigurationFrame is not null) { int frameIndex = sampleCount / m_samplesPerFrame; long subsecondTicks = frameIndex * Ticks.PerSecond / dataFrame.ConfigurationFrame.FrameRate; dataFrame.CommonHeader.Timestamp += subsecondTicks; } } base.OnReceivedDataFrame(frame); } /// /// Casts the parsed to its specific implementation (i.e., , , or ). /// /// that was parsed by that implements protocol specific common frame header interface. protected override void OnReceivedChannelFrame(IChannelFrame frame) { // Raise abstract channel frame events as a priority (i.e., IDataFrame, IConfigurationFrame, etc.) base.OnReceivedChannelFrame(frame); // Raise IEEE 1344 specific channel frame events, if any have been subscribed if (frame is null || ReceivedDataFrame is null && ReceivedConfigurationFrame is null && ReceivedHeaderFrame is null && ReceivedCommandFrame is null) return; switch (frame) { case DataFrame dataFrame: { ReceivedDataFrame?.Invoke(this, new EventArgs(dataFrame)); break; } case ConfigurationFrame configFrame when configFrame.CommonHeader.IsLastFrame: { ReceivedConfigurationFrame?.Invoke(this, new EventArgs(configFrame)); break; } case HeaderFrame headerFrame when headerFrame.CommonHeader.IsLastFrame: { ReceivedHeaderFrame?.Invoke(this, new EventArgs(headerFrame)); break; } case CommandFrame commandFrame: { ReceivedCommandFrame?.Invoke(this, new EventArgs(commandFrame)); break; } } } #endregion #region [ Static ] // Static Methods // Cumulates frame images internal static void CumulateFrameImage(CommonFrameHeader parsedFrameHeader, byte[] buffer, int offset, ref FrameImageCollector frameImages) { // If this is the first frame, cumulate all partial frames together as one complete frame if (parsedFrameHeader.IsFirstFrame) frameImages = new FrameImageCollector(); try { // Append next frame image frameImages.AppendFrameImage(buffer, offset, parsedFrameHeader.FrameLength); } catch { // Stop accumulation if CRC check fails frameImages = null; throw; } // Store a reference to frame image collection in common header state so configuration frame // can be parsed from entire combined binary image collection when last frame is received parsedFrameHeader.FrameImages = frameImages; // Clear local reference to frame image collection if this is the last frame if (parsedFrameHeader.IsLastFrame) frameImages = null; } // Attempts to cast given frame into an IEEE 1344 configuration frame - theoretically this will // allow the same configuration frame to be used for any protocol implementation internal static ConfigurationFrame CastToDerivedConfigurationFrame(IConfigurationFrame sourceFrame) { // See if frame is already an IEEE 1344 configuration frame (if so, we don't need to do any work) if (sourceFrame is ConfigurationFrame derivedFrame) return derivedFrame; // Create a new IEEE 1344 configuration frame converted from equivalent configuration information derivedFrame = new ConfigurationFrame(sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate); foreach (IConfigurationCell sourceCell in sourceFrame.Cells) { // Create new derived configuration cell ConfigurationCell derivedCell = new(derivedFrame, sourceCell.IDCode, sourceCell.NominalFrequency); // Create equivalent derived phasor definitions foreach (IPhasorDefinition sourcePhasor in sourceCell.PhasorDefinitions) derivedCell.PhasorDefinitions.Add(new PhasorDefinition(derivedCell, sourcePhasor.Label, sourcePhasor.ScalingValue, sourcePhasor.Offset, sourcePhasor.PhasorType, null)); // Create equivalent derived frequency definition IFrequencyDefinition sourceFrequency = sourceCell.FrequencyDefinition; if (sourceFrequency is not null) derivedCell.FrequencyDefinition = new FrequencyDefinition(derivedCell, sourceFrequency.Label); // IEEE 1344 does not define analog values... // Create equivalent derived digital definitions foreach (IDigitalDefinition sourceDigital in sourceCell.DigitalDefinitions) derivedCell.DigitalDefinitions.Add(new DigitalDefinition(derivedCell, sourceDigital.Label)); // Add cell to frame derivedFrame.Cells.Add(derivedCell); // IEEE-1344 only supports one cell break; } return derivedFrame; } #endregion } }