//******************************************************************************************************
// 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:
// ----------------------------------------------------------------------------------------------------
// 02/08/2007 - J. Ritchie Carroll & Jian Ryan Zuo
// 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 System.Text;
using GSF.Parsing;
using GSF.Units.EE;
namespace GSF.PhasorProtocols.FNET
{
///
/// Represents a frame parser for a F-NET text based 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 a F-NET has been received.
///
///
/// is the that was received.
///
public new event EventHandler> ReceivedConfigurationFrame;
///
/// Occurs when a F-NET has been received.
///
///
/// is the that was received.
///
public new event EventHandler> ReceivedDataFrame;
// Fields
private ConfigurationFrame m_configurationFrame;
#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.
/// The defined frame rate of this .
/// The nominal of this .
/// The time offset of F-NET device in .
/// The station name of the F-NET device.
public FrameParser(CheckSumValidationFrameTypes checkSumValidationFrameTypes = CheckSumValidationFrameTypes.AllFrames, bool trustHeaderLength = true, ushort frameRate = Common.DefaultFrameRate, LineFrequency nominalFrequency = Common.DefaultNominalFrequency, long timeOffset = Common.DefaultTimeOffset, string stationName = Common.DefaultStationName)
: base(checkSumValidationFrameTypes, trustHeaderLength)
{
// Initialize protocol synchronization bytes for this frame parser
base.ProtocolSyncBytes = new[] { Common.StartByte };
FrameRate = frameRate;
NominalFrequency = nominalFrequency;
TimeOffset = timeOffset;
StationName = stationName;
}
#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 F-NET protocol parsing implementation uses synchronization bytes.
///
public override bool ProtocolUsesSyncBytes => true;
///
/// Gets or sets time offset of F-NET device in .
///
///
/// F-NET devices normally report time in 11 seconds past real-time, this property defines the offset for this artificial delay.
/// Note that the parameter value is in ticks to allow a very high-resolution offset; 1 second = 10000000 ticks.
///
public Ticks TimeOffset { get; set; }
///
/// Gets or sets the configured frame rate for the F-NET device.
///
///
/// This is typically set to 10 frames per second.
///
public ushort FrameRate { get; set; }
///
/// Gets or sets the nominal of the F-NET device.
///
public LineFrequency NominalFrequency { get; set; }
///
/// Gets or sets the station name for the F-NET device.
///
public string StationName { get; set; }
///
/// Gets current descriptive status of the .
///
public override string Status
{
get
{
if (m_configurationFrame is null)
return base.Status;
StringBuilder status = new();
status.Append(" Reported longitude: ");
status.Append(m_configurationFrame.Longitude);
status.Append('°');
status.AppendLine();
status.Append(" Reported latitude: ");
status.Append(m_configurationFrame.Latitude);
status.Append('°');
status.AppendLine();
status.Append(" Number of satellites: ");
status.Append(m_configurationFrame.NumberOfSatellites);
status.AppendLine();
status.Append(base.Status);
return status.ToString();
}
}
///
/// Gets or sets any connection specific that may be needed for parsing.
///
public override IConnectionParameters ConnectionParameters
{
get => base.ConnectionParameters;
set
{
if (value is not ConnectionParameters parameters)
return;
base.ConnectionParameters = parameters;
// Assign new incoming connection parameter values
TimeOffset = parameters.TimeOffset;
FrameRate = parameters.FrameRate;
NominalFrequency = parameters.NominalFrequency;
StationName = parameters.StationName;
}
}
#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) });
}
///
/// 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)
{
// Calculate a maximum reasonable scan size for the buffer
int scanLength = length > Common.MaximumPracticalFrameSize ? Common.MaximumPracticalFrameSize : length;
// See if there is enough data in the buffer to parse the common frame header by scanning for the F-NET termination byte
if (scanLength > 0 && Array.IndexOf(buffer, Common.EndByte, offset, scanLength) >= 0)
{
// Pre-parse F-NET data row...
CommonFrameHeader parsedFrameHeader = new(buffer, offset, length);
int parsedLength = parsedFrameHeader.ParsedLength;
if (parsedLength > 0)
{
// Create configuration frame if it doesn't exist
if (m_configurationFrame is null)
{
string[] data = parsedFrameHeader.DataElements;
// Create virtual configuration frame
m_configurationFrame = new ConfigurationFrame(ushort.Parse(data[Element.UnitID]), DateTime.UtcNow.Ticks, FrameRate, NominalFrequency, TimeOffset, StationName);
// Notify clients of new configuration frame
OnReceivedChannelFrame(m_configurationFrame);
}
if (m_configurationFrame is null)
return null;
// Assign common header and data frame parsing state
parsedFrameHeader.State = new DataFrameParsingState(parsedLength, m_configurationFrame, DataCell.CreateNewCell, TrustHeaderLength, ValidateDataFrameCheckSum);
// Expose the frame buffer image in case client needs this data for any reason
OnReceivedFrameBufferImage(FundamentalFrameType.DataFrame, buffer, offset, parsedLength);
return parsedFrameHeader;
}
}
else if (scanLength == Common.MaximumPracticalFrameSize)
{
throw new InvalidOperationException($"Possible bad F-NET data stream, scanned {Common.MaximumPracticalFrameSize:N0} bytes without finding an expected termination byte 0x0");
}
return null;
}
///
/// 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 F-NET specific channel frame events, if any have been subscribed
if (frame is null || ReceivedDataFrame is null && ReceivedConfigurationFrame is null)
return;
switch (frame)
{
case DataFrame dataFrame:
{
ReceivedDataFrame?.Invoke(this, new EventArgs(dataFrame));
break;
}
case ConfigurationFrame configFrame:
{
ReceivedConfigurationFrame?.Invoke(this, new EventArgs(configFrame));
break;
}
}
}
#endregion
#region [ Static ]
// Attempts to cast given frame into a F-NET 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 a F-NET frame (if so, we don't need to do any work)
if (sourceFrame is ConfigurationFrame derivedFrame)
return derivedFrame;
// Create a new F-NET configuration frame converted from equivalent configuration information; F-NET only supports one device
derivedFrame = sourceFrame.Cells.Count > 0 ?
new ConfigurationFrame(sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate, sourceFrame.Cells[0].NominalFrequency, Common.DefaultTimeOffset, Common.DefaultStationName) :
new ConfigurationFrame(sourceFrame.IDCode, sourceFrame.Timestamp, sourceFrame.FrameRate, LineFrequency.Hz60, Common.DefaultTimeOffset, Common.DefaultStationName);
return derivedFrame;
}
#endregion
}
}